Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 34326c14 authored by Mady Mellor's avatar Mady Mellor
Browse files

Update NoMan's ShortcutHelper to use ShortcutChangeCallback

LauncherApps.Callback isn't user aware so we wouldn't hear about
changes to shortcuts on different user accounts. This was found out
due to bubble CTS not passing on HSUM.

ShortcutChangeCallback is user aware, so migrate to this. Also update
the keys used for shortcuts stored to be package|userId. Added some
tests around this.

Flag: EXEMPT bugfix
Test: atest ShortcutHelperTest NotificationManagerServiceTest CtsNotificationTestCases:NotificationManagerBubbleTest CtsNotificationTestCases:NotificationManagerTest
Test: ran the CTS tests on a secondary user account
Bug: 330507887
Change-Id: I610545fdcdff330d0ef625abd1a6ce0199e7bd08
parent ea262442
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -8477,8 +8477,7 @@ public class NotificationManagerService extends SystemService {
                    mAttentionHelper.updateLightsLocked();
                    if (mShortcutHelper != null) {
                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
                                true /* isRemoved */,
                                mHandler);
                                true /* isRemoved */);
                    }
                } else {
                    // No notification was found, assume that it is snoozed and cancel it.
@@ -8860,8 +8859,7 @@ public class NotificationManagerService extends SystemService {
                    if (mShortcutHelper != null) {
                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
                                false /* isRemoved */,
                                mHandler);
                                false /* isRemoved */);
                    }
                    maybeRecordInterruptionLocked(r);
+74 −88
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
@@ -65,82 +64,31 @@ public class ShortcutHelper {
        void onShortcutRemoved(String key);
    }

    private final ShortcutListener mShortcutListener;
    private LauncherApps mLauncherAppsService;
    private ShortcutListener mShortcutListener;
    private ShortcutServiceInternal mShortcutServiceInternal;
    private UserManager mUserManager;

    // Key: packageName Value: <shortcutId, notifId>
    private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
    private boolean mLauncherAppsCallbackRegistered;
    // Key: packageName|userId Value: <shortcutId, notifId>
    private final HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
    private boolean mShortcutChangedCallbackRegistered;

    // Bubbles can be created based on a shortcut, we need to listen for changes to
    // that shortcut so that we may update the bubble appropriately.
    private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
        @Override
        public void onPackageRemoved(String packageName, UserHandle user) {
        }

        @Override
        public void onPackageAdded(String packageName, UserHandle user) {
        }

        @Override
        public void onPackageChanged(String packageName, UserHandle user) {
        }
    private final LauncherApps.ShortcutChangeCallback mShortcutChangeCallback =
            new LauncherApps.ShortcutChangeCallback() {

                @Override
        public void onPackagesAvailable(String[] packageNames, UserHandle user,
                boolean replacing) {
        }

        @Override
        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
                boolean replacing) {
        }

        @Override
        public void onShortcutsChanged(@NonNull String packageName,
                public void onShortcutsAddedOrUpdated(@NonNull String packageName,
                        @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
            HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
            ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
            if (shortcutBubbles != null) {
                // Copy to avoid a concurrent modification exception when we remove bubbles from
                // shortcutBubbles.
                final Set<String> shortcutIds = new HashSet<>(shortcutBubbles.keySet());

                // If we can't find one of our bubbles in the shortcut list, that bubble needs
                // to be removed.
                for (String shortcutId : shortcutIds) {
                    boolean foundShortcut = false;
                    for (int i = 0; i < shortcuts.size(); i++) {
                        if (shortcuts.get(i).getId().equals(shortcutId)) {
                            foundShortcut = true;
                            break;
                        }
                    }
                    if (!foundShortcut) {
                        bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
                        shortcutBubbles.remove(shortcutId);
                        if (shortcutBubbles.isEmpty()) {
                            mActiveShortcutBubbles.remove(packageName);
                            if (mLauncherAppsCallbackRegistered
                                    && mActiveShortcutBubbles.isEmpty()) {
                                mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
                                mLauncherAppsCallbackRegistered = false;
                            }
                        }
                    }
                }
                }

            // Let NoMan know about the updates
            for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
                // update flag bubble
                String bubbleKey = bubbleKeysToRemove.get(i);
                if (mShortcutListener != null) {
                    mShortcutListener.onShortcutRemoved(bubbleKey);
                }
                public void onShortcutsRemoved(@NonNull String packageName,
                        @NonNull List<ShortcutInfo> removedShortcuts, @NonNull UserHandle user) {
                    final String packageUserKey = getPackageUserKey(packageName, user);
                    if (mActiveShortcutBubbles.get(packageUserKey) == null) return;
                    for (ShortcutInfo info : removedShortcuts) {
                        onShortcutRemoved(packageUserKey, info.getId());
                    }
                }
            };
@@ -172,14 +120,14 @@ public class ShortcutHelper {
     * Returns whether the given shortcut info is a conversation shortcut.
     */
    public static boolean isConversationShortcut(
            ShortcutInfo shortcutInfo, ShortcutServiceInternal mShortcutServiceInternal,
            ShortcutInfo shortcutInfo, ShortcutServiceInternal shortcutServiceInternal,
            int callingUserId) {
        if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) {
            return false;
        }
        // TODO (b/155016294) uncomment when sharing shortcuts are required
        /*
        mShortcutServiceInternal.isSharingShortcut(callingUserId, "android",
        shortcutServiceInternal.isSharingShortcut(callingUserId, "android",
                shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(),
                SHARING_FILTER);
         */
@@ -233,34 +181,30 @@ public class ShortcutHelper {
     *
     * @param r the notification record to check
     * @param removedNotification true if this notification is being removed
     * @param handler handler to register the callback with
     */
    void maybeListenForShortcutChangesForBubbles(NotificationRecord r,
            boolean removedNotification,
            Handler handler) {
            boolean removedNotification) {
        final String shortcutId = r.getNotification().getBubbleMetadata() != null
                ? r.getNotification().getBubbleMetadata().getShortcutId()
                : null;
        final String packageUserKey = getPackageUserKey(r.getSbn().getPackageName(), r.getUser());
        if (!removedNotification
                && !TextUtils.isEmpty(shortcutId)
                && r.getShortcutInfo() != null
                && r.getShortcutInfo().getId().equals(shortcutId)) {
            // Must track shortcut based bubbles in case the shortcut is removed
            HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
                    r.getSbn().getPackageName());
                    packageUserKey);
            if (packageBubbles == null) {
                packageBubbles = new HashMap<>();
            }
            packageBubbles.put(shortcutId, r.getKey());
            mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
            if (!mLauncherAppsCallbackRegistered) {
                mLauncherAppsService.registerCallback(mLauncherAppsCallback, handler);
                mLauncherAppsCallbackRegistered = true;
            }
            mActiveShortcutBubbles.put(packageUserKey, packageBubbles);
            registerCallbackIfNeeded();
        } else {
            // No longer track shortcut
            HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
                    r.getSbn().getPackageName());
                    packageUserKey);
            if (packageBubbles != null) {
                if (!TextUtils.isEmpty(shortcutId)) {
                    packageBubbles.remove(shortcutId);
@@ -278,20 +222,62 @@ public class ShortcutHelper {
                    }
                }
                if (packageBubbles.isEmpty()) {
                    mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
                    mActiveShortcutBubbles.remove(packageUserKey);
                }
            }
            unregisterCallbackIfNeeded();
        }
    }

    private String getPackageUserKey(String packageName, UserHandle user) {
        return packageName + "|" + user.getIdentifier();
    }

    private void onShortcutRemoved(String packageUserKey, String shortcutId) {
        HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageUserKey);
        ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
        if (shortcutBubbles != null) {
            if (shortcutBubbles.containsKey(shortcutId)) {
                bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
                shortcutBubbles.remove(shortcutId);
                if (shortcutBubbles.isEmpty()) {
                    mActiveShortcutBubbles.remove(packageUserKey);
                    unregisterCallbackIfNeeded();
                }
            }
            if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
                mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
                mLauncherAppsCallbackRegistered = false;
            notifyNoMan(bubbleKeysToRemove);
        }
    }

    private void registerCallbackIfNeeded() {
        if (!mShortcutChangedCallbackRegistered) {
            mShortcutChangedCallbackRegistered = true;
            mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeCallback);
        }
    }

    private void unregisterCallbackIfNeeded() {
        if (mShortcutChangedCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
            mShortcutServiceInternal.removeShortcutChangeCallback(mShortcutChangeCallback);
            mShortcutChangedCallbackRegistered = false;
        }
    }

    void destroy() {
        if (mLauncherAppsCallbackRegistered) {
            mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
            mLauncherAppsCallbackRegistered = false;
        if (mShortcutChangedCallbackRegistered) {
            mShortcutServiceInternal.removeShortcutChangeCallback(mShortcutChangeCallback);
            mShortcutChangedCallbackRegistered = false;
        }
    }

    private void notifyNoMan(List<String> bubbleKeysToRemove) {
        // Let NoMan know about the updates
        for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
            // update flag bubble
            String bubbleKey = bubbleKeysToRemove.get(i);
            if (mShortcutListener != null) {
                mShortcutListener.onShortcutRemoved(bubbleKey);
            }
        }
    }
}
+26 −17
Original line number Diff line number Diff line
@@ -87,7 +87,6 @@ import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
@@ -832,13 +831,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        // Pretend the shortcut exists
        List<ShortcutInfo> shortcutInfos = new ArrayList<>();
        ShortcutInfo info = mock(ShortcutInfo.class);
        when(info.getPackage()).thenReturn(mPkg);
        when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
        when(info.getUserId()).thenReturn(USER_SYSTEM);
        when(info.isLongLived()).thenReturn(true);
        when(info.isEnabled()).thenReturn(true);
        shortcutInfos.add(info);
        shortcutInfos.add(createMockConvoShortcut());
        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
        when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
                anyString(), anyInt(), any())).thenReturn(true);
@@ -10771,8 +10764,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                BUBBLE_PREFERENCE_ALL /* app */,
                true /* channel */);
        ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
                ArgumentCaptor.forClass(LauncherApps.Callback.class);
        ArgumentCaptor<LauncherApps.ShortcutChangeCallback> shortcutChangeCallback =
                ArgumentCaptor.forClass(LauncherApps.ShortcutChangeCallback.class);
        // Messaging notification with shortcut info
        Notification.BubbleMetadata metadata =
@@ -10793,7 +10786,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        // Verify:
        // Make sure we register the callback for shortcut changes
        verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
        verify(mShortcutServiceInternal, times(1)).addShortcutChangeCallback(
                shortcutChangeCallback.capture());
        // yes allowed, yes messaging w/shortcut, yes bubble
        Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
@@ -10806,14 +10800,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        // Test: Remove the shortcut
        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
        launcherAppsCallback.getValue().onShortcutsChanged(mPkg, emptyList(),
        ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
        removedShortcuts.add(createMockConvoShortcut());
        shortcutChangeCallback.getValue().onShortcutsRemoved(mPkg, removedShortcuts,
                UserHandle.getUserHandleForUid(mUid));
        waitForIdle();
        // Verify:
        // Make sure callback is unregistered
        verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
        verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(
                shortcutChangeCallback.getValue());
        // We're no longer a bubble
        NotificationRecord notif2 = mService.getNotificationRecord(
@@ -10831,8 +10828,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                BUBBLE_PREFERENCE_ALL /* app */,
                true /* channel */);
        ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
                ArgumentCaptor.forClass(LauncherApps.Callback.class);
        ArgumentCaptor<LauncherApps.ShortcutChangeCallback> shortcutChangeCallback =
                ArgumentCaptor.forClass(LauncherApps.ShortcutChangeCallback.class);
        // Messaging notification with shortcut info
        Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
@@ -10866,7 +10863,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        // Verify:
        // Make sure we register the callback for shortcut changes
        verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
        verify(mShortcutServiceInternal, times(1)).addShortcutChangeCallback(
                shortcutChangeCallback.capture());
        // yes allowed, yes messaging w/shortcut, yes bubble
        Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
@@ -10885,7 +10883,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        // Verify:
        // Make sure callback is unregistered
        verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
        verify(mShortcutServiceInternal, times(1)).removeShortcutChangeCallback(
                shortcutChangeCallback.getValue());
    }
    @Test
@@ -15804,4 +15803,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
    }
    private ShortcutInfo createMockConvoShortcut() {
        ShortcutInfo info = mock(ShortcutInfo.class);
        when(info.getPackage()).thenReturn(mPkg);
        when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
        when(info.getUserId()).thenReturn(USER_SYSTEM);
        when(info.isLongLived()).thenReturn(true);
        when(info.isEnabled()).thenReturn(true);
        return info;
    }
}
+127 −85

File changed.

Preview size limit exceeded, changes collapsed.