Commit 228ddaa0 authored by Ricki Hirner's avatar Ricki Hirner

Re-initialize logger in :sync process, too (IPC using broadcast)

* re-initialize logger in :sync process after changing the settings (IPC using broadcast)
* move settings from SharedPreferences (which is not multi-process-safe) to ServiceDB
* logger: show exception details
* settings: show debug info
parent 24ce0c1a
......@@ -51,6 +51,16 @@
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
<receiver
android:name=".App$ReinitLoggingReceiver"
android:exported="false"
android:process=":sync">
<intent-filter>
<action android:name="at.bitfire.davdroid.REINIT_LOGGER"/>
</intent-filter>
</receiver>
<service
android:name=".syncadapter.AccountAuthenticatorService"
android:exported="false">
......@@ -104,6 +114,7 @@
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_tasks"/>
</service>
<service
android:name=".DavService"
android:enabled="true">
......
......@@ -8,20 +8,12 @@
package at.bitfire.davdroid;
import android.accounts.Account;
import android.app.ActivityManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.*;
import android.os.Process;
import android.support.v4.app.ActivityManagerCompat;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
......@@ -29,7 +21,6 @@ import org.apache.commons.lang3.time.DateFormatUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
......@@ -39,14 +30,15 @@ import javax.net.ssl.HostnameVerifier;
import at.bitfire.davdroid.log.LogcatHandler;
import at.bitfire.davdroid.log.PlainTextFormatter;
import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.model.Settings;
import de.duenndns.ssl.MemorizingTrustManager;
import lombok.Cleanup;
import lombok.Getter;
import okhttp3.internal.tls.OkHostnameVerifier;
public class App extends Application implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String
PREF_FILE = "davdroid_preferences", // preference file name
PREF_LOG_TO_FILE = "log_to_file"; // boolean: external logging enabled
public class App extends Application {
public static final String LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage";
@Getter
private static MemorizingTrustManager memorizingTrustManager;
......@@ -59,16 +51,10 @@ public class App extends Application implements SharedPreferences.OnSharedPrefer
public final static Logger log = Logger.getLogger("davdroid");
@Getter
private static SharedPreferences preferences;
@Override
public void onCreate() {
super.onCreate();
preferences = getSharedPreferences(PREF_FILE, MODE_PRIVATE);
preferences.registerOnSharedPreferenceChangeListener(this);
// initialize MemorizingTrustManager
memorizingTrustManager = new MemorizingTrustManager(this);
sslSocketFactoryCompat = new SSLSocketFactoryCompat(memorizingTrustManager);
......@@ -78,26 +64,14 @@ public class App extends Application implements SharedPreferences.OnSharedPrefer
reinitLogger();
}
// won't be called in production
@Override
public void onTerminate() {
super.onTerminate();
preferences.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PREF_LOG_TO_FILE.equals(key)) {
log.info("Logging preferences changed, initializing logger again");
reinitLogger();
}
}
private void reinitLogger() {
public void reinitLogger() {
// don't use Android default logging, we have our own handlers
log.setUseParentHandlers(false);
boolean logToFile = preferences.getBoolean(PREF_LOG_TO_FILE, false),
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
Settings settings = new Settings(dbHelper.getReadableDatabase());
boolean logToFile = settings.getBoolean(LOG_TO_EXTERNAL_STORAGE, false),
logVerbose = logToFile || Log.isLoggable(log.getName(), Log.DEBUG);
// set logging level according to preferences
......@@ -122,10 +96,11 @@ public class App extends Application implements SharedPreferences.OnSharedPrefer
File dir = getExternalFilesDir(null);
if (dir != null)
try {
String pattern = new File(dir, "davdroid%u-" + DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMdd-HHmmss") + ".txt").toString();
log.info("Logging to " + pattern);
String fileName = new File(dir, "davdroid-" + android.os.Process.myPid() + "-" +
DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMdd-HHmmss") + ".txt").toString();
log.info("Logging to " + fileName);
FileHandler fileHandler = new FileHandler(pattern);
FileHandler fileHandler = new FileHandler(fileName);
fileHandler.setFormatter(PlainTextFormatter.DEFAULT);
log.addHandler(fileHandler);
builder .setContentText(dir.getPath())
......@@ -150,4 +125,16 @@ public class App extends Application implements SharedPreferences.OnSharedPrefer
nm.cancel(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING);
}
public static class ReinitLoggingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
log.info("Received broadcast: re-initializing logger");
App app = (App)context.getApplicationContext();
app.reinitLogger();
}
}
}
......@@ -11,6 +11,7 @@ package at.bitfire.davdroid.log;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.util.logging.Formatter;
......@@ -28,6 +29,7 @@ public class PlainTextFormatter extends Formatter {
}
@Override
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public String format(LogRecord r) {
StringBuilder builder = new StringBuilder();
......@@ -39,14 +41,15 @@ public class PlainTextFormatter extends Formatter {
builder.append(String.format("[%s] %s", shortClassName(r.getSourceClassName()), r.getMessage()));
if (r.getThrown() != null) {
builder.append("\nEXCEPTION ");
builder.append(Log.getStackTraceString(r.getThrown()));
Throwable thrown = r.getThrown();
builder.append("\nEXCEPTION ").append(ExceptionUtils.getMessage(thrown));
builder.append("\tstrack trace = ").append(ExceptionUtils.getStackTrace(thrown));
}
if (r.getParameters() != null) {
int idx = 1;
for (Object param : r.getParameters())
builder.append("\nPARAMETER #").append(idx).append(" = ").append(param);
builder.append("\n\tPARAMETER #").append(idx).append(" = ").append(param);
}
if (!logcat)
......
......@@ -20,6 +20,13 @@ import lombok.Cleanup;
public class ServiceDB {
public static class Settings {
public static final String
_TABLE = "settings",
NAME = "setting",
VALUE = "value";
}
public static class Services {
public static final String
_TABLE = "services",
......@@ -80,6 +87,12 @@ public class ServiceDB {
public void onCreate(SQLiteDatabase db) {
App.log.info("Creating services database");
db.execSQL("CREATE TABLE " + Settings._TABLE + "(" +
Settings.NAME + " TEXT NOT NULL," +
Settings.VALUE + " TEXT NOT NULL" +
")");
db.execSQL("CREATE UNIQUE INDEX settings_name ON " + Settings._TABLE + " (" + Settings.NAME + ")");
db.execSQL("CREATE TABLE " + Services._TABLE + "(" +
Services.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Services.ACCOUNT_NAME + " TEXT NOT NULL," +
......
/*
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid.model;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import lombok.Cleanup;
public class Settings {
final SQLiteDatabase db;
public Settings(SQLiteDatabase db) {
this.db = db;
}
public boolean getBoolean(String name, boolean defaultValue) {
@Cleanup Cursor cursor = db.query(ServiceDB.Settings._TABLE, new String[] { ServiceDB.Settings.VALUE },
ServiceDB.Settings.NAME + "=?", new String[] { name }, null, null, null);
if (cursor.moveToNext())
return cursor.getInt(0) != 0;
else
return defaultValue;
}
public void putBoolean(String name, boolean value) {
ContentValues values = new ContentValues(2);
values.put(ServiceDB.Settings.NAME, name);
values.put(ServiceDB.Settings.VALUE, value ? 1 : 0);
db.insertWithOnConflict(ServiceDB.Settings._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
public void remove(String name) {
db.delete(ServiceDB.Settings._TABLE, ServiceDB.Settings.NAME + "=?", new String[] { name });
}
}
......@@ -8,12 +8,13 @@
package at.bitfire.davdroid.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.widget.Toast;
import android.support.v7.preference.SwitchPreferenceCompat;
import java.security.KeyStoreException;
import java.util.Enumeration;
......@@ -21,7 +22,10 @@ import java.util.logging.Level;
import at.bitfire.davdroid.App;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.model.Settings;
import de.duenndns.ssl.MemorizingTrustManager;
import lombok.Cleanup;
public class AppSettingsActivity extends AppCompatActivity {
......@@ -38,16 +42,21 @@ public class AppSettingsActivity extends AppCompatActivity {
public static class SettingsFragment extends PreferenceFragmentCompat {
Preference prefResetHints,
Preference prefResetHints,
prefResetCertificates;
SwitchPreferenceCompat prefLogToExternalStorage;
@Override
public void onCreatePreferences(Bundle bundle, String s) {
getPreferenceManager().setSharedPreferencesName(App.PREF_FILE);
addPreferencesFromResource(R.xml.settings_app);
prefResetHints = findPreference("reset_hints");
prefResetCertificates = findPreference("reset_certificates");
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
Settings settings = new Settings(dbHelper.getReadableDatabase());
prefLogToExternalStorage = (SwitchPreferenceCompat)findPreference("log_to_external_storage");
prefLogToExternalStorage.setChecked(settings.getBoolean(App.LOG_TO_EXTERNAL_STORAGE, false));
}
@Override
......@@ -56,16 +65,18 @@ public class AppSettingsActivity extends AppCompatActivity {
resetHints();
else if (preference == prefResetCertificates)
resetCertificates();
else if (preference == prefLogToExternalStorage)
setExternalLogging(((SwitchPreferenceCompat)preference).isChecked());
else
return false;
return true;
}
private void resetHints() {
App.getPreferences().edit()
.remove(StartupDialogFragment.PREF_HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED)
.remove(StartupDialogFragment.PREF_HINT_OPENTASKS_NOT_INSTALLED)
.commit();
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
Settings settings = new Settings(dbHelper.getWritableDatabase());
settings.remove(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED);
settings.remove(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED);
Snackbar.make(getView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show();
}
......@@ -83,6 +94,19 @@ public class AppSettingsActivity extends AppCompatActivity {
}
Snackbar.make(getView(), getResources().getQuantityString(R.plurals.app_settings_reset_trusted_certificates_success, deleted, deleted), Snackbar.LENGTH_LONG).show();
}
private void setExternalLogging(boolean externalLogging) {
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
Settings settings = new Settings(dbHelper.getWritableDatabase());
settings.putBoolean(App.LOG_TO_EXTERNAL_STORAGE, externalLogging);
// reinitialize logger of default process
App app = (App)getContext().getApplicationContext();
app.reinitLogger();
// reinitialize logger of :sync process
getContext().sendBroadcast(new Intent("at.bitfire.davdroid.REINIT_LOGGER"));
}
}
}
......@@ -30,12 +30,15 @@ import at.bitfire.davdroid.App;
import at.bitfire.davdroid.BuildConfig;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.model.Settings;
import at.bitfire.davdroid.resource.LocalTaskList;
import lombok.Cleanup;
public class StartupDialogFragment extends DialogFragment {
public static final String
PREF_HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED = "hint_google_play_accounts_removed",
PREF_HINT_OPENTASKS_NOT_INSTALLED = "hint_opentasks_not_installed";
HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED = "hint_GooglePlayAccountsRemoved",
HINT_OPENTASKS_NOT_INSTALLED = "hint_OpenTasksNotInstalled";
private static final String ARGS_MODE = "mode";
......@@ -49,6 +52,9 @@ public class StartupDialogFragment extends DialogFragment {
public static StartupDialogFragment[] getStartupDialogs(Context context) {
List<StartupDialogFragment> dialogs = new LinkedList<>();
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
Settings settings = new Settings(dbHelper.getReadableDatabase());
if (BuildConfig.VERSION_NAME.contains("-alpha") || BuildConfig.VERSION_NAME.contains("-beta"))
dialogs.add(StartupDialogFragment.instantiate(Mode.DEVELOPMENT_VERSION));
else {
......@@ -59,13 +65,13 @@ public class StartupDialogFragment extends DialogFragment {
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && // only on Android <5
"com.android.vending".equals(installedFrom) && // only when installed from Play Store
App.getPreferences().getBoolean(PREF_HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, true)) // and only when "Don't show again" hasn't been clicked yet
settings.getBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, true)) // and only when "Don't show again" hasn't been clicked yet
dialogs.add(StartupDialogFragment.instantiate(Mode.GOOGLE_PLAY_ACCOUNTS_REMOVED));
}
// OpenTasks information
if (!LocalTaskList.tasksProviderAvailable(context.getContentResolver()) &&
App.getPreferences().getBoolean(PREF_HINT_OPENTASKS_NOT_INSTALLED, true))
settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED, true))
dialogs.add(StartupDialogFragment.instantiate(Mode.OPENTASKS_NOT_INSTALLED));
Collections.reverse(dialogs);
......@@ -85,6 +91,8 @@ public class StartupDialogFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
setCancelable(false);
final ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
Mode mode = Mode.valueOf(getArguments().getString(ARGS_MODE));
switch (mode) {
case DEVELOPMENT_VERSION:
......@@ -149,7 +157,8 @@ public class StartupDialogFragment extends DialogFragment {
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
App.getPreferences().edit().putBoolean(PREF_HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, false).commit();
Settings settings = new Settings(dbHelper.getWritableDatabase());
settings.putBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, false);
}
})
.create();
......@@ -174,7 +183,8 @@ public class StartupDialogFragment extends DialogFragment {
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
App.getPreferences().edit().putBoolean(PREF_HINT_OPENTASKS_NOT_INSTALLED, false).commit();
Settings settings = new Settings(dbHelper.getWritableDatabase());
settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false);
}
})
.create();
......
......@@ -54,9 +54,9 @@
<item quantity="other">%d Zertifikaten wird nicht mehr vertraut</item>
</plurals>
<string name="app_settings_debug">Fehlersuche</string>
<string name="app_settings_log_to_file">Protokollierung in externe Datei</string>
<string name="app_settings_log_to_file_on">Protokolliert auf externen Speicher, falls möglich</string>
<string name="app_settings_log_to_file_off">Protokollierung auf externen Speicher deaktiviert</string>
<string name="app_settings_log_to_external_storage">Protokollierung in externe Datei</string>
<string name="app_settings_log_to_external_storage_on">Protokolliert auf externen Speicher, falls möglich</string>
<string name="app_settings_log_to_external_storage_off">Protokollierung auf externen Speicher deaktiviert</string>
<!--AccountActivity-->
<string name="account_synchronize_now">Jetzt synchronisieren</string>
<string name="account_synchronizing_now">Synchronisation gestartet</string>
......
......@@ -76,9 +76,11 @@
<item quantity="other">Distrusted %d certificates</item>
</plurals>
<string name="app_settings_debug">Debugging</string>
<string name="app_settings_log_to_file">Log to external file</string>
<string name="app_settings_log_to_file_on">Logging to external storage (if available)</string>
<string name="app_settings_log_to_file_off">External file logging is disabled</string>
<string name="app_settings_log_to_external_storage">Log to external file</string>
<string name="app_settings_log_to_external_storage_on">Logging to external storage (if available)</string>
<string name="app_settings_log_to_external_storage_off">External file logging is disabled</string>
<string name="app_settings_show_debug_info">Show debug info</string>
<string name="app_settings_show_debug_info_details">View/share software and configuration details</string>
<!-- AccountActivity -->
<string name="account_synchronize_now">Synchronize now</string>
......
......@@ -25,10 +25,17 @@
<PreferenceCategory android:title="@string/app_settings_debug">
<SwitchPreferenceCompat
android:key="log_to_file"
android:title="@string/app_settings_log_to_file"
android:summaryOn="@string/app_settings_log_to_file_on"
android:summaryOff="@string/app_settings_log_to_file_off"/>
android:key="log_to_external_storage"
android:title="@string/app_settings_log_to_external_storage"
android:summaryOn="@string/app_settings_log_to_external_storage_on"
android:summaryOff="@string/app_settings_log_to_external_storage_off"/>
<Preference
android:title="@string/app_settings_show_debug_info"
android:summary="@string/app_settings_show_debug_info_details">
<intent
android:targetPackage="at.bitfire.davdroid"
android:targetClass="at.bitfire.davdroid.ui.DebugInfoActivity"/>
</Preference>
</PreferenceCategory>
</PreferenceScreen>
\ No newline at end of file
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