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

Commit 6d4d8ed1 authored by Winson Chung's avatar Winson Chung
Browse files

Remove some blocking calls in BubbleManager



- When shell thread is enabled, there's a deadlock that can happen from
  having blocking calls from both Shell -> SysUI and vice versa.
  Specifically if you have a scenario like:

  Shell            SysUI
  A ----------------> f() (shell blocking call, posts onto thread & awaits)
                      |
  g() <-------------- B (sysui blocking call while f() is posted)
  |                   |
  |                   V
  V                   f() runs
  g() runs

  The call f() will never actually run since B came in which is blocked
  on g() which can't finish because f() has not returned.
- Instead, replace some blocking calls with preloaded info (ie. can send
  bubble entry and whether the the entry should bubble up to
  onRankingUpdated since it just calls back to query it), and make
  getShouldRestoredEntries() and getPendingOrActiveEntry() async with
  a callback.
- Can remove isNotificationShadeExpand() since it's not actually used
  anymore in bubble code

Bug: 161979899
Test: atest WMShellUnitTests
Test: atest SystemUITests

Change-Id: Iba834172c17c3f4b03c23448fef346c71c16c1bb
Signed-off-by: default avatarWinson Chung <winsonc@google.com>
parent 573555ff
Loading
Loading
Loading
Loading
+44 −46
Original line number Original line Diff line number Diff line
@@ -86,6 +86,7 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
@@ -586,11 +587,15 @@ public class BubbleController {
            // There were no bubbles saved for this used.
            // There were no bubbles saved for this used.
            return;
            return;
        }
        }
        for (BubbleEntry e : mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys)) {
        mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
            mMainExecutor.execute(() -> {
                for (BubbleEntry e : entries) {
                    if (canLaunchInActivityView(mContext, e)) {
                    if (canLaunchInActivityView(mContext, e)) {
                        updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
                        updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
                    }
                    }
                }
                }
            });
        });
        // Finally, remove the entries for this user now that bubbles are restored.
        // Finally, remove the entries for this user now that bubbles are restored.
        mSavedBubbleKeysPerUser.remove(mCurrentUserId);
        mSavedBubbleKeysPerUser.remove(mCurrentUserId);
    }
    }
@@ -856,21 +861,24 @@ public class BubbleController {
        }
        }
    }
    }


    private void onRankingUpdated(RankingMap rankingMap) {
    private void onRankingUpdated(RankingMap rankingMap,
            HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
        if (mTmpRanking == null) {
        if (mTmpRanking == null) {
            mTmpRanking = new NotificationListenerService.Ranking();
            mTmpRanking = new NotificationListenerService.Ranking();
        }
        }
        String[] orderedKeys = rankingMap.getOrderedKeys();
        String[] orderedKeys = rankingMap.getOrderedKeys();
        for (int i = 0; i < orderedKeys.length; i++) {
        for (int i = 0; i < orderedKeys.length; i++) {
            String key = orderedKeys[i];
            String key = orderedKeys[i];
            BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(key);
            Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
            BubbleEntry entry = entryData.first;
            boolean shouldBubbleUp = entryData.second;
            rankingMap.getRanking(key, mTmpRanking);
            rankingMap.getRanking(key, mTmpRanking);
            boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
            boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
            if (isActiveBubble && !mTmpRanking.canBubble()) {
            if (isActiveBubble && !mTmpRanking.canBubble()) {
                // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
                // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
                // This means that the app or channel's ability to bubble has been revoked.
                // This means that the app or channel's ability to bubble has been revoked.
                mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
                mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
            } else if (isActiveBubble && !mSysuiProxy.shouldBubbleUp(key)) {
            } else if (isActiveBubble && !shouldBubbleUp) {
                // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
                // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
                // This happens when DND is enabled and configured to hide bubbles. Dismissing with
                // This happens when DND is enabled and configured to hide bubbles. Dismissing with
                // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
                // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
@@ -919,7 +927,8 @@ public class BubbleController {
    private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
    private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
        Objects.requireNonNull(b);
        Objects.requireNonNull(b);
        b.setIsBubble(isBubble);
        b.setIsBubble(isBubble);
        final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(b.getKey());
        mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> {
            mMainExecutor.execute(() -> {
                if (entry != null) {
                if (entry != null) {
                    // Updating the entry to be a bubble will trigger our normal update flow
                    // Updating the entry to be a bubble will trigger our normal update flow
                    setIsBubble(entry, isBubble, b.shouldAutoExpand());
                    setIsBubble(entry, isBubble, b.shouldAutoExpand());
@@ -930,6 +939,8 @@ public class BubbleController {
                    inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
                    inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
                            !bubble.shouldAutoExpand() /* showInShade */);
                            !bubble.shouldAutoExpand() /* showInShade */);
                }
                }
            });
        });
    }
    }


    @SuppressWarnings("FieldCanBeLocal")
    @SuppressWarnings("FieldCanBeLocal")
@@ -992,7 +1003,8 @@ public class BubbleController {
                    }
                    }


                }
                }
                final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey());
                mSysuiProxy.getPendingOrActiveEntry(bubble.getKey(), (entry) -> {
                    mMainExecutor.execute(() -> {
                        if (entry != null) {
                        if (entry != null) {
                            final String groupKey = entry.getStatusBarNotification().getGroupKey();
                            final String groupKey = entry.getStatusBarNotification().getGroupKey();
                            if (getBubblesInGroup(groupKey).isEmpty()) {
                            if (getBubblesInGroup(groupKey).isEmpty()) {
@@ -1000,6 +1012,8 @@ public class BubbleController {
                                mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
                                mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
                            }
                            }
                        }
                        }
                    });
                });
            }
            }
            mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
            mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);


@@ -1121,23 +1135,6 @@ public class BubbleController {
        mStackView.updateContentDescription();
        mStackView.updateContentDescription();
    }
    }


    /**
     * The task id of the expanded view, if the stack is expanded and not occluded by the
     * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
     */
    private int getExpandedTaskId() {
        if (mStackView == null) {
            return INVALID_TASK_ID;
        }
        final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
        if (expandedViewProvider != null && isStackExpanded()
                && !mStackView.isExpansionAnimating()
                && !mSysuiProxy.isNotificationShadeExpand()) {
            return expandedViewProvider.getTaskId();
        }
        return INVALID_TASK_ID;
    }

    @VisibleForTesting
    @VisibleForTesting
    public BubbleStackView getStackView() {
    public BubbleStackView getStackView() {
        return mStackView;
        return mStackView;
@@ -1343,9 +1340,10 @@ public class BubbleController {
        }
        }


        @Override
        @Override
        public void onRankingUpdated(RankingMap rankingMap) {
        public void onRankingUpdated(RankingMap rankingMap,
                HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
            mMainExecutor.execute(() -> {
            mMainExecutor.execute(() -> {
                BubbleController.this.onRankingUpdated(rankingMap);
                BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey);
            });
            });
        }
        }


+9 −8
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import android.os.Bundle;
import android.os.Looper;
import android.os.Looper;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Pair;
import android.view.View;
import android.view.View;


import androidx.annotation.IntDef;
import androidx.annotation.IntDef;
@@ -37,6 +38,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.BiConsumer;
@@ -182,8 +184,11 @@ public interface Bubbles {
     * permissions on the notification channel or the global setting.
     * permissions on the notification channel or the global setting.
     *
     *
     * @param rankingMap the updated ranking map from NotificationListenerService
     * @param rankingMap the updated ranking map from NotificationListenerService
     * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
     *                       bubble up
     */
     */
    void onRankingUpdated(RankingMap rankingMap);
    void onRankingUpdated(RankingMap rankingMap,
            HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey);


    /**
    /**
     * Called when the status bar has become visible or invisible (either permanently or
     * Called when the status bar has become visible or invisible (either permanently or
@@ -243,14 +248,10 @@ public interface Bubbles {


    /** Callback to tell SysUi components execute some methods. */
    /** Callback to tell SysUi components execute some methods. */
    interface SysuiProxy {
    interface SysuiProxy {
        @Nullable
        void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
        BubbleEntry getPendingOrActiveEntry(String key);


        List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys);
        void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,

                Consumer<List<BubbleEntry>> callback);
        boolean isNotificationShadeExpand();

        boolean shouldBubbleUp(String key);


        void setNotificationInterruption(String key);
        void setNotificationInterruption(String key);


+2 −1
Original line number Original line Diff line number Diff line
@@ -43,6 +43,7 @@ import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_AT
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.getNavigationBarRect;
import static com.android.internal.policy.DecorView.getNavigationBarRect;


import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskDescription;
@@ -498,7 +499,7 @@ public class TaskSnapshotWindow {
        }
        }
    }
    }


    @ExternalThread
    @BinderThread
    static class Window extends BaseIWindow {
    static class Window extends BaseIWindow {
        private TaskSnapshotWindow mOuter;
        private TaskSnapshotWindow mOuter;


+26 −48
Original line number Original line Diff line number Diff line
@@ -45,6 +45,7 @@ import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.View;


import androidx.annotation.NonNull;
import androidx.annotation.NonNull;
@@ -86,10 +87,12 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.List;
import java.util.Optional;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
import java.util.function.Supplier;


@@ -248,38 +251,19 @@ public class BubblesManager implements Dumpable {
                });
                });


        mSysuiProxy = new Bubbles.SysuiProxy() {
        mSysuiProxy = new Bubbles.SysuiProxy() {
            private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor,
                    Class clazz) {
                if (Looper.myLooper() == Looper.getMainLooper()) {
                    return runnable.get();
                }
                final T[] result = (T[]) Array.newInstance(clazz, 1);
                final CountDownLatch latch = new CountDownLatch(1);
                executor.execute(() -> {
                    result[0] = runnable.get();
                    latch.countDown();
                });
                try {
                    latch.await();
                    return result[0];
                } catch (InterruptedException e) {
                    return null;
                }
            }

            @Override
            @Override
            @Nullable
            public void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback) {
            public BubbleEntry getPendingOrActiveEntry(String key) {
                sysuiMainExecutor.execute(() -> {
                return executeBlockingForResult(() -> {
                    NotificationEntry entry =
                    NotificationEntry entry =
                            mNotificationEntryManager.getPendingOrActiveNotif(key);
                            mNotificationEntryManager.getPendingOrActiveNotif(key);
                    return entry == null ? null : notifToBubbleEntry(entry);
                    callback.accept(entry == null ? null : notifToBubbleEntry(entry));
                }, sysuiMainExecutor, BubbleEntry.class);
                });
            }
            }


            @Override
            @Override
            public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
            public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
                return executeBlockingForResult(() -> {
                    Consumer<List<BubbleEntry>> callback) {
                sysuiMainExecutor.execute(() -> {
                    List<BubbleEntry> result = new ArrayList<>();
                    List<BubbleEntry> result = new ArrayList<>();
                    List<NotificationEntry> activeEntries =
                    List<NotificationEntry> activeEntries =
                            mNotificationEntryManager.getActiveNotificationsForCurrentUser();
                            mNotificationEntryManager.getActiveNotificationsForCurrentUser();
@@ -291,27 +275,8 @@ public class BubblesManager implements Dumpable {
                            result.add(notifToBubbleEntry(entry));
                            result.add(notifToBubbleEntry(entry));
                        }
                        }
                    }
                    }
                    return result;
                    callback.accept(result);
                }, sysuiMainExecutor, List.class);
                });
            }

            @Override
            public boolean isNotificationShadeExpand() {
                return executeBlockingForResult(() -> {
                    return mNotificationShadeWindowController.getPanelExpanded();
                }, sysuiMainExecutor, Boolean.class);
            }

            @Override
            public boolean shouldBubbleUp(String key) {
                return executeBlockingForResult(() -> {
                    final NotificationEntry entry =
                            mNotificationEntryManager.getPendingOrActiveNotif(key);
                    if (entry != null) {
                        return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
                    }
                    return false;
                }, sysuiMainExecutor, Boolean.class);
            }
            }


            @Override
            @Override
@@ -587,7 +552,20 @@ public class BubblesManager implements Dumpable {
    }
    }


    void onRankingUpdate(RankingMap rankingMap) {
    void onRankingUpdate(RankingMap rankingMap) {
        mBubbles.onRankingUpdated(rankingMap);
        String[] orderedKeys = rankingMap.getOrderedKeys();
        HashMap<String, Pair<BubbleEntry, Boolean>> pendingOrActiveNotif = new HashMap<>();
        for (int i = 0; i < orderedKeys.length; i++) {
            String key = orderedKeys[i];
            NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
            BubbleEntry bubbleEntry = entry != null
                    ? notifToBubbleEntry(entry)
                    : null;
            boolean shouldBubbleUp = entry != null
                    ? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
                    : false;
            pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
        }
        mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
    }
    }


    /**
    /**