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
|
|
|
*/
|
|
|
|
|
|
|
|
import android.app.Application;
|
2018-08-15 07:40:18 +00:00
|
|
|
import android.app.Notification;
|
|
|
|
import android.app.NotificationChannel;
|
2019-03-17 18:06:51 +00:00
|
|
|
import android.app.NotificationChannelGroup;
|
2018-08-15 07:40:18 +00:00
|
|
|
import android.app.NotificationManager;
|
2018-12-09 17:49:52 +00:00
|
|
|
import android.content.Context;
|
2019-03-17 17:18:53 +00:00
|
|
|
import android.media.Ringtone;
|
|
|
|
import android.media.RingtoneManager;
|
|
|
|
import android.net.Uri;
|
2018-10-15 06:02:56 +00:00
|
|
|
import android.os.Build;
|
|
|
|
import android.os.DeadSystemException;
|
2018-09-14 05:41:02 +00:00
|
|
|
import android.os.RemoteException;
|
2019-03-13 13:42:33 +00:00
|
|
|
import android.webkit.CookieManager;
|
2018-08-03 18:07:12 +00:00
|
|
|
|
2019-03-17 17:18:53 +00:00
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
|
2018-08-11 04:41:26 +00:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileWriter;
|
|
|
|
import java.io.IOException;
|
2019-03-17 17:18:53 +00:00
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collections;
|
2018-12-19 11:51:27 +00:00
|
|
|
import java.util.Date;
|
2019-03-17 17:18:53 +00:00
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import androidx.annotation.RequiresApi;
|
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;
|
|
|
|
|
2019-03-17 17:18:53 +00:00
|
|
|
private static final List<String> DEFAULT_CHANNEL_NAMES = Collections.unmodifiableList(Arrays.asList(
|
|
|
|
"service", "notification", "warning", "error"
|
|
|
|
));
|
|
|
|
|
2018-08-03 18:07:12 +00:00
|
|
|
@Override
|
|
|
|
public void onCreate() {
|
|
|
|
super.onCreate();
|
|
|
|
|
|
|
|
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-18 16:06:54 +00:00
|
|
|
|
2018-12-28 09:56:40 +00:00
|
|
|
if (BuildConfig.BETA_RELEASE ||
|
|
|
|
!Helper.isPlayStoreInstall(ApplicationEx.this))
|
2018-12-18 16:06:54 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2018-08-15 07:40:18 +00:00
|
|
|
|
2018-09-14 08:31:49 +00:00
|
|
|
createNotificationChannels();
|
2019-03-19 19:34:02 +00:00
|
|
|
if (Helper.hasWebView(this))
|
|
|
|
CookieManager.getInstance().setAcceptCookie(false);
|
2019-02-06 13:51:41 +00:00
|
|
|
MessageHelper.setSystemProperties();
|
2019-03-02 15:03:09 +00:00
|
|
|
Core.init(this);
|
2018-09-14 08:31:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void createNotificationChannels() {
|
2018-08-15 07:40:18 +00:00
|
|
|
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);
|
2018-08-15 07:40:18 +00:00
|
|
|
|
|
|
|
NotificationChannel service = new NotificationChannel(
|
|
|
|
"service",
|
|
|
|
getString(R.string.channel_service),
|
|
|
|
NotificationManager.IMPORTANCE_MIN);
|
|
|
|
service.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
2018-10-06 20:58:32 +00:00
|
|
|
service.setShowBadge(false);
|
2018-11-06 10:18:46 +00:00
|
|
|
service.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
|
2018-08-15 07:40:18 +00:00
|
|
|
nm.createNotificationChannel(service);
|
|
|
|
|
|
|
|
NotificationChannel notification = new NotificationChannel(
|
|
|
|
"notification",
|
|
|
|
getString(R.string.channel_notification),
|
2018-11-05 07:48:02 +00:00
|
|
|
NotificationManager.IMPORTANCE_HIGH);
|
2018-11-06 10:18:46 +00:00
|
|
|
notification.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
2018-08-15 07:40:18 +00:00
|
|
|
nm.createNotificationChannel(notification);
|
|
|
|
|
2019-01-23 08:44:25 +00:00
|
|
|
NotificationChannel warning = new NotificationChannel(
|
|
|
|
"warning",
|
|
|
|
getString(R.string.channel_warning),
|
|
|
|
NotificationManager.IMPORTANCE_HIGH);
|
|
|
|
warning.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
|
|
|
nm.createNotificationChannel(warning);
|
|
|
|
|
2018-08-15 07:40:18 +00:00
|
|
|
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);
|
2018-08-15 07:40:18 +00:00
|
|
|
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-15 07:40:18 +00:00
|
|
|
}
|
2018-08-03 18:07:12 +00:00
|
|
|
}
|
2018-09-14 08:31:49 +00:00
|
|
|
|
2019-03-17 17:18:53 +00:00
|
|
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
2019-03-17 18:17:41 +00:00
|
|
|
static JSONArray channelsToJSON(Context context) throws JSONException {
|
2019-03-17 17:18:53 +00:00
|
|
|
JSONArray jchannels = new JSONArray();
|
|
|
|
|
2019-03-17 18:17:41 +00:00
|
|
|
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2019-03-17 17:18:53 +00:00
|
|
|
for (NotificationChannel channel : nm.getNotificationChannels())
|
|
|
|
if (!DEFAULT_CHANNEL_NAMES.contains(channel.getId())) {
|
|
|
|
JSONObject jchannel = new JSONObject();
|
|
|
|
|
|
|
|
jchannel.put("id", channel.getId());
|
|
|
|
jchannel.put("group", channel.getGroup());
|
|
|
|
jchannel.put("name", channel.getName());
|
|
|
|
jchannel.put("description", channel.getDescription());
|
|
|
|
|
|
|
|
jchannel.put("importance", channel.getImportance());
|
|
|
|
jchannel.put("dnd", channel.canBypassDnd());
|
|
|
|
jchannel.put("visibility", channel.getLockscreenVisibility());
|
|
|
|
jchannel.put("badge", channel.canShowBadge());
|
|
|
|
|
|
|
|
Uri sound = channel.getSound();
|
|
|
|
if (sound != null)
|
|
|
|
jchannel.put("sound", sound.toString());
|
|
|
|
// audio attributes
|
|
|
|
|
|
|
|
jchannel.put("light", channel.shouldShowLights());
|
|
|
|
// color
|
|
|
|
|
|
|
|
jchannel.put("vibrate", channel.shouldVibrate());
|
|
|
|
// pattern
|
|
|
|
|
|
|
|
jchannels.put(jchannel);
|
|
|
|
}
|
|
|
|
|
|
|
|
return jchannels;
|
|
|
|
}
|
|
|
|
|
|
|
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
2019-03-17 18:17:41 +00:00
|
|
|
static void channelsFromJSON(Context context, JSONArray jchannels) throws JSONException {
|
|
|
|
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2019-03-17 17:18:53 +00:00
|
|
|
for (int c = 0; c < jchannels.length(); c++) {
|
|
|
|
JSONObject jchannel = (JSONObject) jchannels.get(c);
|
|
|
|
|
|
|
|
String id = jchannel.getString("id");
|
|
|
|
if (nm.getNotificationChannel(id) == null) {
|
|
|
|
NotificationChannel channel = new NotificationChannel(
|
|
|
|
id,
|
|
|
|
jchannel.getString("name"),
|
|
|
|
jchannel.getInt("importance"));
|
|
|
|
|
|
|
|
if (jchannel.has("group") && !jchannel.isNull("group"))
|
|
|
|
channel.setGroup(jchannel.getString("group"));
|
2019-03-17 18:06:51 +00:00
|
|
|
else
|
|
|
|
channel.setGroup("contacts");
|
2019-03-17 17:18:53 +00:00
|
|
|
|
|
|
|
if (jchannel.has("description") && !jchannel.isNull("description"))
|
|
|
|
channel.setDescription(jchannel.getString("description"));
|
|
|
|
|
|
|
|
channel.setBypassDnd(jchannel.getBoolean("dnd"));
|
|
|
|
channel.setLockscreenVisibility(jchannel.getInt("visibility"));
|
|
|
|
channel.setShowBadge(jchannel.getBoolean("badge"));
|
|
|
|
|
|
|
|
if (jchannel.has("sound") && !jchannel.isNull("sound")) {
|
|
|
|
Uri uri = Uri.parse(jchannel.getString("sound"));
|
2019-03-17 18:17:41 +00:00
|
|
|
Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
|
2019-03-17 17:18:53 +00:00
|
|
|
if (ringtone != null)
|
|
|
|
channel.setSound(uri, Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
|
|
|
}
|
|
|
|
|
|
|
|
channel.enableLights(jchannel.getBoolean("light"));
|
|
|
|
channel.enableVibration(jchannel.getBoolean("vibrate"));
|
|
|
|
|
|
|
|
Log.i("Creating channel=" + channel);
|
|
|
|
nm.createNotificationChannel(channel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-14 08:31:49 +00:00
|
|
|
public boolean ownFault(Throwable ex) {
|
|
|
|
if (ex instanceof OutOfMemoryError)
|
|
|
|
return false;
|
2018-09-18 08:28:51 +00:00
|
|
|
|
2018-09-14 08:31:49 +00:00
|
|
|
if (ex instanceof RemoteException)
|
|
|
|
return false;
|
2018-09-18 08:28:51 +00:00
|
|
|
|
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)) {
|
2018-12-19 11:51:27 +00:00
|
|
|
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
|
|
|
}
|