2 * Copyright (C) 2012-2018 Tobias Brunner
4 * Copyright (C) secunet Security Networks AG
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>.
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
17 package org
.strongswan
.android
.ui
;
19 import android
.content
.Context
;
20 import android
.os
.Build
;
21 import android
.os
.Bundle
;
22 import android
.os
.FileObserver
;
23 import android
.os
.Handler
;
24 import android
.os
.Looper
;
25 import android
.view
.LayoutInflater
;
26 import android
.view
.View
;
27 import android
.view
.ViewGroup
;
28 import android
.widget
.ArrayAdapter
;
29 import android
.widget
.ListView
;
31 import org
.strongswan
.android
.R
;
32 import org
.strongswan
.android
.logic
.CharonVpnService
;
34 import java
.io
.BufferedReader
;
36 import java
.io
.FileNotFoundException
;
37 import java
.io
.FileReader
;
38 import java
.io
.StringReader
;
39 import java
.util
.ArrayList
;
41 import androidx
.annotation
.NonNull
;
42 import androidx
.annotation
.RequiresApi
;
43 import androidx
.fragment
.app
.Fragment
;
45 public class LogFragment
extends Fragment
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
;
56 public void onCreate(Bundle savedInstanceState
)
58 super.onCreate(savedInstanceState
);
60 mLogFilePath
= getActivity().getFilesDir() + File
.separator
+ CharonVpnService
.LOG_FILE
;
62 mLogHandler
= new Handler(Looper
.getMainLooper());
64 File logdir
= getActivity().getFilesDir();
65 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.Q
)
67 mDirectoryObserver
= new LogDirectoryObserver(logdir
);
71 mDirectoryObserver
= new LogDirectoryObserver(logdir
.getAbsolutePath());
76 public View
onCreateView(LayoutInflater inflater
, ViewGroup container
, Bundle savedInstanceState
)
78 View view
= inflater
.inflate(R
.layout
.log_fragment
, null);
80 mLogAdapter
= new LogAdapter(getActivity());
81 mLog
= view
.findViewById(R
.id
.log
);
82 mLog
.setAdapter(mLogAdapter
);
85 if (savedInstanceState
!= null)
87 mScrollPosition
= savedInstanceState
.getInt(SCROLL_POSITION
, mScrollPosition
);
93 public void onSaveInstanceState(Bundle outState
)
95 super.onSaveInstanceState(outState
);
97 if (mLog
.getLastVisiblePosition() == (mLogAdapter
.getCount() - 1))
99 outState
.putInt(SCROLL_POSITION
, -1);
103 outState
.putInt(SCROLL_POSITION
, mLog
.getFirstVisiblePosition());
108 public void onStart()
111 mLogAdapter
.restart();
112 mDirectoryObserver
.startWatching();
119 mDirectoryObserver
.stopWatching();
123 private class LogAdapter
extends ArrayAdapter
<String
> implements Runnable
125 private BufferedReader mReader
;
126 private Thread mThread
;
127 private volatile boolean mRunning
;
129 public LogAdapter(@NonNull Context context
)
131 super(context
, R
.layout
.log_list_item
, R
.id
.log_line
);
134 public void restart()
145 mReader
= new BufferedReader(new FileReader(mLogFilePath
));
147 catch (FileNotFoundException e
)
149 mReader
= new BufferedReader(new StringReader(""));
152 mThread
= new Thread(this);
164 catch (InterruptedException e
)
169 private void logLines(final ArrayList
<String
> lines
)
171 mLogHandler
.post(() -> {
172 boolean scroll
= getCount() == 0;
173 setNotifyOnChange(false);
174 for (String line
: lines
)
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
;
182 notifyDataSetChanged();
184 { /* scroll to the bottom or saved position after adding the first batch */
185 mLogHandler
.post(() -> mLog
.setSelection(mScrollPosition
== -1 ?
getCount() - 1 : mScrollPosition
));
193 ArrayList
<String
> lines
= null;
198 { /* this works as long as the file is not truncated */
199 String line
= mReader
.readLine();
207 /* wait until there is more to log */
214 lines
= new ArrayList
<>();
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.
236 private class LogDirectoryObserver
extends FileObserver
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();
242 @SuppressWarnings("deprecation")
243 public LogDirectoryObserver(String path
)
248 @RequiresApi(api
= Build
.VERSION_CODES
.Q
)
249 public LogDirectoryObserver(File path
)
255 public void onEvent(int event
, String path
)
257 if (path
== null || !path
.equals(CharonVpnService
.LOG_FILE
))
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
:
268 case FileObserver
.MODIFY
:
269 /* if the size got smaller reopen the log file, as it was probably truncated */
270 long size
= mFile
.length();
280 private void restartLogReader()
282 /* we are called from a separate thread, so we use the handler */
283 mLogHandler
.post(new Runnable() {
287 mLogAdapter
.restart();