]> git.ipfire.org Git - thirdparty/strongswan.git/blame_incremental - src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
nm: Don't set DL_LIBS to 'none required' in configure script
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / LogFragment.java
... / ...
CommitLineData
1/*
2 * Copyright (C) 2012-2018 Tobias Brunner
3 *
4 * Copyright (C) secunet Security Networks AG
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17package org.strongswan.android.ui;
18
19import android.content.Context;
20import android.os.Build;
21import android.os.Bundle;
22import android.os.FileObserver;
23import android.os.Handler;
24import android.os.Looper;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.ArrayAdapter;
29import android.widget.ListView;
30
31import org.strongswan.android.R;
32import org.strongswan.android.logic.CharonVpnService;
33
34import java.io.BufferedReader;
35import java.io.File;
36import java.io.FileNotFoundException;
37import java.io.FileReader;
38import java.io.StringReader;
39import java.util.ArrayList;
40
41import androidx.annotation.NonNull;
42import androidx.annotation.RequiresApi;
43import androidx.fragment.app.Fragment;
44
45public class LogFragment extends Fragment
46{
47 private static String SCROLL_POSITION = "SCROLL_POSITION";
48 private String mLogFilePath;
49 private Handler mLogHandler;
50 private ListView mLog;
51 private LogAdapter mLogAdapter;
52 private FileObserver mDirectoryObserver;
53 private int mScrollPosition;
54
55 @Override
56 public void onCreate(Bundle savedInstanceState)
57 {
58 super.onCreate(savedInstanceState);
59
60 mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE;
61
62 mLogHandler = new Handler(Looper.getMainLooper());
63
64 File logdir = getActivity().getFilesDir();
65 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
66 {
67 mDirectoryObserver = new LogDirectoryObserver(logdir);
68 }
69 else
70 {
71 mDirectoryObserver = new LogDirectoryObserver(logdir.getAbsolutePath());
72 }
73 }
74
75 @Override
76 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
77 {
78 View view = inflater.inflate(R.layout.log_fragment, null);
79
80 mLogAdapter = new LogAdapter(getActivity());
81 mLog = view.findViewById(R.id.log);
82 mLog.setAdapter(mLogAdapter);
83
84 mScrollPosition = -1;
85 if (savedInstanceState != null)
86 {
87 mScrollPosition = savedInstanceState.getInt(SCROLL_POSITION, mScrollPosition);
88 }
89 return view;
90 }
91
92 @Override
93 public void onSaveInstanceState(Bundle outState)
94 {
95 super.onSaveInstanceState(outState);
96
97 if (mLog.getLastVisiblePosition() == (mLogAdapter.getCount() - 1))
98 {
99 outState.putInt(SCROLL_POSITION, -1);
100 }
101 else
102 {
103 outState.putInt(SCROLL_POSITION, mLog.getFirstVisiblePosition());
104 }
105 }
106
107 @Override
108 public void onStart()
109 {
110 super.onStart();
111 mLogAdapter.restart();
112 mDirectoryObserver.startWatching();
113 }
114
115 @Override
116 public void onStop()
117 {
118 super.onStop();
119 mDirectoryObserver.stopWatching();
120 mLogAdapter.stop();
121 }
122
123 private class LogAdapter extends ArrayAdapter<String> implements Runnable
124 {
125 private BufferedReader mReader;
126 private Thread mThread;
127 private volatile boolean mRunning;
128
129 public LogAdapter(@NonNull Context context)
130 {
131 super(context, R.layout.log_list_item, R.id.log_line);
132 }
133
134 public void restart()
135 {
136 if (mRunning)
137 {
138 stop();
139 }
140
141 clear();
142
143 try
144 {
145 mReader = new BufferedReader(new FileReader(mLogFilePath));
146 }
147 catch (FileNotFoundException e)
148 {
149 mReader = new BufferedReader(new StringReader(""));
150 }
151 mRunning = true;
152 mThread = new Thread(this);
153 mThread.start();
154 }
155
156 public void stop()
157 {
158 try
159 {
160 mRunning = false;
161 mThread.interrupt();
162 mThread.join();
163 }
164 catch (InterruptedException e)
165 {
166 }
167 }
168
169 private void logLines(final ArrayList<String> lines)
170 {
171 mLogHandler.post(() -> {
172 boolean scroll = getCount() == 0;
173 setNotifyOnChange(false);
174 for (String line : lines)
175 {
176 if (getResources().getConfiguration().screenWidthDp < 600)
177 { /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */
178 line = line.length() > 18 ? line.substring(18) : line;
179 }
180 add(line);
181 }
182 notifyDataSetChanged();
183 if (scroll)
184 { /* scroll to the bottom or saved position after adding the first batch */
185 mLogHandler.post(() -> mLog.setSelection(mScrollPosition == -1 ? getCount() - 1 : mScrollPosition));
186 }
187 });
188 }
189
190 @Override
191 public void run()
192 {
193 ArrayList<String> lines = null;
194
195 while (mRunning)
196 {
197 try
198 { /* this works as long as the file is not truncated */
199 String line = mReader.readLine();
200 if (line == null)
201 {
202 if (lines != null)
203 {
204 logLines(lines);
205 lines = null;
206 }
207 /* wait until there is more to log */
208 Thread.sleep(1000);
209 }
210 else
211 {
212 if (lines == null)
213 {
214 lines = new ArrayList<>();
215 }
216 lines.add(line);
217 }
218 }
219 catch (Exception e)
220 {
221 break;
222 }
223 }
224 if (lines != null)
225 {
226 logLines(lines);
227 }
228 }
229 }
230
231 /**
232 * FileObserver that checks for changes regarding the log file. Since charon
233 * truncates it (for which there is no explicit event) we check for any modification
234 * to the file, keep track of the file size and reopen it if it got smaller.
235 */
236 private class LogDirectoryObserver extends FileObserver
237 {
238 private static final int mMask = FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE;
239 private final File mFile = new File(mLogFilePath);
240 private long mSize = mFile.length();
241
242 @SuppressWarnings("deprecation")
243 public LogDirectoryObserver(String path)
244 {
245 super(path, mMask);
246 }
247
248 @RequiresApi(api = Build.VERSION_CODES.Q)
249 public LogDirectoryObserver(File path)
250 {
251 super(path, mMask);
252 }
253
254 @Override
255 public void onEvent(int event, String path)
256 {
257 if (path == null || !path.equals(CharonVpnService.LOG_FILE))
258 {
259 return;
260 }
261 switch (event)
262 { /* even though we only subscribed for these we check them,
263 * as strange events are sometimes received */
264 case FileObserver.CREATE:
265 case FileObserver.DELETE:
266 restartLogReader();
267 break;
268 case FileObserver.MODIFY:
269 /* if the size got smaller reopen the log file, as it was probably truncated */
270 long size = mFile.length();
271 if (size < mSize)
272 {
273 restartLogReader();
274 }
275 mSize = size;
276 break;
277 }
278 }
279
280 private void restartLogReader()
281 {
282 /* we are called from a separate thread, so we use the handler */
283 mLogHandler.post(new Runnable() {
284 @Override
285 public void run()
286 {
287 mLogAdapter.restart();
288 }
289 });
290 }
291 }
292}