|
|
|
@ -19,7 +19,6 @@ package eu.faircode.email;
|
|
|
|
|
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import android.app.ActivityManager;
|
|
|
|
|
import android.app.Application;
|
|
|
|
|
import android.app.Notification;
|
|
|
|
|
import android.app.NotificationChannel;
|
|
|
|
@ -28,36 +27,11 @@ import android.app.NotificationManager;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
|
import android.content.res.Configuration;
|
|
|
|
|
import android.os.Build;
|
|
|
|
|
import android.os.DeadSystemException;
|
|
|
|
|
import android.os.RemoteException;
|
|
|
|
|
import android.view.OrientationEventListener;
|
|
|
|
|
import android.webkit.CookieManager;
|
|
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
|
|
|
|
|
|
import com.bugsnag.android.BeforeNotify;
|
|
|
|
|
import com.bugsnag.android.BeforeSend;
|
|
|
|
|
import com.bugsnag.android.Bugsnag;
|
|
|
|
|
import com.bugsnag.android.Client;
|
|
|
|
|
import com.bugsnag.android.Error;
|
|
|
|
|
import com.bugsnag.android.Report;
|
|
|
|
|
import com.sun.mail.iap.ProtocolException;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
|
import java.io.FileWriter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.UUID;
|
|
|
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
|
|
|
|
|
|
import javax.mail.MessagingException;
|
|
|
|
|
|
|
|
|
|
public class ApplicationEx extends Application {
|
|
|
|
|
private Thread.UncaughtExceptionHandler prev = null;
|
|
|
|
@ -71,7 +45,7 @@ public class ApplicationEx extends Application {
|
|
|
|
|
public void onCreate() {
|
|
|
|
|
super.onCreate();
|
|
|
|
|
|
|
|
|
|
logMemory("App create version=" + BuildConfig.VERSION_NAME);
|
|
|
|
|
Log.logMemory(this, "App create version=" + BuildConfig.VERSION_NAME);
|
|
|
|
|
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
|
final boolean crash_reports = prefs.getBoolean("crash_reports", false);
|
|
|
|
@ -81,12 +55,12 @@ public class ApplicationEx extends Application {
|
|
|
|
|
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
|
|
|
|
@Override
|
|
|
|
|
public void uncaughtException(Thread thread, Throwable ex) {
|
|
|
|
|
if (!crash_reports && ownFault(ex)) {
|
|
|
|
|
if (!crash_reports && Log.ownFault(ex)) {
|
|
|
|
|
Log.e(ex);
|
|
|
|
|
|
|
|
|
|
if (BuildConfig.BETA_RELEASE ||
|
|
|
|
|
!Helper.isPlayStoreInstall(ApplicationEx.this))
|
|
|
|
|
writeCrashLog(ApplicationEx.this, ex);
|
|
|
|
|
Log.writeCrash(ApplicationEx.this, ex);
|
|
|
|
|
|
|
|
|
|
if (prev != null)
|
|
|
|
|
prev.uncaughtException(thread, ex);
|
|
|
|
@ -97,7 +71,7 @@ public class ApplicationEx extends Application {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setupBugsnag();
|
|
|
|
|
Log.setupBugsnag(this);
|
|
|
|
|
|
|
|
|
|
upgrade(this);
|
|
|
|
|
|
|
|
|
@ -115,156 +89,16 @@ public class ApplicationEx extends Application {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onTrimMemory(int level) {
|
|
|
|
|
logMemory("Trim memory level=" + level);
|
|
|
|
|
Log.logMemory(this, "Trim memory level=" + level);
|
|
|
|
|
super.onTrimMemory(level);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onLowMemory() {
|
|
|
|
|
logMemory("Low memory");
|
|
|
|
|
Log.logMemory(this, "Low memory");
|
|
|
|
|
super.onLowMemory();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void logMemory(String message) {
|
|
|
|
|
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
|
|
|
|
|
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
|
|
|
|
|
activityManager.getMemoryInfo(mi);
|
|
|
|
|
int mb = Math.round(mi.availMem / 0x100000L);
|
|
|
|
|
int perc = Math.round(mi.availMem / (float) mi.totalMem * 100.0f);
|
|
|
|
|
Log.i(message + " " + mb + " MB" + " " + perc + " %");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void setupBugsnag() {
|
|
|
|
|
// https://docs.bugsnag.com/platforms/android/sdk/
|
|
|
|
|
com.bugsnag.android.Configuration config =
|
|
|
|
|
new com.bugsnag.android.Configuration("9d2d57476a0614974449a3ec33f2604a");
|
|
|
|
|
|
|
|
|
|
if (BuildConfig.DEBUG)
|
|
|
|
|
config.setReleaseStage("debug");
|
|
|
|
|
else {
|
|
|
|
|
String type = "other";
|
|
|
|
|
if (Helper.hasValidFingerprint(this))
|
|
|
|
|
if (BuildConfig.PLAY_STORE_RELEASE)
|
|
|
|
|
type = "play";
|
|
|
|
|
else
|
|
|
|
|
type = "full";
|
|
|
|
|
config.setReleaseStage(type + (BuildConfig.BETA_RELEASE ? "/beta" : ""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config.setAutoCaptureSessions(false);
|
|
|
|
|
|
|
|
|
|
config.setDetectAnrs(false);
|
|
|
|
|
config.setDetectNdkCrashes(false);
|
|
|
|
|
|
|
|
|
|
List<String> ignore = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
ignore.add("com.sun.mail.util.MailConnectException");
|
|
|
|
|
|
|
|
|
|
ignore.add("android.accounts.OperationCanceledException");
|
|
|
|
|
ignore.add("android.app.RemoteServiceException");
|
|
|
|
|
|
|
|
|
|
ignore.add("java.lang.NoClassDefFoundError");
|
|
|
|
|
ignore.add("java.lang.UnsatisfiedLinkError");
|
|
|
|
|
|
|
|
|
|
ignore.add("java.nio.charset.MalformedInputException");
|
|
|
|
|
|
|
|
|
|
ignore.add("java.net.ConnectException");
|
|
|
|
|
ignore.add("java.net.SocketException");
|
|
|
|
|
ignore.add("java.net.SocketTimeoutException");
|
|
|
|
|
ignore.add("java.net.UnknownHostException");
|
|
|
|
|
|
|
|
|
|
ignore.add("javax.mail.AuthenticationFailedException");
|
|
|
|
|
ignore.add("javax.mail.FolderClosedException");
|
|
|
|
|
ignore.add("javax.mail.internet.AddressException");
|
|
|
|
|
ignore.add("javax.mail.MessageRemovedException");
|
|
|
|
|
ignore.add("javax.mail.ReadOnlyFolderException");
|
|
|
|
|
ignore.add("javax.mail.StoreClosedException");
|
|
|
|
|
|
|
|
|
|
ignore.add("org.xmlpull.v1.XmlPullParserException");
|
|
|
|
|
|
|
|
|
|
config.setIgnoreClasses(ignore.toArray(new String[0]));
|
|
|
|
|
|
|
|
|
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
|
|
|
|
|
|
config.beforeSend(new BeforeSend() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean run(@NonNull Report report) {
|
|
|
|
|
Error error = report.getError();
|
|
|
|
|
if (error != null) {
|
|
|
|
|
Throwable ex = error.getException();
|
|
|
|
|
|
|
|
|
|
if (ex instanceof MessagingException &&
|
|
|
|
|
(ex.getCause() instanceof IOException ||
|
|
|
|
|
ex.getCause() instanceof ProtocolException))
|
|
|
|
|
// IOException includes SocketException, SocketTimeoutException
|
|
|
|
|
// ProtocolException includes ConnectionException
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (ex instanceof MessagingException &&
|
|
|
|
|
("connection failure".equals(ex.getMessage()) ||
|
|
|
|
|
"failed to create new store connection".equals(ex.getMessage()) ||
|
|
|
|
|
"Failed to fetch headers".equals(ex.getMessage()) ||
|
|
|
|
|
"Failed to load IMAP envelope".equals(ex.getMessage()) ||
|
|
|
|
|
"Unable to load BODYSTRUCTURE".equals(ex.getMessage())))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (ex instanceof IllegalStateException &&
|
|
|
|
|
("Not connected".equals(ex.getMessage()) ||
|
|
|
|
|
"This operation is not allowed on a closed folder".equals(ex.getMessage())))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (ex instanceof FileNotFoundException &&
|
|
|
|
|
ex.getMessage() != null &&
|
|
|
|
|
(ex.getMessage().startsWith("Download image failed") ||
|
|
|
|
|
ex.getMessage().startsWith("https://ipinfo.io/") ||
|
|
|
|
|
ex.getMessage().startsWith("https://autoconfig.thunderbird.net/")))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return prefs.getBoolean("crash_reports", false); // opt-in
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Bugsnag.init(this, config);
|
|
|
|
|
|
|
|
|
|
Client client = Bugsnag.getClient();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Log.i("Disabling orientation listener");
|
|
|
|
|
Field fOrientationListener = Client.class.getDeclaredField("orientationListener");
|
|
|
|
|
fOrientationListener.setAccessible(true);
|
|
|
|
|
OrientationEventListener orientationListener = (OrientationEventListener) fOrientationListener.get(client);
|
|
|
|
|
orientationListener.disable();
|
|
|
|
|
Log.i("Disabled orientation listener");
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.e(ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String uuid = prefs.getString("uuid", null);
|
|
|
|
|
if (uuid == null) {
|
|
|
|
|
uuid = UUID.randomUUID().toString();
|
|
|
|
|
prefs.edit().putString("uuid", uuid).apply();
|
|
|
|
|
}
|
|
|
|
|
Log.i("uuid=" + uuid);
|
|
|
|
|
client.setUserId(uuid);
|
|
|
|
|
|
|
|
|
|
if (prefs.getBoolean("crash_reports", false))
|
|
|
|
|
Bugsnag.startSession();
|
|
|
|
|
|
|
|
|
|
final String installer = getPackageManager().getInstallerPackageName(BuildConfig.APPLICATION_ID);
|
|
|
|
|
final boolean fingerprint = Helper.hasValidFingerprint(this);
|
|
|
|
|
|
|
|
|
|
Bugsnag.beforeNotify(new BeforeNotify() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean run(@NonNull Error error) {
|
|
|
|
|
error.addToTab("extra", "installer", installer == null ? "-" : installer);
|
|
|
|
|
error.addToTab("extra", "fingerprint", fingerprint);
|
|
|
|
|
error.addToTab("extra", "free", Log.getFreeMemMb());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void upgrade(Context context) {
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
|
int version = prefs.getInt("version", BuildConfig.VERSION_CODE);
|
|
|
|
@ -304,18 +138,6 @@ public class ApplicationEx extends Application {
|
|
|
|
|
editor.apply();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Context getLocalizedContext(Context context) {
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
|
boolean english = prefs.getBoolean("english", false);
|
|
|
|
|
|
|
|
|
|
if (english) {
|
|
|
|
|
Configuration config = new Configuration(context.getResources().getConfiguration());
|
|
|
|
|
config.setLocale(Locale.US);
|
|
|
|
|
return context.createConfigurationContext(config);
|
|
|
|
|
} else
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void createNotificationChannels() {
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
|
|
|
|
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
@ -378,61 +200,15 @@ public class ApplicationEx extends Application {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean ownFault(Throwable ex) {
|
|
|
|
|
if (ex instanceof OutOfMemoryError)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (ex instanceof RemoteException)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
java.lang.NoSuchMethodError: No direct method ()V in class Landroid/security/IKeyChainService$Stub; or its super classes (declaration of 'android.security.IKeyChainService$Stub' appears in /system/framework/framework.jar!classes2.dex)
|
|
|
|
|
java.lang.NoSuchMethodError: No direct method ()V in class Landroid/security/IKeyChainService$Stub; or its super classes (declaration of 'android.security.IKeyChainService$Stub' appears in /system/framework/framework.jar!classes2.dex)
|
|
|
|
|
at com.android.keychain.KeyChainService$1.(KeyChainService.java:95)
|
|
|
|
|
at com.android.keychain.KeyChainService.(KeyChainService.java:95)
|
|
|
|
|
at java.lang.Class.newInstance(Native Method)
|
|
|
|
|
at android.app.AppComponentFactory.instantiateService(AppComponentFactory.java:103)
|
|
|
|
|
*/
|
|
|
|
|
if (ex instanceof NoSuchMethodError)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (ex.getMessage() != null &&
|
|
|
|
|
(ex.getMessage().startsWith("Bad notification posted") ||
|
|
|
|
|
ex.getMessage().contains("ActivityRecord not found") ||
|
|
|
|
|
ex.getMessage().startsWith("Unable to create layer")))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (ex instanceof TimeoutException &&
|
|
|
|
|
ex.getMessage() != null &&
|
|
|
|
|
ex.getMessage().startsWith("com.sun.mail.imap.IMAPStore.finalize"))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
|
|
|
|
if (ex instanceof RuntimeException && ex.getCause() instanceof DeadSystemException)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (BuildConfig.BETA_RELEASE)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
while (ex != null) {
|
|
|
|
|
for (StackTraceElement ste : ex.getStackTrace())
|
|
|
|
|
if (ste.getClassName().startsWith(getPackageName()))
|
|
|
|
|
return true;
|
|
|
|
|
ex = ex.getCause();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void writeCrashLog(Context context, Throwable ex) {
|
|
|
|
|
File file = new File(context.getCacheDir(), "crash.log");
|
|
|
|
|
Log.w("Writing exception to " + file);
|
|
|
|
|
static Context getLocalizedContext(Context context) {
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
|
boolean english = prefs.getBoolean("english", false);
|
|
|
|
|
|
|
|
|
|
try (FileWriter out = new FileWriter(file, true)) {
|
|
|
|
|
out.write(BuildConfig.VERSION_NAME + " " + new Date() + "\r\n");
|
|
|
|
|
out.write(ex + "\r\n" + android.util.Log.getStackTraceString(ex) + "\r\n");
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Log.e(e);
|
|
|
|
|
}
|
|
|
|
|
if (english) {
|
|
|
|
|
Configuration config = new Configuration(context.getResources().getConfiguration());
|
|
|
|
|
config.setLocale(Locale.US);
|
|
|
|
|
return context.createConfigurationContext(config);
|
|
|
|
|
} else
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|