FairEmail/app/src/main/java/eu/faircode/email/ApplicationEx.java

377 lines
15 KiB
Java
Raw Normal View History

2018-08-02 13:33:06 +00:00
package eu.faircode.email;
/*
2018-08-14 05:53:24 +00:00
This file is part of FairEmail.
2018-08-02 13:33:06 +00:00
2018-08-14 05:53:24 +00:00
FairEmail is free software: you can redistribute it and/or modify
2018-08-02 13:33:06 +00:00
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
2018-10-29 10:46:49 +00:00
FairEmail is distributed in the hope that it will be useful,
2018-08-02 13:33:06 +00:00
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
2018-10-29 10:46:49 +00:00
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
2018-08-02 13:33:06 +00:00
2018-12-31 08:04:33 +00:00
Copyright 2018-2019 by Marcel Bokhorst (M66B)
2018-08-02 13:33:06 +00:00
*/
2019-04-12 06:20:24 +00:00
import android.app.ActivityManager;
2018-08-02 13:33:06 +00:00
import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
2019-03-17 18:06:51 +00:00
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
2018-12-09 17:49:52 +00:00
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
2018-10-15 06:02:56 +00:00
import android.os.Build;
import android.os.DeadSystemException;
2019-05-06 17:19:37 +00:00
import android.os.Handler;
import android.os.RemoteException;
2019-05-27 09:11:41 +00:00
import android.view.OrientationEventListener;
2019-03-13 13:42:33 +00:00
import android.webkit.CookieManager;
2018-08-03 18:07:12 +00:00
2019-05-10 06:53:45 +00:00
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
2019-05-10 09:46:11 +00:00
import com.bugsnag.android.BeforeNotify;
2019-05-10 06:53:45 +00:00
import com.bugsnag.android.BeforeSend;
import com.bugsnag.android.Bugsnag;
2019-05-27 09:11:41 +00:00
import com.bugsnag.android.Client;
2019-05-10 09:46:11 +00:00
import com.bugsnag.android.Error;
2019-05-10 06:53:45 +00:00
import com.bugsnag.android.Report;
import com.sun.mail.iap.ProtocolException;
2019-05-10 06:53:45 +00:00
2018-08-11 04:41:26 +00:00
import java.io.File;
2019-05-10 15:00:50 +00:00
import java.io.FileNotFoundException;
2018-08-11 04:41:26 +00:00
import java.io.FileWriter;
import java.io.IOException;
2019-05-27 09:11:41 +00:00
import java.lang.reflect.Field;
2019-05-10 09:46:11 +00:00
import java.util.ArrayList;
import java.util.Date;
2019-03-17 17:18:53 +00:00
import java.util.List;
import java.util.Locale;
2019-05-23 09:08:04 +00:00
import java.util.UUID;
2019-05-11 07:21:48 +00:00
import java.util.concurrent.TimeoutException;
2019-03-17 17:18:53 +00:00
2019-05-10 13:44:36 +00:00
import javax.mail.MessagingException;
2018-08-02 13:33:06 +00:00
public class ApplicationEx extends Application {
2018-08-03 18:07:12 +00:00
private Thread.UncaughtExceptionHandler prev = null;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(getLocalizedContext(base));
}
2018-08-03 18:07:12 +00:00
@Override
public void onCreate() {
super.onCreate();
2019-05-10 06:53:45 +00:00
2019-04-12 06:20:24 +00:00
logMemory("App create version=" + BuildConfig.VERSION_NAME);
2018-08-03 18:07:12 +00:00
prev = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
2018-09-14 08:31:49 +00:00
if (ownFault(ex)) {
2018-12-24 12:27:45 +00:00
Log.e(ex);
2018-12-28 09:56:40 +00:00
if (BuildConfig.BETA_RELEASE ||
!Helper.isPlayStoreInstall(ApplicationEx.this))
writeCrashLog(ApplicationEx.this, ex);
2018-08-08 13:05:43 +00:00
2018-09-14 08:31:49 +00:00
if (prev != null)
prev.uncaughtException(thread, ex);
} else {
2018-12-24 12:27:45 +00:00
Log.w(ex);
2018-09-14 08:31:49 +00:00
System.exit(1);
2018-08-03 18:07:12 +00:00
}
}
});
2019-05-31 17:31:28 +00:00
setupBugsnag();
2019-05-10 13:18:53 +00:00
upgrade(this);
createNotificationChannels();
if (Helper.hasWebView(this))
CookieManager.getInstance().setAcceptCookie(false);
MessageHelper.setSystemProperties();
ContactInfo.init(this, new Handler());
2019-05-12 18:15:16 +00:00
WorkerWatchdog.init(this);
2019-05-10 13:18:53 +00:00
}
@Override
public void onTrimMemory(int level) {
logMemory("Trim memory level=" + level);
super.onTrimMemory(level);
}
@Override
public void onLowMemory() {
logMemory("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() {
2019-05-10 06:53:45 +00:00
// https://docs.bugsnag.com/platforms/android/sdk/
com.bugsnag.android.Configuration config =
new com.bugsnag.android.Configuration("9d2d57476a0614974449a3ec33f2604a");
2019-06-01 06:50:13 +00:00
if (BuildConfig.DEBUG || !Helper.hasValidFingerprint(this))
2019-05-10 06:53:45 +00:00
config.setReleaseStage("development");
else if (BuildConfig.BETA_RELEASE)
config.setReleaseStage(BuildConfig.PLAY_STORE_RELEASE ? "beta/play" : "beta");
else
config.setReleaseStage(BuildConfig.PLAY_STORE_RELEASE ? "stable/play" : "stable");
2019-05-10 09:03:01 +00:00
config.setAutoCaptureSessions(false);
2019-05-10 09:46:11 +00:00
2019-05-10 13:17:30 +00:00
config.setDetectAnrs(false);
2019-05-10 09:46:11 +00:00
List<String> ignore = new ArrayList<>();
2019-05-10 13:44:36 +00:00
ignore.add("com.sun.mail.util.MailConnectException");
ignore.add("javax.mail.AuthenticationFailedException");
2019-05-10 15:02:05 +00:00
ignore.add("java.net.UnknownHostException");
ignore.add("java.net.ConnectException");
2019-05-10 20:33:47 +00:00
ignore.add("java.net.SocketTimeoutException");
2019-05-11 05:55:53 +00:00
ignore.add("java.net.SocketException");
2019-06-01 11:17:32 +00:00
ignore.add("android.accounts.OperationCanceledException");
2019-05-10 13:44:36 +00:00
ignore.add("javax.mail.StoreClosedException");
ignore.add("javax.mail.FolderClosedException");
ignore.add("javax.mail.ReadOnlyFolderException");
2019-05-10 09:46:11 +00:00
ignore.add("javax.mail.MessageRemovedException");
2019-05-10 13:44:36 +00:00
ignore.add("javax.mail.internet.AddressException");
2019-06-01 11:17:32 +00:00
ignore.add("java.nio.charset.MalformedInputException");
2019-05-10 09:46:11 +00:00
config.setIgnoreClasses(ignore.toArray(new String[0]));
2019-05-10 06:53:45 +00:00
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
config.beforeSend(new BeforeSend() {
@Override
public boolean run(@NonNull Report report) {
2019-05-10 13:44:36 +00:00
Error error = report.getError();
if (error != null) {
Throwable ex = error.getException();
2019-05-10 20:33:47 +00:00
if (ex instanceof MessagingException &&
(ex.getCause() instanceof IOException ||
ex.getCause() instanceof ProtocolException))
2019-05-15 19:20:42 +00:00
// IOException includes SocketException, SocketTimeoutException
// ProtocolException includes ConnectionException
2019-05-10 13:44:36 +00:00
return false;
if (ex instanceof MessagingException &&
("connection failure".equals(ex.getMessage()) ||
"failed to create new store connection".equals(ex.getMessage())))
return false;
2019-05-10 15:00:50 +00:00
2019-05-10 20:33:47 +00:00
if (ex instanceof IllegalStateException &&
("Not connected".equals(ex.getMessage()) ||
"This operation is not allowed on a closed folder".equals(ex.getMessage())))
return false;
2019-05-10 15:00:50 +00:00
if (ex instanceof FileNotFoundException &&
ex.getMessage() != null &&
2019-05-11 13:28:55 +00:00
(ex.getMessage().startsWith("Download image failed") ||
2019-05-13 06:06:52 +00:00
ex.getMessage().startsWith("https://ipinfo.io/") ||
2019-05-11 13:28:55 +00:00
ex.getMessage().startsWith("https://autoconfig.thunderbird.net/")))
2019-05-10 15:00:50 +00:00
return false;
2019-05-10 13:44:36 +00:00
}
2019-05-10 09:03:01 +00:00
return prefs.getBoolean("crash_reports", false); // opt-in
2019-05-10 06:53:45 +00:00
}
});
Bugsnag.init(this, config);
2019-05-23 09:08:04 +00:00
2019-05-27 09:11:41 +00:00
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);
}
2019-05-23 09:08:04 +00:00
String uuid = prefs.getString("uuid", null);
if (uuid == null) {
uuid = UUID.randomUUID().toString();
prefs.edit().putString("uuid", uuid).apply();
}
Log.i("uuid=" + uuid);
2019-05-27 09:11:41 +00:00
client.setUserId(uuid);
2019-05-23 09:08:04 +00:00
2019-05-10 09:03:01 +00:00
if (prefs.getBoolean("crash_reports", false))
Bugsnag.startSession();
2019-05-10 06:53:45 +00:00
2019-05-10 09:46:11 +00:00
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);
2019-05-22 13:41:54 +00:00
error.addToTab("extra", "free", Log.getFreeMemMb());
2019-05-10 09:46:11 +00:00
return true;
}
});
2019-04-12 06:20:24 +00:00
}
static void upgrade(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2019-05-21 12:28:38 +00:00
int version = prefs.getInt("version", BuildConfig.VERSION_CODE);
if (version < 468) {
Log.i("Upgrading from " + version + " to " + BuildConfig.VERSION_CODE);
SharedPreferences.Editor editor = prefs.edit();
editor.remove("notify_trash");
editor.remove("notify_archive");
editor.remove("notify_reply");
editor.remove("notify_flag");
editor.remove("notify_seen");
editor.putInt("version", BuildConfig.VERSION_CODE);
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;
}
2018-09-14 08:31:49 +00:00
private void createNotificationChannels() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
2018-12-09 17:49:52 +00:00
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel service = new NotificationChannel(
2019-06-09 18:51:02 +00:00
"service", getString(R.string.channel_service),
NotificationManager.IMPORTANCE_MIN);
service.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
service.setShowBadge(false);
service.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
nm.createNotificationChannel(service);
2019-06-09 18:51:02 +00:00
NotificationChannel send = new NotificationChannel(
"send", getString(R.string.channel_send),
2019-06-10 06:22:09 +00:00
NotificationManager.IMPORTANCE_DEFAULT);
2019-06-09 18:51:02 +00:00
send.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
send.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(send);
NotificationChannel notification = new NotificationChannel(
2019-06-09 18:51:02 +00:00
"notification", getString(R.string.channel_notification),
NotificationManager.IMPORTANCE_HIGH);
notification.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notification.enableLights(true);
nm.createNotificationChannel(notification);
2019-01-23 08:44:25 +00:00
NotificationChannel warning = new NotificationChannel(
2019-06-09 18:51:02 +00:00
"warning", getString(R.string.channel_warning),
2019-01-23 08:44:25 +00:00
NotificationManager.IMPORTANCE_HIGH);
warning.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(warning);
NotificationChannel error = new NotificationChannel(
"error",
getString(R.string.channel_error),
NotificationManager.IMPORTANCE_HIGH);
2019-01-23 08:44:25 +00:00
error.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(error);
2019-03-17 18:06:51 +00:00
NotificationChannelGroup group = new NotificationChannelGroup(
"contacts",
getString(R.string.channel_group_contacts));
nm.createNotificationChannelGroup(group);
}
2018-08-03 18:07:12 +00:00
}
2018-09-14 08:31:49 +00:00
public boolean ownFault(Throwable ex) {
if (ex instanceof OutOfMemoryError)
return false;
2018-09-14 08:31:49 +00:00
if (ex instanceof RemoteException)
return false;
2019-05-29 06:50:48 +00:00
/*
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;
2019-05-11 07:21:48 +00:00
if (ex instanceof TimeoutException &&
ex.getMessage() != null &&
ex.getMessage().startsWith("com.sun.mail.imap.IMAPStore.finalize"))
return false;
2018-10-15 06:02:56 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
if (ex instanceof RuntimeException && ex.getCause() instanceof DeadSystemException)
return false;
2018-12-30 07:55:31 +00:00
if (BuildConfig.BETA_RELEASE)
return true;
2018-09-14 08:31:49 +00:00
while (ex != null) {
for (StackTraceElement ste : ex.getStackTrace())
if (ste.getClassName().startsWith(getPackageName()))
return true;
ex = ex.getCause();
}
2018-12-10 09:53:46 +00:00
2018-09-14 08:31:49 +00:00
return false;
}
2018-12-10 09:53:46 +00:00
static void writeCrashLog(Context context, Throwable ex) {
File file = new File(context.getCacheDir(), "crash.log");
2018-12-24 12:27:45 +00:00
Log.w("Writing exception to " + file);
2018-09-14 08:31:49 +00:00
2019-02-22 15:59:23 +00:00
try (FileWriter out = new FileWriter(file, true)) {
out.write(BuildConfig.VERSION_NAME + " " + new Date() + "\r\n");
2018-12-24 12:27:45 +00:00
out.write(ex + "\r\n" + android.util.Log.getStackTraceString(ex) + "\r\n");
2018-09-14 08:31:49 +00:00
} catch (IOException e) {
2018-12-24 12:27:45 +00:00
Log.e(e);
2018-09-14 08:31:49 +00:00
}
}
2018-08-02 13:33:06 +00:00
}