Skip to content

Commit

Permalink
[Refactor] Prevent overriding notifications
Browse files Browse the repository at this point in the history
Signed-off-by: Muntashir Al-Islam <[email protected]>
  • Loading branch information
MuntashirAkon committed Jul 18, 2023
1 parent 904fb5e commit 78bfe11
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ static void launchApp(@NonNull FragmentActivity activity, @NonNull FreezeUnfreez
// There's a small chance that the notification by shortcutInfo.hasCode() already exists, in that case,
// find the next one. This will cause trouble with dismissing the notification, but this is a viable
// trade-off.
int notificationId = NotificationUtils.nextNotificationId(shortcutInfo.hashCode());
NotificationUtils.displayFreezeUnfreezeNotification(activity, notificationId, builder -> builder
String notificationTag = String.valueOf(shortcutInfo.hashCode());
NotificationUtils.displayFreezeUnfreezeNotification(activity, notificationTag, builder -> builder
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_default_notification)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ protected void onNewIntent(Intent intent) {

private void hideNotification(@Nullable FreezeUnfreezeShortcutInfo shortcutInfo) {
if (shortcutInfo == null) return;
int notificationId = shortcutInfo.hashCode();
NotificationUtils.getFreezeUnfreezeNotificationManager(this).cancel(notificationId);
String notificationTag = String.valueOf(shortcutInfo.hashCode());
NotificationUtils.getFreezeUnfreezeNotificationManager(this).cancel(notificationTag, 1);
}

public static class FreezeUnfreezeViewModel extends AndroidViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class FreezeUnfreezeService extends Service {
private static final String STOP_ACTION = BuildConfig.APPLICATION_ID + ".action.STOP_FREEZE_UNFREEZE_MONITOR";

private final Map<String, FreezeUnfreezeShortcutInfo> mPackagesToShortcut = new HashMap<>();
private final Map<String, Integer> mPackagesToNotificationId = new HashMap<>();
private final Map<String, String> mPackagesToNotificationTag = new HashMap<>();
private ScreenLockChecker mScreenLockChecker;
private final BroadcastReceiver mScreenLockedReceiver = new BroadcastReceiver() {
@Override
Expand Down Expand Up @@ -105,7 +105,7 @@ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
.setSubText(getText(R.string.freeze_unfreeze))
.setPriority(NotificationCompat.PRIORITY_LOW)
.addAction(stopServiceAction);
startForeground(NotificationUtils.nextNotificationId(), builder.build());
startForeground(NotificationUtils.nextNotificationId(null), builder.build());
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
Expand Down Expand Up @@ -142,15 +142,15 @@ private void onHandleIntent(@Nullable Intent intent) {
FreezeUnfreezeShortcutInfo shortcutInfo = FreezeUnfreeze.getShortcutInfo(intent);
if (shortcutInfo == null) return;
mPackagesToShortcut.put(shortcutInfo.packageName, shortcutInfo);
int notificationId = shortcutInfo.hashCode();
mPackagesToNotificationId.put(shortcutInfo.packageName, notificationId);
String notificationTag = String.valueOf(shortcutInfo.hashCode());
mPackagesToNotificationTag.put(shortcutInfo.packageName, notificationTag);
}

@WorkerThread
private void freezeAllPackages() {
for (String packageName : mPackagesToShortcut.keySet()) {
FreezeUnfreezeShortcutInfo shortcutInfo = mPackagesToShortcut.get(packageName);
Integer notificationId = mPackagesToNotificationId.get(packageName);
String notificationTag = mPackagesToNotificationTag.get(packageName);
if (shortcutInfo != null) {
try {
ApplicationInfo applicationInfo = PackageManagerCompat.getApplicationInfo(shortcutInfo.packageName,
Expand All @@ -165,8 +165,8 @@ private void freezeAllPackages() {
e.printStackTrace();
}
}
if (notificationId != null) {
NotificationUtils.getFreezeUnfreezeNotificationManager(this).cancel(notificationId);
if (notificationTag != null) {
NotificationUtils.getFreezeUnfreezeNotificationManager(this).cancel(notificationTag, 1);
}
}
stopSelf();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
import io.github.muntashirakon.AppManager.utils.NotificationUtils;

public class NotificationProgressHandler extends QueuedProgressHandler {
private static final String TAG_PROGRESS = null;
private static final String TAG_QUEUE = "queue";
private static final String TAG_ALERT = "alert";

@NonNull
private final Context mContext;
@NonNull
Expand Down Expand Up @@ -62,7 +66,7 @@ public NotificationProgressHandler(@NonNull Context context,
mProgressNotificationManager = getNotificationManager(context, mProgressNotificationManagerInfo);
mCompletionNotificationManager = getNotificationManager(context, mCompletionNotificationManagerInfo);
mQueueNotificationManager = getNotificationManager(context, mQueueNotificationManagerInfo);
mProgressNotificationId = NotificationUtils.acquireNotificationId();
mProgressNotificationId = NotificationUtils.nextNotificationId(TAG_PROGRESS);
}

@Override
Expand All @@ -75,7 +79,7 @@ public void onQueue(@Nullable Object message) {
.getBuilder(mContext, mQueueNotificationManagerInfo)
.setLocalOnly(true)
.build();
notify(mContext, mQueueNotificationManager, NotificationUtils.nextNotificationId(), notification);
notify(mContext, mQueueNotificationManager, TAG_QUEUE, NotificationUtils.nextNotificationId(TAG_QUEUE), notification);
}

@Override
Expand Down Expand Up @@ -127,13 +131,13 @@ public void onProgressUpdate(int max, float current, @Nullable Object message) {
builder.setContentText(mContext.getString(R.string.operation_running));
}
}
notify(mContext, mProgressNotificationManager, mProgressNotificationId, builder.build());
notify(mContext, mProgressNotificationManager, TAG_PROGRESS, mProgressNotificationId, builder.build());
}

@Override
public void onResult(@Nullable Object message) {
if (!mAttachedToService) {
mProgressNotificationManager.cancel(mProgressNotificationId);
mProgressNotificationManager.cancel(TAG_PROGRESS, mProgressNotificationId);
} else {
onProgressUpdate(MAX_FINISHED, 0, null); // Trick to remove progressbar
}
Expand All @@ -144,15 +148,14 @@ public void onResult(@Nullable Object message) {
Notification notification = info
.getBuilder(mContext, mCompletionNotificationManagerInfo)
.build();
notify(mContext, mCompletionNotificationManager, NotificationUtils.nextNotificationId(), notification);
notify(mContext, mCompletionNotificationManager, TAG_ALERT, NotificationUtils.nextNotificationId(TAG_ALERT), notification);
}

@Override
public void onDetach(@Nullable Service service) {
NotificationUtils.releaseNotificationId(mProgressNotificationId);
if (service != null) {
mAttachedToService = false;
mProgressNotificationManager.cancel(mProgressNotificationId);
mProgressNotificationManager.cancel(TAG_PROGRESS, mProgressNotificationId);
}
}

Expand Down Expand Up @@ -189,10 +192,11 @@ public void postUpdate(int max, float current, @Nullable Object message) {

private static void notify(@NonNull Context context,
@NonNull NotificationManagerCompat notificationManager,
@Nullable String notificationTag,
int notificationId,
@NonNull Notification notification) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
notificationManager.notify(notificationId, notification);
notificationManager.notify(notificationTag, notificationId, notification);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentIntent(defaultIntent)
.addAction(stopServiceAction);
startForeground(NotificationUtils.nextNotificationId(), builder.build());
startForeground(NotificationUtils.nextNotificationId(null), builder.build());
if (screenLockEnabled && Prefs.Privacy.isAutoLockEnabled()) {
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@
package io.github.muntashirakon.AppManager.utils;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Process;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
Expand All @@ -23,11 +19,9 @@

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.progress.NotificationProgressHandler;
Expand Down Expand Up @@ -71,39 +65,17 @@ public interface NotificationBuilder {
Notification build(NotificationCompat.Builder builder);
}

private static final Set<Integer> sNotificationIds = Collections.synchronizedSet(new HashSet<>(8));
private static final Set<Integer> sReservedNotificationIds = new HashSet<>(8);
private static final Map<String, Integer> sNotificationIds = Collections.synchronizedMap(new HashMap<>());

public static int acquireNotificationId() {
int newNotificationId = nextNotificationId();
synchronized (sReservedNotificationIds) {
sReservedNotificationIds.add(newNotificationId);
}
return newNotificationId;
}

public static void releaseNotificationId(int notificationId) {
synchronized (sReservedNotificationIds) {
sReservedNotificationIds.remove(notificationId);
}
}

public static int nextNotificationId() {
return nextNotificationId(1);
}

public static int nextNotificationId(int start) {
synchronized (sNotificationIds) {
sNotificationIds.clear();
for (StatusBarNotification notification : getActiveNotifications()) {
sNotificationIds.add(notification.getId());
}
int i = start;
synchronized (sReservedNotificationIds) {
while (sNotificationIds.contains(i) || sReservedNotificationIds.contains(i)) ++i;
}
return i;
public static int nextNotificationId(@Nullable String tag) {
Integer id = sNotificationIds.get(tag);
if (id == null) {
sNotificationIds.put(tag, 1);
return 1;
}
++id;
sNotificationIds.put(tag, id);
return id;
}

@NonNull
Expand All @@ -119,23 +91,24 @@ public static void displayHighPriorityNotification(@NonNull Context context, Not

public static void displayHighPriorityNotification(@NonNull Context context,
@NonNull NotificationBuilder notification) {
int notificationId = nextNotificationId();
String notificationTag = "alert";
int notificationId = nextNotificationId(notificationTag);
displayNotification(context, HIGH_PRIORITY_CHANNEL_ID, "Alerts",
NotificationManagerCompat.IMPORTANCE_HIGH, notificationId, notification);
NotificationManagerCompat.IMPORTANCE_HIGH, notificationTag , notificationId, notification);
}

public static void displayFreezeUnfreezeNotification(@NonNull Context context,
int notificationId,
String notificationTag,
@NonNull NotificationBuilder notification) {
displayNotification(context, FREEZE_UNFREEZE_CHANNEL_ID, "Freeze",
NotificationManagerCompat.IMPORTANCE_DEFAULT, notificationId, notification);
NotificationManagerCompat.IMPORTANCE_DEFAULT, notificationTag, 1, notification);
}

public static int displayInstallConfirmNotification(@NonNull Context context,
@NonNull NotificationBuilder notification) {
int notificationId = nextNotificationId();
int notificationId = nextNotificationId(INSTALL_CONFIRM_CHANNEL_ID);
displayNotification(context, INSTALL_CONFIRM_CHANNEL_ID, "Confirm Installation",
NotificationManagerCompat.IMPORTANCE_HIGH, notificationId, notification);
NotificationManagerCompat.IMPORTANCE_HIGH, INSTALL_CONFIRM_CHANNEL_ID, notificationId, notification);
return notificationId;
}

Expand All @@ -145,21 +118,22 @@ public static void cancelInstallConfirmNotification(@NonNull Context context, in
}
NotificationManagerCompat manager = getNewNotificationManager(context, INSTALL_CONFIRM_CHANNEL_ID,
"Confirm Installation", NotificationManagerCompat.IMPORTANCE_HIGH);
manager.cancel(notificationId);
manager.cancel(INSTALL_CONFIRM_CHANNEL_ID, notificationId);
}

private static void displayNotification(@NonNull Context context,
@NonNull String channelId,
@NonNull CharSequence channelName,
@NotificationImportance int importance,
@Nullable String notificationTag,
int notificationId,
@NonNull NotificationBuilder notification) {
NotificationManagerCompat manager = getNewNotificationManager(context, channelId, channelName, importance);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
.setLocalOnly(!Prefs.Misc.sendNotificationsToConnectedDevices())
.setPriority(importanceToPriority(importance));
if (SelfPermissions.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)) {
manager.notify(notificationId, notification.build(builder));
manager.notify(notificationTag, notificationId, notification.build(builder));
}
}

Expand Down Expand Up @@ -221,24 +195,4 @@ public static Intent getNotificationSettingIntent(@Nullable String channelId) {
}
return intent;
}

@SuppressWarnings("JavaReflectionMemberAccess")
@SuppressLint("DiscouragedPrivateApi")
private static StatusBarNotification[] getActiveNotifications() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
NotificationManager notificationManager = (NotificationManager) ContextUtils.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
return ArrayUtils.defeatNullable(StatusBarNotification.class, notificationManager.getActiveNotifications());
}
try {
// Fetch using private API

Method getService = NotificationManager.class.getDeclaredMethod("getService");
INotificationManager notificationManager = (INotificationManager) Objects.requireNonNull(getService.invoke(null));
return ArrayUtils.defeatNullable(StatusBarNotification.class, notificationManager.getActiveNotifications(BuildConfig.APPLICATION_ID));
} catch (Throwable th) {
// Should never happen
th.printStackTrace();
return ArrayUtils.emptyArray(StatusBarNotification.class);
}
}
}

0 comments on commit 78bfe11

Please sign in to comment.