]>
Commit | Line | Data |
---|---|---|
f9a162a2 | 1 | /* |
9e05f219 | 2 | * Copyright (C) 2012-2018 Tobias Brunner |
19ef2aec TB |
3 | * |
4 | * Copyright (C) secunet Security Networks AG | |
f9a162a2 TB |
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 | ||
17 | package org.strongswan.android.ui; | |
18 | ||
9e05f219 | 19 | import android.content.Context; |
c9761655 | 20 | import android.os.Build; |
f9a162a2 | 21 | import android.os.Bundle; |
ae10e8c4 | 22 | import android.os.FileObserver; |
f9a162a2 | 23 | import android.os.Handler; |
dc351a30 | 24 | import android.os.Looper; |
f9a162a2 TB |
25 | import android.view.LayoutInflater; |
26 | import android.view.View; | |
27 | import android.view.ViewGroup; | |
9e05f219 TB |
28 | import android.widget.ArrayAdapter; |
29 | import android.widget.ListView; | |
f9a162a2 | 30 | |
7c5fec3a TB |
31 | import org.strongswan.android.R; |
32 | import org.strongswan.android.logic.CharonVpnService; | |
33 | ||
34 | import java.io.BufferedReader; | |
35 | import java.io.File; | |
36 | import java.io.FileNotFoundException; | |
37 | import java.io.FileReader; | |
38 | import java.io.StringReader; | |
74d44e15 | 39 | import java.util.ArrayList; |
7c5fec3a | 40 | |
3b9696fc | 41 | import androidx.annotation.NonNull; |
c9761655 | 42 | import androidx.annotation.RequiresApi; |
3b9696fc TB |
43 | import androidx.fragment.app.Fragment; |
44 | ||
9e05f219 | 45 | public class LogFragment extends Fragment |
f9a162a2 | 46 | { |
9e05f219 | 47 | private static String SCROLL_POSITION = "SCROLL_POSITION"; |
f9a162a2 TB |
48 | private String mLogFilePath; |
49 | private Handler mLogHandler; | |
9e05f219 TB |
50 | private ListView mLog; |
51 | private LogAdapter mLogAdapter; | |
ae10e8c4 | 52 | private FileObserver mDirectoryObserver; |
9e05f219 | 53 | private int mScrollPosition; |
f9a162a2 TB |
54 | |
55 | @Override | |
56 | public void onCreate(Bundle savedInstanceState) | |
57 | { | |
58 | super.onCreate(savedInstanceState); | |
59 | ||
60 | mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; | |
9e05f219 | 61 | |
dc351a30 | 62 | mLogHandler = new Handler(Looper.getMainLooper()); |
ae10e8c4 | 63 | |
c9761655 TB |
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 | } | |
f9a162a2 TB |
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); | |
9e05f219 TB |
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 | } | |
f9a162a2 TB |
89 | return view; |
90 | } | |
91 | ||
9e05f219 TB |
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 | ||
f9a162a2 TB |
107 | @Override |
108 | public void onStart() | |
109 | { | |
110 | super.onStart(); | |
9e05f219 | 111 | mLogAdapter.restart(); |
ae10e8c4 TB |
112 | mDirectoryObserver.startWatching(); |
113 | } | |
114 | ||
115 | @Override | |
116 | public void onStop() | |
117 | { | |
118 | super.onStop(); | |
119 | mDirectoryObserver.stopWatching(); | |
9e05f219 | 120 | mLogAdapter.stop(); |
ae10e8c4 TB |
121 | } |
122 | ||
9e05f219 | 123 | private class LogAdapter extends ArrayAdapter<String> implements Runnable |
ae10e8c4 | 124 | { |
9e05f219 TB |
125 | private BufferedReader mReader; |
126 | private Thread mThread; | |
127 | private volatile boolean mRunning; | |
128 | ||
129 | public LogAdapter(@NonNull Context context) | |
f9a162a2 | 130 | { |
9e05f219 | 131 | super(context, R.layout.log_list_item, R.id.log_line); |
f9a162a2 | 132 | } |
9e05f219 TB |
133 | |
134 | public void restart() | |
f9a162a2 | 135 | { |
9e05f219 TB |
136 | if (mRunning) |
137 | { | |
138 | stop(); | |
139 | } | |
ae10e8c4 | 140 | |
9e05f219 | 141 | clear(); |
f9a162a2 | 142 | |
9e05f219 TB |
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(); | |
f9a162a2 | 154 | } |
9e05f219 TB |
155 | |
156 | public void stop() | |
f9a162a2 | 157 | { |
9e05f219 TB |
158 | try |
159 | { | |
160 | mRunning = false; | |
161 | mThread.interrupt(); | |
162 | mThread.join(); | |
163 | } | |
164 | catch (InterruptedException e) | |
165 | { | |
166 | } | |
f9a162a2 | 167 | } |
f9a162a2 | 168 | |
9e05f219 TB |
169 | private void logLines(final ArrayList<String> lines) |
170 | { | |
171 | mLogHandler.post(() -> { | |
172 | boolean scroll = getCount() == 0; | |
173 | setNotifyOnChange(false); | |
74d44e15 | 174 | for (String line : lines) |
27cf3e66 TB |
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 | } | |
9e05f219 | 180 | add(line); |
74d44e15 | 181 | } |
9e05f219 TB |
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 | } | |
74d44e15 | 189 | |
9e05f219 TB |
190 | @Override |
191 | public void run() | |
f9a162a2 | 192 | { |
9e05f219 TB |
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) | |
74d44e15 | 201 | { |
9e05f219 TB |
202 | if (lines != null) |
203 | { | |
204 | logLines(lines); | |
205 | lines = null; | |
206 | } | |
207 | /* wait until there is more to log */ | |
208 | Thread.sleep(1000); | |
74d44e15 | 209 | } |
9e05f219 | 210 | else |
74d44e15 | 211 | { |
9e05f219 TB |
212 | if (lines == null) |
213 | { | |
214 | lines = new ArrayList<>(); | |
215 | } | |
216 | lines.add(line); | |
74d44e15 | 217 | } |
9e05f219 TB |
218 | } |
219 | catch (Exception e) | |
220 | { | |
221 | break; | |
f9a162a2 TB |
222 | } |
223 | } | |
9e05f219 | 224 | if (lines != null) |
f9a162a2 | 225 | { |
9e05f219 | 226 | logLines(lines); |
f9a162a2 TB |
227 | } |
228 | } | |
229 | } | |
ae10e8c4 TB |
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 | { | |
c9761655 TB |
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(); | |
ae10e8c4 | 241 | |
c9761655 | 242 | @SuppressWarnings("deprecation") |
ae10e8c4 TB |
243 | public LogDirectoryObserver(String path) |
244 | { | |
c9761655 TB |
245 | super(path, mMask); |
246 | } | |
247 | ||
248 | @RequiresApi(api = Build.VERSION_CODES.Q) | |
249 | public LogDirectoryObserver(File path) | |
250 | { | |
251 | super(path, mMask); | |
ae10e8c4 TB |
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 | { | |
9e05f219 | 287 | mLogAdapter.restart(); |
ae10e8c4 TB |
288 | } |
289 | }); | |
290 | } | |
291 | } | |
f9a162a2 | 292 | } |