diff --git a/app/src/main/java/com/termux/app/settings/preferences/TermuxAppSharedPreferences.java b/app/src/main/java/com/termux/app/settings/preferences/TermuxAppSharedPreferences.java index 33c12d9079..a1e663d7bc 100644 --- a/app/src/main/java/com/termux/app/settings/preferences/TermuxAppSharedPreferences.java +++ b/app/src/main/java/com/termux/app/settings/preferences/TermuxAppSharedPreferences.java @@ -122,6 +122,16 @@ public void setLogLevel(Context context, int logLevel) { + public int getLastNotificationId() { + return SharedPreferenceUtils.getInt(mSharedPreferences, TERMUX_APP.KEY_LAST_NOTIFICATION_ID, TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID); + } + + public void setLastNotificationId(int notificationId) { + SharedPreferenceUtils.setInt(mSharedPreferences, TERMUX_APP.KEY_LAST_NOTIFICATION_ID, notificationId, false); + } + + + public boolean getTerminalViewKeyLoggingEnabled() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_TERMINAL_VIEW_KEY_LOGGING_ENABLED, TERMUX_APP.DEFAULT_VALUE_TERMINAL_VIEW_KEY_LOGGING_ENABLED); } diff --git a/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java b/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java index 605e9f0610..51ac344f84 100644 --- a/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java +++ b/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java @@ -1,7 +1,7 @@ package com.termux.app.settings.preferences; /* - * Version: v0.4.0 + * Version: v0.5.0 * * Changelog * @@ -9,19 +9,23 @@ * - Initial Release. * * - 0.2.0 (2021-03-13) - * - Added `KEY_LOG_LEVEL` and `KEY_TERMINAL_VIEW_LOGGING_ENABLED` - * + * - Added `KEY_LOG_LEVEL` and `KEY_TERMINAL_VIEW_LOGGING_ENABLED`. + * * - 0.3.0 (2021-03-16) * - Changed to per app scoping of variables so that the same file can store all constants of * Termux app and its plugins. This will allow {@link com.termux.app.TermuxSettings} to * manage preferences of plugins as well if they don't have launcher activity themselves * and also allow plugin apps to make changes to preferences from background. * - Added following to `TERMUX_TASKER_APP`: - * `KEY_LOG_LEVEL`. + * `KEY_LOG_LEVEL`. * * - 0.4.0 (2021-03-13) * - Added following to `TERMUX_APP`: * `KEY_PLUGIN_ERROR_NOTIFICATIONS_ENABLED` and `DEFAULT_VALUE_PLUGIN_ERROR_NOTIFICATIONS_ENABLED`. + * + * - 0.5.0 (2021-03-24) + * - Added following to `TERMUX_APP`: + * `KEY_LAST_NOTIFICATION_ID` and `DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID`. */ /** @@ -70,6 +74,13 @@ public static final class TERMUX_APP { public static final String KEY_LOG_LEVEL = "log_level"; + /** + * Defines the key for last used notification id + */ + public static final String KEY_LAST_NOTIFICATION_ID = "last_notification_id"; + public static final int DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID = 0; + + /** * Defines the key for whether termux terminal view key logging is enabled or not */ diff --git a/app/src/main/java/com/termux/app/utils/NotificationUtils.java b/app/src/main/java/com/termux/app/utils/NotificationUtils.java new file mode 100644 index 0000000000..9c83132ed1 --- /dev/null +++ b/app/src/main/java/com/termux/app/utils/NotificationUtils.java @@ -0,0 +1,166 @@ +package com.termux.app.utils; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.os.Build; + +import androidx.annotation.Nullable; + +import com.termux.app.RunCommandService; +import com.termux.app.TermuxService; +import com.termux.app.settings.preferences.TermuxAppSharedPreferences; +import com.termux.app.settings.preferences.TermuxPreferenceConstants; + +public class NotificationUtils { + + /** Do not show notification */ + public static final int NOTIFICATION_MODE_NONE = 0; + /** Show notification without sound, vibration or lights */ + public static final int NOTIFICATION_MODE_SILENT = 1; + /** Show notification with sound */ + public static final int NOTIFICATION_MODE_SOUND = 2; + /** Show notification with vibration */ + public static final int NOTIFICATION_MODE_VIBRATE = 3; + /** Show notification with lights */ + public static final int NOTIFICATION_MODE_LIGHTS = 4; + /** Show notification with sound and vibration */ + public static final int NOTIFICATION_MODE_SOUND_AND_VIBRATE = 5; + /** Show notification with sound and lights */ + public static final int NOTIFICATION_MODE_SOUND_AND_LIGHTS = 6; + /** Show notification with vibration and lights */ + public static final int NOTIFICATION_MODE_VIBRATE_AND_LIGHTS = 7; + /** Show notification with sound, vibration and lights */ + public static final int NOTIFICATION_MODE_ALL = 8; + + private static final String LOG_TAG = "NotificationUtils"; + + /** + * Get the {@link NotificationManager}. + * + * @param context The {@link Context} for operations. + * @return Returns the {@link NotificationManager}. + */ + @Nullable + public static NotificationManager getNotificationManager(final Context context) { + if(context == null) return null; + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + /** + * Try to get the next unique notification id that isn't already being used by the app. + * + * @param context The {@link Context} for operations. + * @return Returns the notification id that should be safe to use. + */ + public synchronized static int getNextNotificationId(final Context context) { + if(context == null) return TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; + + TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); + int lastNotificationId = preferences.getLastNotificationId(); + + int nextNotificationId = lastNotificationId + 1; + while(nextNotificationId == TermuxService.NOTIFICATION_ID || nextNotificationId == RunCommandService.NOTIFICATION_ID) { + nextNotificationId++; + } + + if(nextNotificationId == Integer.MAX_VALUE || nextNotificationId < 0) + nextNotificationId = TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; + + preferences.setLastNotificationId(nextNotificationId); + return nextNotificationId; + } + + /** + * Get {@link Notification.Builder}. + * + * @param context The {@link Context} for operations. + * @param title The title for the notification. + * @param notifiationText The second line text of the notification. + * @param notificationBigText The full text of the notification that may optionally be styled. + * @param pendingIntent The {@link PendingIntent} which should be sent when notification is clicked. + * @param notificationMode The notification mode. It must be one of {@code NotificationUtils.NOTIFICATION_MODE_*}. + * The builder returned will be {@code null} if {@link #NOTIFICATION_MODE_NONE} + * is passed. That case should ideally be handled before calling this function. + * @return Returns the {@link Notification.Builder}. + */ + @Nullable + public static Notification.Builder geNotificationBuilder(final Context context, final String channelId, final int priority, final CharSequence title, final CharSequence notifiationText, final CharSequence notificationBigText, final PendingIntent pendingIntent, final int notificationMode) { + if(context == null) return null; + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle(title); + builder.setContentText(notifiationText); + builder.setStyle(new Notification.BigTextStyle().bigText(notificationBigText)); + builder.setContentIntent(pendingIntent); + + builder.setPriority(priority); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + builder.setChannelId(channelId); + + builder = setNotificationDefaults(builder, notificationMode); + + return builder; + } + + /** + * Setup the notification channel if Android version is greater than or equal to + * {@link Build.VERSION_CODES#O}. + * + * @param context The {@link Context} for operations. + * @param channelId The id of the channel. Must be unique per package. + * @param channelName The user visible name of the channel. + * @param importance The importance of the channel. This controls how interruptive notifications + * posted to this channel are. + */ + public static void setupNotificationChannel(final Context context, final String channelId, final CharSequence channelName, final int importance) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + + NotificationManager notificationManager = getNotificationManager(context); + if(notificationManager != null) + notificationManager.createNotificationChannel(channel); + } + + public static Notification.Builder setNotificationDefaults(Notification.Builder builder, final int notificationMode) { + + // TODO: setDefaults() is deprecated and should also implement setting notification mode via notification channel + switch (notificationMode) { + case NOTIFICATION_MODE_NONE: + Logger.logWarn(LOG_TAG, "The NOTIFICATION_MODE_NONE passed to setNotificationDefaults(), force setting builder to null."); + return null; // return null since notification is not supposed to be shown + case NOTIFICATION_MODE_SILENT: + break; + case NOTIFICATION_MODE_SOUND: + builder.setDefaults(Notification.DEFAULT_SOUND); + break; + case NOTIFICATION_MODE_VIBRATE: + builder.setDefaults(Notification.DEFAULT_VIBRATE); + break; + case NOTIFICATION_MODE_LIGHTS: + builder.setDefaults(Notification.DEFAULT_LIGHTS); + break; + case NOTIFICATION_MODE_SOUND_AND_VIBRATE: + builder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE); + break; + case NOTIFICATION_MODE_SOUND_AND_LIGHTS: + builder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS); + break; + case NOTIFICATION_MODE_VIBRATE_AND_LIGHTS: + builder.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS); + break; + case NOTIFICATION_MODE_ALL: + builder.setDefaults(Notification.DEFAULT_ALL); + break; + default: + Logger.logError(LOG_TAG, "Invalid notificationMode: \"" + notificationMode + "\" passed to setNotificationDefaults()"); + break; + } + + return builder; + } + +}