Move Bubbles into a hw-accelerated TYPE_APPLICATION_OVERLAY WindowManager layer.
This addresses several issues caused by Bubbles living in the status bar window and makes it easier to address several more open bugs.
Test: atest SystemUITests
Test: manually, a lot
Fixes: 130579263
Fixes: 142075351
Fixes: 151086286
Fixes: 149473357
Bug: 150906076
Change-Id: I61594931cce55cba78e6cb1fcec440e4fec32bb0
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 013f222..cafa060 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -29,7 +29,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -53,7 +53,9 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -66,7 +68,7 @@
import android.util.SparseSetArray;
import android.view.Display;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.view.WindowManager;
import androidx.annotation.IntDef;
import androidx.annotation.MainThread;
@@ -76,7 +78,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.R;
import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
@@ -88,6 +89,7 @@
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
+import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -98,6 +100,7 @@
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -145,7 +148,6 @@
private final NotificationEntryManager mNotificationEntryManager;
private final NotifPipeline mNotifPipeline;
private final BubbleTaskStackListener mTaskStackListener;
- private BubbleStateChangeListener mStateChangeListener;
private BubbleExpandListener mExpandListener;
@Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final NotificationGroupManager mNotificationGroupManager;
@@ -153,6 +155,7 @@
private final FloatingContentCoordinator mFloatingContentCoordinator;
private BubbleData mBubbleData;
+ private ScrimView mBubbleScrim;
@Nullable private BubbleStackView mStackView;
private BubbleIconFactory mBubbleIconFactory;
@@ -175,11 +178,16 @@
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private IStatusBarService mBarService;
+ private WindowManager mWindowManager;
private SysUiState mSysUiState;
// Used to post to main UI thread
private Handler mHandler = new Handler();
+ /** LayoutParams used to add the BubbleStackView to the window maanger. */
+ private WindowManager.LayoutParams mWmLayoutParams;
+
+
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
@@ -195,16 +203,6 @@
private final List<NotifCallback> mCallbacks = new ArrayList<>();
/**
- * Listener to be notified when some states of the bubbles change.
- */
- public interface BubbleStateChangeListener {
- /**
- * Called when the stack has bubbles or no longer has bubbles.
- */
- void onHasBubblesChanged(boolean hasBubbles);
- }
-
- /**
* Listener to find out about stack expansion / collapse events.
*/
public interface BubbleExpandListener {
@@ -393,9 +391,12 @@
}
mSurfaceSynchronizer = synchronizer;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mBubbleScrim = new ScrimView(mContext);
+
mSavedBubbleKeysPerUser = new SparseSetArray<>();
mCurrentUserId = mNotifUserManager.getCurrentUserId();
mNotifUserManager.addUserChangedListener(
@@ -573,6 +574,15 @@
}
/**
+ * Returns the scrim drawn behind the bubble stack. This is managed by {@link ScrimController}
+ * since we want the scrim's appearance and behavior to be identical to that of the notification
+ * shade scrim.
+ */
+ public ScrimView getScrimForBubble() {
+ return mBubbleScrim;
+ }
+
+ /**
* Sets whether to perform inflation on the same thread as the caller. This method should only
* be used in tests, not in production.
*/
@@ -592,7 +602,6 @@
return mBubbleData.getOverflowBubbles();
}
-
/**
* BubbleStackView is lazily created by this method the first time a Bubble is added. This
* method initializes the stack view and adds it to the StatusBar just above the scrim.
@@ -602,11 +611,8 @@
mStackView = new BubbleStackView(
mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
mSysUiState, mNotificationShadeWindowController);
- ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView();
- int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble));
- int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim.
- nsv.addView(mStackView, stackIndex,
- new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mStackView.addView(mBubbleScrim);
+ addToWindowManager();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
@@ -616,6 +622,45 @@
}
}
+ /** Adds the BubbleStackView to the WindowManager. */
+ private void addToWindowManager() {
+ mWmLayoutParams = new WindowManager.LayoutParams(
+ // Fill the screen so we can use translation animations to position the bubble
+ // stack. We'll use touchable regions to ignore touches that are not on the bubbles
+ // themselves.
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ // Start not focusable - we'll become focusable when expanded so the ActivityView
+ // can use the IME.
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT);
+
+ mWmLayoutParams.setFitInsetsTypes(0);
+ mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ mWmLayoutParams.token = new Binder();
+ mWmLayoutParams.setTitle("Bubbles!");
+ mWmLayoutParams.packageName = mContext.getPackageName();
+ mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+ mWindowManager.addView(mStackView, mWmLayoutParams);
+ }
+
+ private void updateWmFlags() {
+ if (isStackExpanded()) {
+ // If we're expanded, we want to be focusable so that the ActivityView can receive focus
+ // and show the IME.
+ mWmLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ // If we're collapsed, we don't want to be able to receive focus. Doing so would
+ // preclude applications from using the IME since we are always above them.
+ mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+
+ mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ }
+
/**
* Records the notification key for any active bubbles. These are used to restore active
* bubbles when the user returns to the foreground.
@@ -687,13 +732,6 @@
}
/**
- * Set a listener to be notified when some states of the bubbles change.
- */
- public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
- mStateChangeListener = listener;
- }
-
- /**
* Set a listener to be notified of bubble expand events.
*/
public void setExpandListener(BubbleExpandListener listener) {
@@ -701,7 +739,8 @@
if (listener != null) {
listener.onBubbleExpandChanged(isExpanding, key);
}
- mNotificationShadeWindowController.setBubbleExpanded(isExpanding);
+
+ updateWmFlags();
});
if (mStackView != null) {
mStackView.setExpandListener(mExpandListener);
@@ -712,7 +751,8 @@
* Whether or not there are bubbles present, regardless of them being visible on the
* screen (e.g. if on AOD).
*/
- public boolean hasBubbles() {
+ @VisibleForTesting
+ boolean hasBubbles() {
if (mStackView == null) {
return false;
}
@@ -1146,8 +1186,7 @@
/**
* Lets any listeners know if bubble state has changed.
* Updates the visibility of the bubbles based on current state.
- * Does not un-bubble, just hides or un-hides. Notifies any
- * {@link BubbleStateChangeListener}s of visibility changes.
+ * Does not un-bubble, just hides or un-hides.
* Updates stack description for TalkBack focus.
*/
public void updateStack() {
@@ -1161,29 +1200,10 @@
mStackView.setVisibility(INVISIBLE);
}
- // Let listeners know if bubble state changed.
- boolean hadBubbles = mNotificationShadeWindowController.getBubblesShowing();
- boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
- mNotificationShadeWindowController.setBubblesShowing(hasBubblesShowing);
- if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
- mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
- }
-
mStackView.updateContentDescription();
}
/**
- * Rect indicating the touchable region for the bubble stack / expanded stack.
- */
- public Rect getTouchableRegion() {
- if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
- return null;
- }
- mStackView.getBoundsOnScreen(mTempRect);
- return mTempRect;
- }
-
- /**
* The display id of the expanded view, if the stack is expanded and not occluded by the
* status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 366d4a7..c802b59 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -108,7 +108,8 @@
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
*/
-public class BubbleStackView extends FrameLayout {
+public class BubbleStackView extends FrameLayout
+ implements ViewTreeObserver.OnComputeInternalInsetsListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
/** Animation durations for bubble stack user education views. **/
@@ -647,7 +648,6 @@
private boolean mShowingManage = false;
private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
@SuppressLint("ClickableViewAccessibility")
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
@@ -1052,14 +1052,24 @@
}
@Override
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
- getBoundsOnScreen(outRect);
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+ getTouchableRegion(mTempRect);
+ inoutInfo.touchableRegion.set(mTempRect);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
}
@Override
@@ -1960,8 +1970,16 @@
mAfterFlyoutHidden = null;
}
- @Override
- public void getBoundsOnScreen(Rect outRect) {
+ /**
+ * Fills the Rect with the touchable region of the bubbles. This will be used by WindowManager
+ * to decide which touch events go to Bubbles.
+ *
+ * Bubbles is below the status bar/notification shade but above application windows. If you're
+ * trying to get touch events from the status bar or another higher-level window layer, you'll
+ * need to re-order TYPE_BUBBLES in WindowManagerPolicy so that we have the opportunity to steal
+ * them.
+ */
+ public void getTouchableRegion(Rect outRect) {
if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
// When user education shows then capture all touches
outRect.set(0, 0, getWidth(), getHeight());
@@ -1971,12 +1989,12 @@
if (!mIsExpanded) {
if (getBubbleCount() > 0) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
+ // Increase the touch target size of the bubble
+ outRect.top -= mBubbleTouchPadding;
+ outRect.left -= mBubbleTouchPadding;
+ outRect.right += mBubbleTouchPadding;
+ outRect.bottom += mBubbleTouchPadding;
}
- // Increase the touch target size of the bubble
- outRect.top -= mBubbleTouchPadding;
- outRect.left -= mBubbleTouchPadding;
- outRect.right += mBubbleTouchPadding;
- outRect.bottom += mBubbleTouchPadding;
} else {
mBubbleContainer.getBoundsOnScreen(outRect);
}