From 46b77bf34219aa47e916f0ab980d3bd5bb05ba34 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Wed, 12 Sep 2018 22:32:47 +0200 Subject: initial work toward api 26+ * introduce notification channels * always use foreground service on 26+ --- build.gradle | 5 +- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/AndroidManifest.xml | 4 +- .../pixart/messenger/services/BarcodeProvider.java | 1 - .../services/ContactChooserTargetService.java | 1 - .../pixart/messenger/services/EventReceiver.java | 19 +- .../messenger/services/ExportLogsService.java | 35 +-- .../messenger/services/NotificationService.java | 304 ++++++++++++++------- .../pixart/messenger/services/UpdateService.java | 15 +- .../messenger/services/XmppConnectionService.java | 40 +-- .../de/pixart/messenger/ui/SettingsActivity.java | 3 +- .../de/pixart/messenger/ui/SettingsFragment.java | 2 + .../java/de/pixart/messenger/ui/XmppActivity.java | 6 +- .../de/pixart/messenger/utils/Compatibility.java | 66 +++++ .../messenger/utils/ConversationsFileObserver.java | 11 +- src/main/res/values/strings.xml | 11 + src/main/res/xml/preferences.xml | 14 + 18 files changed, 362 insertions(+), 178 deletions(-) create mode 100644 src/main/java/de/pixart/messenger/utils/Compatibility.java diff --git a/build.gradle b/build.gradle index aa6c0a122..ec296b161 100644 --- a/build.gradle +++ b/build.gradle @@ -85,11 +85,11 @@ ext { android { - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { minSdkVersion 16 - targetSdkVersion 25 + targetSdkVersion 28 versionCode 239 versionName "2.1.1 beta (2018-08-28)" @@ -139,7 +139,6 @@ android { } buildTypes { debug { - minSdkVersion 14 debuggable true buildTypes.release.signingConfig = null } diff --git a/gradle.properties b/gradle.properties index fbdb9f362..b3c7a0330 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1 @@ org.gradle.jvmargs=-Xmx2048M -org.gradle.configureondemand=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 68382dc23..7a84270ae 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index c64f89ef8..e34cb1cb2 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ + @@ -235,8 +236,7 @@ android:label="@string/share_location" /> - + android:label="@string/show_location"> diff --git a/src/main/java/de/pixart/messenger/services/BarcodeProvider.java b/src/main/java/de/pixart/messenger/services/BarcodeProvider.java index 67dc74cc6..5ac2ee016 100644 --- a/src/main/java/de/pixart/messenger/services/BarcodeProvider.java +++ b/src/main/java/de/pixart/messenger/services/BarcodeProvider.java @@ -162,7 +162,6 @@ public class BarcodeProvider extends ContentProvider implements ServiceConnectio synchronized (this) { if (mXmppConnectionService == null && !mBindingInProcess) { Log.d(Config.LOGTAG, "calling to bind service"); - context.startService(intent); context.bindService(intent, this, Context.BIND_AUTO_CREATE); this.mBindingInProcess = true; } diff --git a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java index 5d6469bae..d20f9c1f3 100644 --- a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java +++ b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java @@ -32,7 +32,6 @@ public class ContactChooserTargetService extends ChooserTargetService implements public List onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { Intent intent = new Intent(this, XmppConnectionService.class); intent.setAction("contact_chooser"); - startService(intent); bindService(intent, this, Context.BIND_AUTO_CREATE); ArrayList chooserTargets = new ArrayList<>(); try { diff --git a/src/main/java/de/pixart/messenger/services/EventReceiver.java b/src/main/java/de/pixart/messenger/services/EventReceiver.java index 6200a40ce..529741ead 100644 --- a/src/main/java/de/pixart/messenger/services/EventReceiver.java +++ b/src/main/java/de/pixart/messenger/services/EventReceiver.java @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.preference.PreferenceManager; +import android.support.v4.content.ContextCompat; import android.util.Log; import de.pixart.messenger.Config; @@ -13,26 +14,26 @@ public class EventReceiver extends BroadcastReceiver { public static final String SETTING_ENABLED_ACCOUNTS = "enabled_accounts"; @Override - public void onReceive(Context context, Intent intent) { - Intent mIntentForService = new Intent(context, XmppConnectionService.class); - if (intent.getAction() != null) { - mIntentForService.setAction(intent.getAction()); + public void onReceive(final Context context, final Intent originalIntent) { + final Intent intentForService = new Intent(context, XmppConnectionService.class); + if (originalIntent.getAction() != null) { + intentForService.setAction(originalIntent.getAction()); } else { - mIntentForService.setAction("other"); + intentForService.setAction("other"); } - final String action = intent.getAction(); + final String action = originalIntent.getAction(); if (action.equals("ui") || hasEnabledAccounts(context)) { try { - context.startService(mIntentForService); + ContextCompat.startForegroundService(context, intentForService); } catch (RuntimeException e) { Log.d(Config.LOGTAG, "EventReceiver was unable to start service"); } } else { - Log.d(Config.LOGTAG, "EventReceiver ignored action " + mIntentForService.getAction()); + Log.d(Config.LOGTAG, "EventReceiver ignored action " + intentForService.getAction()); } } - public static boolean hasEnabledAccounts(Context context) { + public static boolean hasEnabledAccounts(final Context context) { return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTING_ENABLED_ACCOUNTS, true); } diff --git a/src/main/java/de/pixart/messenger/services/ExportLogsService.java b/src/main/java/de/pixart/messenger/services/ExportLogsService.java index b485fb91d..c2704490d 100644 --- a/src/main/java/de/pixart/messenger/services/ExportLogsService.java +++ b/src/main/java/de/pixart/messenger/services/ExportLogsService.java @@ -1,7 +1,5 @@ package de.pixart.messenger.services; -import android.app.NotificationManager; -import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -10,7 +8,6 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.preference.PreferenceManager; import android.support.annotation.BoolRes; -import android.support.v4.app.NotificationCompat; import android.util.Log; import java.io.BufferedWriter; @@ -41,19 +38,17 @@ import rocks.xmpp.addr.Jid; import static de.pixart.messenger.ui.SettingsActivity.USE_MULTI_ACCOUNTS; -public class ExportLogsService extends Service { +public class ExportLogsService extends XmppConnectionService { private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsDirectory("Chats", false) + "%s"; private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n"; - private static final int NOTIFICATION_ID = 1; private static AtomicBoolean running = new AtomicBoolean(false); + boolean ReadableLogsEnabled = false; private DatabaseBackend mDatabaseBackend; private List mAccounts; - boolean ReadableLogsEnabled = false; private WakeLock wakeLock; private PowerManager pm; - XmppConnectionService mXmppConnectionService; @Override public void onCreate() { @@ -63,20 +58,19 @@ public class ExportLogsService extends Service { ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", getResources().getBoolean(R.bool.plain_text_logs)); pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExportLogsService"); + this.startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, getNotificationService().exportLogsNotification()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (running.compareAndSet(false, true)) { - new Thread(new Runnable() { - @Override - public void run() { - export(); - stopForeground(true); - WakeLockHelper.release(wakeLock); - running.set(false); - stopSelf(); - } + new Thread(() -> { + startForcingForegroundNotification(); + export(); + stopForcingForegroundNotification(); + WakeLockHelper.release(wakeLock); + running.set(false); + stopSelf(); }).start(); } return START_NOT_STICKY; @@ -84,16 +78,9 @@ public class ExportLogsService extends Service { private void export() { wakeLock.acquire(); + getNotificationService().exportLogsServiceNotification(getNotificationService().exportLogsNotification()); List conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE); conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED)); - NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); - mBuilder.setContentTitle(getString(R.string.app_name)) - .setContentText(getString(R.string.notification_export_logs_title)) - .setSmallIcon(R.drawable.ic_import_export_white_24dp) - .setProgress(0, 0, true); - startForeground(NOTIFICATION_ID, mBuilder.build()); - mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); if (ReadableLogsEnabled) { for (Conversation conversation : conversations) { writeToFile(conversation); diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java index 7fa091478..4cdde8390 100644 --- a/src/main/java/de/pixart/messenger/services/NotificationService.java +++ b/src/main/java/de/pixart/messenger/services/NotificationService.java @@ -1,16 +1,23 @@ package de.pixart.messenger.services; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Typeface; +import android.media.AudioAttributes; +import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.SystemClock; import android.preference.PreferenceManager; +import android.support.annotation.RequiresApi; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.BigPictureStyle; import android.support.v4.app.NotificationCompat.Builder; @@ -46,9 +53,9 @@ import de.pixart.messenger.entities.Conversational; import de.pixart.messenger.entities.Message; import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.ui.ConversationsActivity; -import de.pixart.messenger.ui.EditAccountActivity; import de.pixart.messenger.ui.ManageAccountActivity; import de.pixart.messenger.ui.TimePreference; +import de.pixart.messenger.utils.Compatibility; import de.pixart.messenger.utils.GeoHelper; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.XmppConnection; @@ -56,39 +63,108 @@ import de.pixart.messenger.xmpp.XmppConnection; public class NotificationService { public static final Object CATCHUP_LOCK = new Object(); + private static final String CONVERSATIONS_GROUP = "de.pixart.messenger"; - private final XmppConnectionService mXmppConnectionService; - private final LinkedHashMap> notifications = new LinkedHashMap<>(); private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024; public static final int NOTIFICATION_ID = 2 * NOTIFICATION_ID_MULTIPLIER; public static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4; public static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6; - public static final int UPDATE_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8; + public static final String MESSAGES_CHANNEL_ID = "messages"; + public static final String FOREGROUND_CHANNEL_ID = "foreground"; + public static final String ERROR_CHANNEL_ID = "error"; + + private final XmppConnectionService mXmppConnectionService; + private final LinkedHashMap> notifications = new LinkedHashMap<>(); + private final HashMap mBacklogMessageCounter = new HashMap<>(); private Conversation mOpenConversation; private boolean mIsInForeground; private long mLastNotification; - private final HashMap mBacklogMessageCounter = new HashMap<>(); - - public NotificationService(final XmppConnectionService service) { + NotificationService(final XmppConnectionService service) { this.mXmppConnectionService = service; } + private static boolean displaySnoozeAction(List messages) { + int numberOfMessagesWithoutReply = 0; + for (Message message : messages) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + ++numberOfMessagesWithoutReply; + } else { + return false; + } + } + return numberOfMessagesWithoutReply >= 3; + } + public static Pattern generateNickHighlightPattern(final String nick) { return Pattern.compile("(?<=(^|\\s))" + Pattern.quote(nick) + "\\b"); } + @RequiresApi(api = Build.VERSION_CODES.O) + public void initializeChannels() { + final Context c = mXmppConnectionService; + NotificationManager notificationManager = c.getSystemService(NotificationManager.class); + if (notificationManager == null) { + return; + } + notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("status", c.getString(R.string.notification_group_status_information))); + notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("chats", c.getString(R.string.notification_group_messages))); + + final NotificationChannel foregroundServiceChannel = new NotificationChannel(FOREGROUND_CHANNEL_ID, + c.getString(R.string.foreground_service_channel_name), + NotificationManager.IMPORTANCE_MIN); + foregroundServiceChannel.setDescription(c.getString(R.string.foreground_service_channel_description)); + foregroundServiceChannel.setShowBadge(false); + foregroundServiceChannel.setGroup("status"); + notificationManager.createNotificationChannel(foregroundServiceChannel); + + final NotificationChannel errorChannel = new NotificationChannel(ERROR_CHANNEL_ID, + c.getString(R.string.error_channel_name), + NotificationManager.IMPORTANCE_LOW); + errorChannel.setDescription(c.getString(R.string.error_channel_description)); + errorChannel.setShowBadge(false); + errorChannel.setGroup("status"); + notificationManager.createNotificationChannel(errorChannel); + + final NotificationChannel messagesChannel = new NotificationChannel(MESSAGES_CHANNEL_ID, + c.getString(R.string.messages_channel_name), + NotificationManager.IMPORTANCE_HIGH); + messagesChannel.setShowBadge(true); + messagesChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build()); + messagesChannel.setLightColor(0xff00ff00); + final int dat = 70; + final long[] pattern = {0, 3 * dat, dat, dat}; + messagesChannel.setVibrationPattern(pattern); + messagesChannel.enableVibration(true); + messagesChannel.enableLights(true); + messagesChannel.setGroup("chats"); + notificationManager.createNotificationChannel(messagesChannel); + + final NotificationChannel silentMessagesChannel = new NotificationChannel("silent_messages", + c.getString(R.string.silent_messages_channel_name), + NotificationManager.IMPORTANCE_LOW); + silentMessagesChannel.setDescription(c.getString(R.string.silent_messages_channel_description)); + silentMessagesChannel.setShowBadge(true); + silentMessagesChannel.setLightColor(0xff00ff00); + silentMessagesChannel.enableLights(true); + silentMessagesChannel.setGroup("chats"); + notificationManager.createNotificationChannel(silentMessagesChannel); + } + public boolean notify(final Message message) { final Conversation conversation = (Conversation) message.getConversation(); return message.getStatus() == Message.STATUS_RECEIVED && notificationsEnabled() && !conversation.isMuted() && (conversation.alwaysNotify() || wasHighlightedOrPrivate(message)) - && (!conversation.isWithStranger() || notificationsFromStrangers() - ); + && (!conversation.isWithStranger() || notificationsFromStrangers()) + ; } - public boolean notificationsEnabled() { + private boolean notificationsEnabled() { return mXmppConnectionService.getBooleanPreference("show_notification", R.bool.show_notification); } @@ -96,7 +172,7 @@ public class NotificationService { return mXmppConnectionService.getBooleanPreference("notifications_from_strangers", R.bool.notifications_from_strangers); } - public boolean isQuietHours() { + private boolean isQuietHours() { if (!mXmppConnectionService.getBooleanPreference("enable_quiet_hours", R.bool.enable_quiet_hours)) { return false; } @@ -104,7 +180,6 @@ public class NotificationService { final long startTime = preferences.getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY; final long endTime = preferences.getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY; final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY; - if (endTime < startTime) { return nowTime > startTime || nowTime < endTime; } else { @@ -159,7 +234,7 @@ public class NotificationService { } } } - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": backlog message count="+count); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": backlog message count=" + count); return count; } @@ -260,9 +335,8 @@ public class NotificationService { updateNotification(notify, false); } - public void updateNotification(final boolean notify, boolean summaryOnly) { + private void updateNotification(final boolean notify, boolean summaryOnly) { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService); - if (notifications.size() == 0) { cancel(NOTIFICATION_ID); } else { @@ -271,15 +345,16 @@ public class NotificationService { } final Builder mBuilder; if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - mBuilder = buildSingleConversations(notifications.values().iterator().next()); + mBuilder = buildSingleConversations(notifications.values().iterator().next(), notify); modifyForSoundVibrationAndLight(mBuilder, notify, preferences); notify(NOTIFICATION_ID, mBuilder.build()); } else { - mBuilder = buildMultipleConversation(); + mBuilder = buildMultipleConversation(notify); + mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN); modifyForSoundVibrationAndLight(mBuilder, notify, preferences); if (!summaryOnly) { for (Map.Entry> entry : notifications.entrySet()) { - Builder singleBuilder = buildSingleConversations(entry.getValue()); + Builder singleBuilder = buildSingleConversations(entry.getValue(), notify); singleBuilder.setGroup(CONVERSATIONS_GROUP); setNotificationColor(singleBuilder); notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build()); @@ -330,9 +405,8 @@ public class NotificationService { } } - private Builder buildMultipleConversation() { - final Builder mBuilder = new NotificationCompat.Builder( - mXmppConnectionService); + private Builder buildMultipleConversation(final boolean notify) { + final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, notify ? "messages" : "silent_messages"); final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); style.setBigContentTitle(notifications.size() + " " @@ -378,8 +452,8 @@ public class NotificationService { return mBuilder; } - private Builder buildSingleConversations(final ArrayList messages) { - final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); + private Builder buildSingleConversations(final ArrayList messages, final boolean notify) { + final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, notify ? "messages" : "silent_messages"); if (messages.size() >= 1) { final Conversation conversation = (Conversation) messages.get(0).getConversation(); final UnreadConversation.Builder mUnreadBuilder = new UnreadConversation.Builder(conversation.getName().toString()); @@ -407,8 +481,7 @@ public class NotificationService { R.drawable.ic_reply_white_24dp, replyLabel, createReplyIntent(conversation, false)).addRemoteInput(remoteInput).build(); - NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder( - R.drawable.ic_wear_reply, + NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply, replyLabel, createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build(); mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction)); @@ -473,18 +546,6 @@ public class NotificationService { return mBuilder; } - private static boolean displaySnoozeAction(List messages) { - int numberOfMessagesWithoutReply = 0; - for (Message message : messages) { - if (message.getStatus() == Message.STATUS_RECEIVED) { - ++numberOfMessagesWithoutReply; - } else { - return false; - } - } - return numberOfMessagesWithoutReply >= 3; - } - private void modifyForImage(final Builder builder, final UnreadConversation.Builder uBuilder, final Message message, final ArrayList messages) { try { @@ -545,7 +606,7 @@ public class NotificationService { styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0); builder.setContentText(styledString); } else { - builder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count)); + builder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count)); } } } @@ -596,7 +657,7 @@ public class NotificationService { private CharSequence getMergedBodies(final ArrayList messages) { final StringBuilder text = new StringBuilder(); - for(Message message : messages) { + for (Message message : messages) { if (text.length() != 0) { text.append("\n"); } @@ -606,7 +667,7 @@ public class NotificationService { } private PendingIntent createShowLocationIntent(final Message message) { - Iterable intents = GeoHelper.createGeoIntentsFromMessage(message, this.mXmppConnectionService.getApplicationContext()); + Iterable intents = GeoHelper.createGeoIntentsFromMessage(message, mXmppConnectionService); for (Intent intent : intents) { if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) { return PendingIntent.getActivity(mXmppConnectionService, generateRequestCode(message.getConversation(), 18), intent, PendingIntent.FLAG_UPDATE_CURRENT); @@ -676,7 +737,7 @@ public class NotificationService { return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 16), intent, PendingIntent.FLAG_UPDATE_CURRENT); } - public PendingIntent createSnoozeIntent(Conversation conversation) { + private PendingIntent createSnoozeIntent(Conversation conversation) { final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_SNOOZE); intent.putExtra("uuid", conversation.getUuid()); @@ -736,52 +797,61 @@ public class NotificationService { } public Notification createForegroundNotification() { - final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); - List accounts = mXmppConnectionService.getAccounts(); - String status; - Account mAccount = null; - Log.d(Config.LOGTAG, "Accounts size " + accounts.size()); - if (accounts.size() == 1) { - mAccount = accounts.get(0); - if (mAccount.getStatus() == Account.State.ONLINE) { - status = "(" + mXmppConnectionService.getString(R.string.account_status_online) + ")"; - } else if (mAccount.getStatus() == Account.State.CONNECTING) { - status = "(" + mXmppConnectionService.getString(R.string.account_status_connecting) + ")"; - } else { - status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")"; - } - } else if (accounts.size() > 1) { - status = ""; // todo: status for multiple accounts??? - } else { - status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")"; - } - status = " " + status; - Log.d(Config.LOGTAG, "Status: " + status); - mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status); - if (Config.SHOW_CONNECTED_ACCOUNTS) { + final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service)); + if (Compatibility.twentySix() || Config.SHOW_CONNECTED_ACCOUNTS) { + List accounts = mXmppConnectionService.getAccounts(); int enabled = 0; int connected = 0; - for (Account account : accounts) { - if (account.isOnlineAndConnected()) { - connected++; - enabled++; - } else if (account.isEnabled()) { - enabled++; + String status; + Account mAccount = null; + if (accounts != null) { + for (Account account : accounts) { + if (account.isOnlineAndConnected()) { + connected++; + enabled++; + } else if (account.isEnabled()) { + enabled++; + } + } + if (accounts.size() == 1) { + mAccount = accounts.get(0); + if (mAccount.getStatus() == Account.State.ONLINE) { + status = "(" + mXmppConnectionService.getString(R.string.account_status_online) + ")"; + status = " " + status; + Log.d(Config.LOGTAG, "Status: " + status); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status); + } else if (mAccount.getStatus() == Account.State.CONNECTING) { + status = "(" + mXmppConnectionService.getString(R.string.account_status_connecting) + ")"; + status = " " + status; + Log.d(Config.LOGTAG, "Status: " + status); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status); + } else { + status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")"; + status = " " + status; + Log.d(Config.LOGTAG, "Status: " + status); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status); + } + } else if (accounts.size() > 1) { + mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled)); + } else { + status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")"; + status = " " + status; + Log.d(Config.LOGTAG, "Status: " + status); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status); } + } else { + mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); } - mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled)); } else { mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); } mBuilder.setContentIntent(createOpenConversationsIntent()); mBuilder.setWhen(0); - mBuilder.setPriority(Config.SHOW_CONNECTED_ACCOUNTS ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_MIN); - if (accounts.size() == 1 && accounts.get(0).getStatus() == Account.State.ONLINE) { - mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); - } else if (accounts.size() > 1) { - mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); // todo: status for multiple accounts??? - } else { - mBuilder.setSmallIcon(R.drawable.ic_unlink_white_24dp); + mBuilder.setPriority(Notification.PRIORITY_LOW); + mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); + if (Compatibility.twentySix()) { + mBuilder.setChannelId(FOREGROUND_CHANNEL_ID); } return mBuilder.build(); } @@ -801,10 +871,10 @@ public class NotificationService { errors.add(account); } } - if (mXmppConnectionService.showForegroundService()) { + if (Compatibility.keepForegroundService(mXmppConnectionService)) { notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } - final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); + final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService); if (errors.size() == 0) { cancel(ERROR_NOTIFICATION_ID); return; @@ -819,38 +889,70 @@ public class NotificationService { mXmppConnectionService.getString(R.string.try_again), createTryAgainIntent()); mBuilder.setDeleteIntent(createDismissErrorIntent()); - mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE); - mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); - mBuilder.setLocalOnly(true); - mBuilder.setPriority(NotificationCompat.PRIORITY_LOW); - if (errors.size() == 1) { - Intent intent = new Intent(mXmppConnectionService, EditAccountActivity.class); - Account mAccount = mXmppConnectionService.getAccounts().get(0); - intent.putExtra("jid", mAccount.getJid().asBareJid().toString()); - intent.putExtra("init", false); - mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, - 145, - intent, - PendingIntent.FLAG_UPDATE_CURRENT)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE); + mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); } else { - mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, - 145, - new Intent(mXmppConnectionService, ManageAccountActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT)); + mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + mBuilder.setLocalOnly(true); + } + mBuilder.setPriority(Notification.PRIORITY_LOW); + mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, + 145, + new Intent(mXmppConnectionService, ManageAccountActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT)); + if (Compatibility.twentySix()) { + mBuilder.setChannelId(ERROR_CHANNEL_ID); } notify(ERROR_NOTIFICATION_ID, mBuilder.build()); } - public Notification updateFileAddingNotification(int current, Message message) { - final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService); - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); + public void updateFileAddingNotification(int current, Message message) { + Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService); mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video)); mBuilder.setProgress(100, current, false); mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp); mBuilder.setContentIntent(createContentIntent(message.getConversation())); + if (Compatibility.twentySix()) { + mBuilder.setChannelId(FOREGROUND_CHANNEL_ID); + } Notification notification = mBuilder.build(); notify(FOREGROUND_NOTIFICATION_ID, notification); - return notification; + } + + public Notification exportLogsNotification() { + Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name)); + mBuilder.setContentText(mXmppConnectionService.getString(R.string.notification_export_logs_title)); + mBuilder.setProgress(0, 0, true); + mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp); + if (Compatibility.twentySix()) { + mBuilder.setChannelId(FOREGROUND_CHANNEL_ID); + } + return mBuilder.build(); + } + + public void exportLogsServiceNotification(Notification notification) { + notify(FOREGROUND_NOTIFICATION_ID, notification); + } + + public Notification AppUpdateNotification(PendingIntent intent, String version, String filesize) { + Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name)); + mBuilder.setContentText(mXmppConnectionService.getString(R.string.notification_export_logs_title)); + mBuilder.setContentText(String.format(mXmppConnectionService.getString(R.string.update_available), version, filesize)); + mBuilder.setSmallIcon(R.drawable.ic_update_notification); + mBuilder.setContentIntent(intent); + if (Compatibility.twentySix()) { + mBuilder.setChannelId(FOREGROUND_CHANNEL_ID); + } + return mBuilder.build(); + } + + public void AppUpdateServiceNotification(Notification notification) { + notify(FOREGROUND_NOTIFICATION_ID, notification); } private void notify(String tag, int id, Notification notification) { diff --git a/src/main/java/de/pixart/messenger/services/UpdateService.java b/src/main/java/de/pixart/messenger/services/UpdateService.java index 3b3829290..bf5242939 100644 --- a/src/main/java/de/pixart/messenger/services/UpdateService.java +++ b/src/main/java/de/pixart/messenger/services/UpdateService.java @@ -1,14 +1,11 @@ package de.pixart.messenger.services; -import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; import android.util.Log; import android.widget.Toast; @@ -27,12 +24,12 @@ import de.pixart.messenger.R; import de.pixart.messenger.ui.UpdaterActivity; import static de.pixart.messenger.http.HttpConnectionManager.getProxy; -import static de.pixart.messenger.services.NotificationService.UPDATE_NOTIFICATION_ID; public class UpdateService extends AsyncTask { private boolean mUseTor; private Context context; private String store; + private NotificationService getNotificationService; public UpdateService() { } @@ -40,6 +37,7 @@ public class UpdateService extends AsyncTask UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList( + SettingsActivity.SHOW_FOREGROUND_SERVICE, + "led", + "notification_ringtone", + "notification_headsup", + "vibrate_on_notification"); + private static final List UNUESD_SETTINGS_PRE_TWENTYSIX = Collections.singletonList("more_notification_settings"); + + public static boolean twentySix() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + + private static boolean getBooleanPreference(Context context, String name, @BoolRes int res) { + return getPreferences(context).getBoolean(name, context.getResources().getBoolean(res)); + } + + private static SharedPreferences getPreferences(final Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + + public static boolean keepForegroundService(Context context) { + return twentySix() || getBooleanPreference(context, SettingsActivity.SHOW_FOREGROUND_SERVICE, R.bool.show_foreground_service); + } + + public static void removeUnusedPreferences(SettingsFragment settingsFragment) { + List screens = Arrays.asList( + (PreferenceScreen) settingsFragment.findPreference("notifications")); + List categories = Arrays.asList( + (PreferenceCategory) settingsFragment.findPreference("general")); + for (String key : (twentySix() ? UNUSED_SETTINGS_POST_TWENTYSIX : UNUESD_SETTINGS_PRE_TWENTYSIX)) { + Preference preference = settingsFragment.findPreference(key); + if (preference != null) { + for (PreferenceScreen screen : screens) { + if (screen != null) { + screen.removePreference(preference); + } + } + for (PreferenceCategory category : categories) { + if (category != null) { + category.removePreference(preference); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java index 4e275df3f..73d5589e4 100644 --- a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java +++ b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java @@ -2,12 +2,15 @@ package de.pixart.messenger.utils; import android.os.FileObserver; +import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Stack; +import de.pixart.messenger.Config; + /** * Copyright (C) 2012 Bartek Przybylski * Copyright (C) 2015 ownCloud Inc. @@ -19,7 +22,7 @@ public abstract class ConversationsFileObserver { private final String path; private final List mObservers = new ArrayList<>(); - public ConversationsFileObserver(String path) { + protected ConversationsFileObserver(String path) { this.path = path; } @@ -88,13 +91,17 @@ public abstract class ConversationsFileObserver { private class SingleFileObserver extends FileObserver { private final String path; - public SingleFileObserver(String path, int mask) { + SingleFileObserver(String path, int mask) { super(path, mask); this.path = path; } @Override public void onEvent(int event, String filename) { + if (filename == null) { + Log.d(Config.LOGTAG, "ignored file event with NULL filename (event=" + event + ")"); + return; + } ConversationsFileObserver.this.onEvent(event, path + '/' + filename); } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 1481bc6cc..3c1e766da 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -814,4 +814,15 @@ Attachment choice Address book Unable to save recording + Foreground service + This notification category is used to display a permanent notification indicating that Conversations is running. + Status Information + Connectivity Problems + This notification category is used to display a notification in case there is a problem connecting to an account. + Messages + Messages + Silent messages + This notification group is used to display notifications that should not trigger any sound. For example when being active on another device (Grace Period). + Notification Settings + Importance, Sound, Vibrate diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index b4edb7a34..73d817782 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -152,6 +152,20 @@ android:key="notifications_from_strangers" android:summary="@string/pref_notifications_from_strangers_summary" android:title="@string/pref_notifications_from_strangers" /> + + + + + +