From 183bb1b7a97dec1ac854afd9c2fbd70a5d23d119 Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 8 Jun 2019 09:34:29 +0200 Subject: [PATCH] Refactored export/import notification channels --- .../java/eu/faircode/email/ActivitySetup.java | 147 +++++++++++++++++- .../java/eu/faircode/email/AdapterFolder.java | 15 +- .../java/eu/faircode/email/ApplicationEx.java | 98 ------------ .../java/eu/faircode/email/EntityAccount.java | 32 ++-- .../eu/faircode/email/FragmentAccount.java | 14 +- .../eu/faircode/email/ServiceSynchronize.java | 10 +- .../java/eu/faircode/email/TupleFolderEx.java | 32 ++-- 7 files changed, 196 insertions(+), 152 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index 8ce4a9eded..fe97cd9dae 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -19,6 +19,10 @@ package eu.faircode.email; Copyright 2018-2019 by Marcel Bokhorst (M66B) */ +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -31,6 +35,8 @@ import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.media.Ringtone; +import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -42,6 +48,7 @@ import android.view.View; import android.widget.RadioGroup; import android.widget.TextView; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.documentfile.provider.DocumentFile; @@ -602,6 +609,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On Log.i("Collecting data"); DB db = DB.getInstance(context); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); // Accounts JSONArray jaccounts = new JSONArray(); @@ -609,6 +617,18 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On // Account JSONObject jaccount = account.toJSON(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (account.notify) { + NotificationChannel channel = nm.getNotificationChannel( + EntityAccount.getNotificationChannelId(account.id)); + if (channel != null && channel.getImportance() != NotificationManager.IMPORTANCE_NONE) { + JSONObject jchannel = channelToJSON(channel); + jaccount.put("channel", jchannel); + Log.i("Exported account channel=" + jchannel); + } + } + } + // Identities JSONArray jidentities = new JSONArray(); for (EntityIdentity identity : db.identity().getIdentities(account.id)) @@ -619,10 +639,22 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On JSONArray jfolders = new JSONArray(); for (EntityFolder folder : db.folder().getFolders(account.id)) { JSONObject jfolder = folder.toJSON(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = nm.getNotificationChannel( + EntityFolder.getNotificationChannelId(folder.id)); + if (channel != null && channel.getImportance() != NotificationManager.IMPORTANCE_NONE) { + JSONObject jchannel = channelToJSON(channel); + jfolder.put("channel", jchannel); + Log.i("Exported folder channel=" + jchannel); + } + } + JSONArray jrules = new JSONArray(); for (EntityRule rule : db.rule().getRules(folder.id)) jrules.put(rule.toJSON()); jfolder.put("rules", jrules); + jfolders.put(jfolder); } jaccount.put("folders", jfolders); @@ -656,8 +688,20 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On jexport.put("accounts", jaccounts); jexport.put("answers", janswers); jexport.put("settings", jsettings); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - jexport.put("channels", ApplicationEx.channelsToJSON(context)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + JSONArray jchannels = new JSONArray(); + for (NotificationChannel channel : nm.getNotificationChannels()) { + String id = channel.getId(); + if (id.startsWith("notification.") && id.contains("@") && + channel.getImportance() != NotificationManager.IMPORTANCE_NONE) { + JSONObject jchannel = channelToJSON(channel); + jchannels.put(jchannel); + Log.i("Exported contact channel=" + jchannel); + } + } + jexport.put("channels", jchannels); + } ContentResolver resolver = context.getContentResolver(); DocumentFile file = DocumentFile.fromSingleUri(context, uri); @@ -761,6 +805,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On JSONObject jimport = new JSONObject(data.toString()); DB db = DB.getInstance(context); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); try { db.beginTransaction(); @@ -795,9 +840,21 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On account.id = db.account().insertAccount(account); Log.i("Imported account=" + account.name); - account.deleteNotificationChannel(context); - if (account.notify) - account.createNotificationChannel(context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + account.deleteNotificationChannel(context); + if (account.notify) + if (jaccount.has("channel")) { + NotificationChannelGroup group = new NotificationChannelGroup(account.name, account.name); + nm.createNotificationChannelGroup(group); + + JSONObject jchannel = (JSONObject) jaccount.get("channel"); + jchannel.put("id", EntityAccount.getNotificationChannelId(account.id)); + nm.createNotificationChannel(channelFromJSON(context, jchannel)); + + Log.i("Imported account channel=" + jchannel); + } else + account.createNotificationChannel(context); + } Map xIdentity = new HashMap<>(); JSONArray jidentities = (JSONArray) jaccount.get("identities"); @@ -833,6 +890,19 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On if (Objects.equals(swipe_right, id)) account.swipe_right = folder.id; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (jfolder.has("channel")) { + NotificationChannelGroup group = new NotificationChannelGroup(account.name, account.name); + nm.createNotificationChannelGroup(group); + + JSONObject jchannel = (JSONObject) jfolder.get("channel"); + jchannel.put("id", EntityFolder.getNotificationChannelId(folder.id)); + nm.createNotificationChannel(channelFromJSON(context, jchannel)); + + Log.i("Imported folder channel=" + jchannel); + } + } + if (jfolder.has("rules")) { JSONArray jrules = jfolder.getJSONArray("rules"); for (int r = 0; r < jrules.length(); r++) { @@ -916,11 +986,17 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On editor.apply(); ApplicationEx.upgrade(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (jimport.has("channels")) { JSONArray jchannels = jimport.getJSONArray("channels"); - ApplicationEx.channelsFromJSON(context, jchannels); + for (int i = 0; i < jchannels.length(); i++) { + JSONObject jchannel = (JSONObject) jchannels.get(i); + nm.createNotificationChannel(channelFromJSON(context, jchannel)); + + Log.i("Imported contact channel=" + jchannel); + } } + } db.setTransactionSuccessful(); } finally { @@ -950,6 +1026,63 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On }.execute(this, args, "setup:import"); } + @RequiresApi(api = Build.VERSION_CODES.O) + private JSONObject channelToJSON(NotificationChannel channel) throws JSONException { + 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 + + return jchannel; + } + + @RequiresApi(api = Build.VERSION_CODES.O) + static NotificationChannel channelFromJSON(Context context, JSONObject jchannel) throws JSONException { + NotificationChannel channel = new NotificationChannel( + jchannel.getString("id"), + jchannel.getString("name"), + jchannel.getInt("importance")); + + channel.setGroup(jchannel.getString("group")); + + 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")); + Ringtone ringtone = RingtoneManager.getRingtone(context, uri); + if (ringtone != null) + channel.setSound(uri, Notification.AUDIO_ATTRIBUTES_DEFAULT); + } + + channel.enableLights(jchannel.getBoolean("light")); + channel.enableVibration(jchannel.getBoolean("vibrate")); + + return channel; + } + private void onEditAccount(Intent intent) { FragmentAccount fragment = new FragmentAccount(); fragment.setArguments(intent.getExtras()); diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index afa311e764..76005a2521 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -19,7 +19,6 @@ package eu.faircode.email; Copyright 2018-2019 by Marcel Bokhorst (M66B) */ -import android.annotation.TargetApi; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; @@ -43,6 +42,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.appcompat.widget.PopupMenu; import androidx.lifecycle.LifecycleOwner; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -410,15 +410,18 @@ public class AdapterFolder extends RecyclerView.Adapter= Build.VERSION_CODES.O) + onActionCreateChannel(); return true; case R.string.title_edit_channel: - onActionEditChannel(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + onActionEditChannel(); return true; case R.string.title_delete_channel: - onActionDeleteChannel(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + onActionDeleteChannel(); return true; default: @@ -622,6 +625,7 @@ public class AdapterFolder extends RecyclerView.Adapter DEFAULT_CHANNEL_NAMES = Collections.unmodifiableList(Arrays.asList( - "service", "notification", "warning", "error" - )); - @Override protected void attachBaseContext(Context base) { super.attachBaseContext(getLocalizedContext(base)); @@ -334,89 +319,6 @@ public class ApplicationEx extends Application { } } - @RequiresApi(api = Build.VERSION_CODES.O) - static JSONArray channelsToJSON(Context context) throws JSONException { - JSONArray jchannels = new JSONArray(); - - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - 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) - static void channelsFromJSON(Context context, JSONArray jchannels) throws JSONException { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - for (int c = 0; c < jchannels.length(); c++) { - JSONObject jchannel = (JSONObject) jchannels.get(c); - - // Legacy - if (!jchannel.has("group") || - jchannel.isNull("group") || - TextUtils.isEmpty(jchannel.getString("group"))) - continue; - - String id = jchannel.getString("id"); - if (nm.getNotificationChannel(id) == null) { - NotificationChannel channel = new NotificationChannel( - id, - jchannel.getString("name"), - jchannel.getInt("importance")); - - String groupName = jchannel.getString("group"); - NotificationChannelGroup group = new NotificationChannelGroup(groupName, groupName); - nm.createNotificationChannelGroup(group); - channel.setGroup(groupName); - - 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")); - Ringtone ringtone = RingtoneManager.getRingtone(context, uri); - 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); - } - } - } - public boolean ownFault(Throwable ex) { if (ex instanceof OutOfMemoryError) return false; diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index 3c2334e96f..41b15f0ae4 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -24,8 +24,10 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.Context; +import android.os.Build; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.room.Entity; import androidx.room.PrimaryKey; @@ -104,28 +106,26 @@ public class EntityAccount extends EntityOrder implements Serializable { return "notification" + (id == 0 ? "" : "." + id); } + @RequiresApi(api = Build.VERSION_CODES.O) void createNotificationChannel(Context context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannelGroup group = new NotificationChannelGroup(name, name); - nm.createNotificationChannelGroup(group); + NotificationChannelGroup group = new NotificationChannelGroup(name, name); + nm.createNotificationChannelGroup(group); - NotificationChannel channel = new NotificationChannel( - getNotificationChannelId(id), name, - NotificationManager.IMPORTANCE_HIGH); - channel.setGroup(name); - channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); - channel.enableLights(true); - nm.createNotificationChannel(channel); - } + NotificationChannel channel = new NotificationChannel( + getNotificationChannelId(id), name, + NotificationManager.IMPORTANCE_HIGH); + channel.setGroup(name); + channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + channel.enableLights(true); + nm.createNotificationChannel(channel); } + @RequiresApi(api = Build.VERSION_CODES.O) void deleteNotificationChannel(Context context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.deleteNotificationChannel(getNotificationChannelId(id)); - } + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.deleteNotificationChannel(getNotificationChannelId(id)); } @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index eac7d4c1cd..138945c8ac 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -943,12 +943,14 @@ public class FragmentAccount extends FragmentBase { EntityLog.log(context, (update ? "Updated" : "Added") + " account=" + account.name); // Make sure the channel exists on commit - if (account.notify) { - // Add or update notification channel - account.deleteNotificationChannel(context); - account.createNotificationChannel(context); - } else if (!account.synchronize) - account.deleteNotificationChannel(context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (account.notify) { + // Add or update notification channel + account.deleteNotificationChannel(context); + account.createNotificationChannel(context); + } else if (!account.synchronize) + account.deleteNotificationChannel(context); + } List folders = new ArrayList<>(); diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 8f63617cff..36affbf601 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -464,10 +464,12 @@ public class ServiceSynchronize extends LifecycleService { // Start monitoring accounts List accounts = db.account().getSynchronizingAccounts(); for (final EntityAccount account : accounts) { - if (account.notify) - account.createNotificationChannel(ServiceSynchronize.this); - else - account.deleteNotificationChannel(ServiceSynchronize.this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (account.notify) + account.createNotificationChannel(ServiceSynchronize.this); + else + account.deleteNotificationChannel(ServiceSynchronize.this); + } Log.i(account.host + "/" + account.user + " run"); final Core.State astate = new Core.State(state); diff --git a/app/src/main/java/eu/faircode/email/TupleFolderEx.java b/app/src/main/java/eu/faircode/email/TupleFolderEx.java index bed0973341..a71212c6cc 100644 --- a/app/src/main/java/eu/faircode/email/TupleFolderEx.java +++ b/app/src/main/java/eu/faircode/email/TupleFolderEx.java @@ -24,7 +24,9 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.Context; +import android.os.Build; +import androidx.annotation.RequiresApi; import androidx.room.Ignore; import java.io.Serializable; @@ -72,28 +74,26 @@ public class TupleFolderEx extends EntityFolder implements Serializable { return false; } + @RequiresApi(api = Build.VERSION_CODES.O) void createNotificationChannel(Context context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannelGroup group = new NotificationChannelGroup(accountName, accountName); - nm.createNotificationChannelGroup(group); + NotificationChannelGroup group = new NotificationChannelGroup(accountName, accountName); + nm.createNotificationChannelGroup(group); - NotificationChannel channel = new NotificationChannel( - getNotificationChannelId(id), getDisplayName(context), - NotificationManager.IMPORTANCE_HIGH); - channel.setGroup(accountName); - channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); - channel.enableLights(true); - nm.createNotificationChannel(channel); - } + NotificationChannel channel = new NotificationChannel( + getNotificationChannelId(id), getDisplayName(context), + NotificationManager.IMPORTANCE_HIGH); + channel.setGroup(accountName); + channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + channel.enableLights(true); + nm.createNotificationChannel(channel); } + @RequiresApi(api = Build.VERSION_CODES.O) void deleteNotificationChannel(Context context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.deleteNotificationChannel(getNotificationChannelId(id)); - } + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.deleteNotificationChannel(getNotificationChannelId(id)); } @Override