Commit 5986b329 authored by akwizgran's avatar akwizgran

Save log messages between runs in case the process is killed.

parent f2c2f803
package org.briarproject.snooze;
import android.util.Log;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class FileLogger extends Thread {
private static final String TAG = FileLogger.class.getSimpleName();
private final File logFile;
private final LogLoadedCallback callback;
private final BlockingQueue<String> messagesToWrite;
FileLogger(File logFile, LogLoadedCallback callback) {
this.logFile = logFile;
this.callback = callback;
messagesToWrite = new LinkedBlockingQueue<>();
setDaemon(true);
}
@Override
public void run() {
InputStream in = null;
PrintStream out = null;
try {
// Load any existing messages and pass them to the callback
logFile.createNewFile();
in = new FileInputStream(logFile);
Scanner scanner = new Scanner(in);
List<String> messagesLoaded = new ArrayList<>();
while (scanner.hasNextLine()) messagesLoaded.add(scanner.nextLine());
in.close();
callback.onLogLoaded(messagesLoaded);
// Append messages to the file until interrupted
out = new PrintStream(new FileOutputStream(logFile, true), true);
while (!isInterrupted()) {
String message = messagesToWrite.take();
if (message.isEmpty()) {
Log.i(TAG, "Clearing log file");
out.close();
out = new PrintStream(new FileOutputStream(logFile, false), true);
} else {
out.println(message);
}
}
} catch (IOException e) {
Log.e(TAG, e.toString());
} catch (InterruptedException e) {
Log.i(TAG, "Interrupted");
} finally {
tryToClose(in);
tryToClose(out);
}
}
private void tryToClose(@Nullable Closeable closeable) {
try {
if (closeable != null) closeable.close();
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
void writeMessage(String message) {
if (message.isEmpty()) throw new IllegalArgumentException();
messagesToWrite.add(message);
}
void clearLog() {
messagesToWrite.add("");
}
interface LogLoadedCallback {
void onLogLoaded(List<String> messagesLoaded);
}
}
package org.briarproject.snooze;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.support.annotation.UiThread;
import android.util.Log;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import static android.content.Context.POWER_SERVICE;
import static java.util.Locale.US;
class Logger {
class Logger implements FileLogger.LogLoadedCallback {
private final Handler handler;
private final Context appContext;
private final String tag;
private final List<LogListener> listeners = new CopyOnWriteArrayList<>();
private final StringBuffer messages = new StringBuffer();
private final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss", US);
private final Handler handler;
private final FileLogger fileLogger;
private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", US);
Logger(Context ctx, String tag) {
handler = new Handler(ctx.getMainLooper());
private final List<LogListener> listeners = new ArrayList<>(); // Locking: this
private final List<String> messages = new ArrayList<>(); // Locking: this
private boolean logLoaded = false, logCleared = false; // Locking: this
Logger(Context appContext, String tag) {
this.appContext = appContext;
this.tag = tag;
handler = new Handler(appContext.getMainLooper());
fileLogger = new FileLogger(new File(appContext.getFilesDir(), tag + ".onMessageLogged"), this);
fileLogger.start();
logInitialMessages();
}
private void logInitialMessages() {
log("Manufacturer: " + Build.MANUFACTURER);
log("Model: " + Build.MODEL);
log("Device: " + Build.DEVICE);
log("Product: " + Build.PRODUCT);
log("API version: " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 23) {
PowerManager pm = (PowerManager) appContext.getSystemService(POWER_SERVICE);
boolean ignoring = pm.isIgnoringBatteryOptimizations(appContext.getPackageName());
log("Ignoring battery optimizations: " + ignoring);
}
}
String addListener(LogListener listener) {
synchronized List<String> addListener(LogListener listener) {
listeners.add(listener);
return messages.toString();
if (logLoaded) return new ArrayList<>(messages);
else return Collections.emptyList();
}
void removeListener(LogListener listener) {
synchronized void removeListener(LogListener listener) {
listeners.remove(listener);
}
void log(final String message) {
final String formatted;
final List<LogListener> listenersSnapshot;
synchronized (this) {
formatted = dateFormat.format(new Date()) + " " + message;
messages.add(formatted);
if (logLoaded) listenersSnapshot = new ArrayList<>(listeners);
else listenersSnapshot = Collections.emptyList();
}
fileLogger.writeMessage(formatted);
Log.i(tag, message);
handler.post(new Runnable() {
@Override
public void run() {
for (LogListener listener : listenersSnapshot) listener.onMessageLogged(formatted);
}
});
}
void clear() {
synchronized (this) {
logCleared = true;
messages.clear();
}
fileLogger.clearLog();
logInitialMessages();
}
@Override
public void onLogLoaded(List<String> messagesLoaded) {
final List<String> messagesSnapshot;
final List<LogListener> listenersSnapshot;
synchronized (this) {
if (logLoaded) throw new IllegalStateException();
logLoaded = true;
if (!logCleared) {
messages.addAll(messagesLoaded);
Collections.sort(messages);
}
messagesSnapshot = new ArrayList<>(messages);
listenersSnapshot = new ArrayList<>(listeners);
}
handler.post(new Runnable() {
@Override
public void run() {
Log.i(tag, message);
String formatted = dateFormat.format(new Date()) + " " + message + "\n";
messages.append(formatted);
for (LogListener listener : listeners) listener.log(formatted);
for (String message : messagesSnapshot) {
for (LogListener listener : listenersSnapshot)
listener.onMessageLogged(message);
}
}
});
}
interface LogListener {
void log(String message);
@UiThread
void onMessageLogged(String message);
}
}
......@@ -49,7 +49,9 @@ public class SnoozeActivity extends AppCompatActivity implements LogListener,
alarmTypeSpinner.setOnItemSelectedListener(this);
outputScrollView = (ScrollView) findViewById(R.id.outputScrollView);
outputTextView = (TextView) findViewById(R.id.outputTextView);
outputTextView.setText(log.addListener(this));
StringBuilder sb = new StringBuilder();
for (String message : log.addListener(this)) sb.append(message).append('\n');
outputTextView.setText(sb.toString());
}
@Override
......@@ -60,8 +62,8 @@ public class SnoozeActivity extends AppCompatActivity implements LogListener,
}
@Override
public void log(String message) {
outputTextView.append(message);
public void onMessageLogged(String message) {
outputTextView.append(message + '\n');
outputScrollView.post(new Runnable() {
@Override
public void run() {
......@@ -83,13 +85,18 @@ public class SnoozeActivity extends AppCompatActivity implements LogListener,
startService(i);
}
public void onClickCopyToClipboardButton(View v) {
public void onClickCopyLogButton(View v) {
ClipboardManager cp = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
String text = outputTextView.getText().toString();
cp.setPrimaryClip(ClipData.newPlainText(getString(R.string.app_name), text));
Toast.makeText(this, R.string.copied_to_clipboard, LENGTH_SHORT).show();
}
public void onClickClearLogButton(View view) {
log.clear();
outputTextView.setText("");
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
alarmType = AlarmType.values()[position];
......
......@@ -11,18 +11,8 @@ public class SnoozeApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
log = new Logger(this, SnoozeApplication.class.getSimpleName());
log = new Logger(this, getString(R.string.app_name));
log.log("Application created");
log.log("Manufacturer: " + Build.MANUFACTURER);
log.log("Model: " + Build.MODEL);
log.log("Device: " + Build.DEVICE);
log.log("Product: " + Build.PRODUCT);
log.log("API version: " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 23) {
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
boolean ignoring = pm.isIgnoringBatteryOptimizations(getPackageName());
log.log("Ignoring battery optimizations: " + ignoring);
}
}
Logger getLogger() {
......
......@@ -4,8 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="12dp"
android:orientation="vertical"
tools:context="org.briarproject.snooze.SnoozeActivity">
<Switch
......@@ -36,8 +36,8 @@
android:id="@+id/applySettingsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/apply_settings"
android:onClick="onClickApplySettingsButton"/>
android:onClick="onClickApplySettingsButton"
android:text="@string/apply_settings"/>
<ScrollView
android:id="@+id/outputScrollView"
......@@ -53,11 +53,26 @@
</ScrollView>
<Button
android:id="@+id/copyToClipboardButton"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/copy_to_clipboard"
android:onClick="onClickCopyToClipboardButton"/>
android:orientation="horizontal">
<Button
android:id="@+id/clearLogButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onClickClearLogButton"
android:text="@string/clear_log"/>
<Button
android:id="@+id/copyLogButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onClickCopyLogButton"
android:text="@string/copy_log"/>
</LinearLayout>
</LinearLayout>
......@@ -6,7 +6,8 @@
<string name="use_foreground_service">Foreground service</string>
<string name="apply_settings">Apply settings</string>
<string name="use_wifi_lock">Wi-Fi lock</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="clear_log">Clear log</string>
<string name="copy_log">Copy log</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string-array name="alarm_types">
<item>No alarm</item>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment