Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +18 −14 Original line number Diff line number Diff line Loading @@ -173,7 +173,7 @@ public class BubbleController implements ConfigurationChangeListener, private final Context mContext; private final BubblesImpl mImpl = new BubblesImpl(); private Bubbles.BubbleExpandListener mExpandListener; @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private final WindowManagerShellWrapper mWindowManagerShellWrapper; Loading @@ -197,12 +197,12 @@ public class BubbleController implements ConfigurationChangeListener, private final Handler mMainHandler; private final ShellExecutor mBackgroundExecutor; private BubbleLogger mLogger; private BubbleData mBubbleData; private final BubbleLogger mLogger; private final BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @Nullable private BubbleBarLayerView mLayerView; private BubbleIconFactory mBubbleIconFactory; private BubblePositioner mBubblePositioner; private final BubblePositioner mBubblePositioner; private Bubbles.SysuiProxy mSysuiProxy; // Tracks the id of the current (foreground) user. Loading Loading @@ -232,13 +232,17 @@ public class BubbleController implements ConfigurationChangeListener, /** Whether or not the BubbleStackView has been added to the WindowManager. */ private boolean mAddedToWindowManager = false; /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */ /** * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}. */ private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED; /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/ private Rect mScreenBounds = new Rect(); /** * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}. */ private final Rect mScreenBounds = new Rect(); /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */ private float mFontScale = 0; /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */ Loading @@ -253,9 +257,9 @@ public class BubbleController implements ConfigurationChangeListener, private boolean mIsStatusBarShade = true; /** One handed mode controller to register transition listener. */ private Optional<OneHandedController> mOneHandedOptional; private final Optional<OneHandedController> mOneHandedOptional; /** Drag and drop controller to register listener for onDragStarted. */ private Optional<DragAndDropController> mDragAndDropController; private final Optional<DragAndDropController> mDragAndDropController; /** Used to send bubble events to launcher. */ private Bubbles.BubbleStateListener mBubbleStateListener; Loading Loading @@ -731,9 +735,11 @@ public class BubbleController implements ConfigurationChangeListener, } } else { if (mStackView == null) { BubbleStackViewManager bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(this); mStackView = new BubbleStackView( mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor); mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor); mStackView.onOrientationChanged(); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); Loading Loading @@ -893,7 +899,6 @@ public class BubbleController implements ConfigurationChangeListener, * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been * added in the meantime. */ @VisibleForTesting public void onAllBubblesAnimatedOut() { if (mStackView != null) { mStackView.setVisibility(INVISIBLE); Loading Loading @@ -1047,7 +1052,6 @@ public class BubbleController implements ConfigurationChangeListener, return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow(); } @VisibleForTesting public boolean isStackExpanded() { return mBubbleData.isExpanded(); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +16 −10 Original line number Diff line number Diff line Loading @@ -204,7 +204,7 @@ public class BubbleStackView extends FrameLayout Choreographer.getInstance().postFrameCallback(frameCallback); } }; private final BubbleController mBubbleController; private final BubbleStackViewManager mManager; private final BubbleData mBubbleData; private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider; private StackViewState mStackViewState = new StackViewState(); Loading Loading @@ -858,6 +858,7 @@ public class BubbleStackView extends FrameLayout private BubbleOverflow mBubbleOverflow; private StackEducationView mStackEduView; private StackEducationView.Manager mStackEducationViewManager; private ManageEducationView mManageEduView; private DismissView mDismissView; Loading @@ -873,15 +874,16 @@ public class BubbleStackView extends FrameLayout private BubblePositioner mPositioner; @SuppressLint("ClickableViewAccessibility") public BubbleStackView(Context context, BubbleController bubbleController, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager, BubblePositioner bubblePositioner, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, Bubbles.SysuiProxy.Provider sysuiProxyProvider, ShellExecutor mainExecutor) { super(context); mMainExecutor = mainExecutor; mBubbleController = bubbleController; mManager = bubbleStackViewManager; mBubbleData = data; mSysuiProxyProvider = sysuiProxyProvider; Loading @@ -893,7 +895,7 @@ public class BubbleStackView extends FrameLayout mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mPositioner = mBubbleController.getPositioner(); mPositioner = bubblePositioner; final TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.dialogCornerRadius}); Loading @@ -903,7 +905,7 @@ public class BubbleStackView extends FrameLayout final Runnable onBubbleAnimatedOut = () -> { if (getBubbleCount() == 0) { mExpandedViewTemporarilyHidden = false; mBubbleController.onAllBubblesAnimatedOut(); mManager.onAllBubblesAnimatedOut(); } }; mStackAnimationController = new StackAnimationController( Loading Loading @@ -1383,7 +1385,9 @@ public class BubbleStackView extends FrameLayout return false; } if (mStackEduView == null) { mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); mStackEducationViewManager = mManager::updateWindowFlagsForBackpress; mStackEduView = new StackEducationView(mContext, mPositioner, mStackEducationViewManager); addView(mStackEduView); } return showStackEdu(); Loading Loading @@ -1412,7 +1416,9 @@ public class BubbleStackView extends FrameLayout private void updateUserEdu() { if (isStackEduVisible() && !mStackEduView.isHiding()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); mStackEducationViewManager = mManager::updateWindowFlagsForBackpress; mStackEduView = new StackEducationView(mContext, mPositioner, mStackEducationViewManager); addView(mStackEduView); showStackEdu(); } Loading Loading @@ -2106,7 +2112,7 @@ public class BubbleStackView extends FrameLayout logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> { mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> { if (!notifPanelExpanded && mIsExpanded) { startMonitoringSwipeUpGesture(); } Loading Loading @@ -2227,7 +2233,7 @@ public class BubbleStackView extends FrameLayout */ void hideCurrentInputMethod() { mPositioner.setImeVisible(false, 0); mBubbleController.hideCurrentInputMethod(); mManager.hideCurrentInputMethod(); } /** Set the stack position to whatever the positioner says. */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.bubbles import java.util.function.Consumer /** Defines callbacks from [BubbleStackView] to its manager. */ interface BubbleStackViewManager { /** Notifies that all bubbles animated out. */ fun onAllBubblesAnimatedOut() /** Notifies whether backpress should be intercepted. */ fun updateWindowFlagsForBackpress(interceptBack: Boolean) /** * Checks the current expansion state of the notification panel, and invokes [callback] with the * result. */ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) /** Requests to hide the current input method. */ fun hideCurrentInputMethod() companion object { @JvmStatic fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager { override fun onAllBubblesAnimatedOut() { controller.onAllBubblesAnimatedOut() } override fun updateWindowFlagsForBackpress(interceptBack: Boolean) { controller.updateWindowFlagsForBackpress(interceptBack) } override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) { controller.isNotificationPanelExpanded(callback) } override fun hideCurrentInputMethod() { controller.hideCurrentInputMethod() } } } } libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +10 −4 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ import com.android.wm.shell.animation.Interpolators class StackEducationView( context: Context, private val positioner: BubblePositioner, private val controller: BubbleController private val manager: Manager ) : LinearLayout(context) { companion object { Loading @@ -44,6 +44,12 @@ class StackEducationView( private const val ANIMATE_DURATION_SHORT: Long = 40 } /** Callbacks to notify managers of [StackEducationView] about events. */ interface Manager { /** Notifies whether backpress should be intercepted. */ fun updateWindowFlagsForBackpress(interceptBack: Boolean) } private val view by lazy { requireViewById<View>(R.id.stack_education_layout) } private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) } private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) } Loading Loading @@ -93,7 +99,7 @@ class StackEducationView( override fun onDetachedFromWindow() { super.onDetachedFromWindow() setOnKeyListener(null) controller.updateWindowFlagsForBackpress(false /* interceptBack */) manager.updateWindowFlagsForBackpress(false /* interceptBack */) } private fun setTextColor() { Loading Loading @@ -124,7 +130,7 @@ class StackEducationView( isHiding = false if (visibility == VISIBLE) return false controller.updateWindowFlagsForBackpress(true /* interceptBack */) manager.updateWindowFlagsForBackpress(true /* interceptBack */) layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape) context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width) Loading Loading @@ -185,7 +191,7 @@ class StackEducationView( if (visibility != VISIBLE || isHiding) return isHiding = true controller.updateWindowFlagsForBackpress(false /* interceptBack */) manager.updateWindowFlagsForBackpress(false /* interceptBack */) animate() .alpha(0f) .setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION) Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +73 −28 Original line number Diff line number Diff line Loading @@ -56,9 +56,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever /** * Tests for loading / inflating views & icons for a bubble. */ /** Tests for loading / inflating views & icons for a bubble. */ @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) Loading @@ -76,25 +74,33 @@ class BubbleViewInfoTest : ShellTestCase() { @Before fun setup() { metadataFlagListener = Bubbles.BubbleMetadataFlagListener {} iconFactory = BubbleIconFactory(context, iconFactory = BubbleIconFactory( context, 60, 30, Color.RED, mContext.resources.getDimensionPixelSize( R.dimen.importance_ring_stroke_width)) mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width) ) mainExecutor = TestShellExecutor() val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) val bubblePositioner = BubblePositioner(context, windowManager) val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner, BubbleEducationController(context), mainExecutor) val bubbleData = BubbleData( context, mock<BubbleLogger>(), bubblePositioner, BubbleEducationController(context), mainExecutor ) val surfaceSynchronizer = { obj: Runnable -> obj.run() } bubbleController = BubbleController( bubbleController = BubbleController( context, shellInit, shellCommandHandler, Loading Loading @@ -122,18 +128,36 @@ class BubbleViewInfoTest : ShellTestCase() { mock<Transitions>(), mock<SyncTransactionQueue>(), mock<IWindowManager>(), mock<BubbleProperties>()) mock<BubbleProperties>() ) bubbleStackView = BubbleStackView(context, bubbleController, bubbleData, surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor) val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController) bubbleStackView = BubbleStackView( context, bubbleStackViewManager, bubblePositioner, bubbleData, surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor ) bubbleBarLayerView = BubbleBarLayerView(context, bubbleController) } @Test fun testPopulate() { bubble = createBubbleWithShortcut() val info = BubbleViewInfoTask.BubbleViewInfo.populate(context, bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */) val info = BubbleViewInfoTask.BubbleViewInfo.populate( context, bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */ ) assertThat(info!!).isNotNull() assertThat(info.imageView).isNotNull() Loading @@ -151,9 +175,15 @@ class BubbleViewInfoTest : ShellTestCase() { @Test fun testPopulateForBubbleBar() { bubble = createBubbleWithShortcut() val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context, bubbleController, bubbleBarLayerView, iconFactory, bubble, false /* skipInflation */) val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar( context, bubbleController, bubbleBarLayerView, iconFactory, bubble, false /* skipInflation */ ) assertThat(info!!).isNotNull() assertThat(info.imageView).isNull() Loading @@ -176,12 +206,18 @@ class BubbleViewInfoTest : ShellTestCase() { // exception here if the app has an issue loading the shortcut icon; we default to // the app icon in that case / none of the icons will be null. val mockIconFactory = mock<BubbleIconFactory>() whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any())).doThrow(RuntimeException()) whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any())) .doThrow(RuntimeException()) val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context, bubbleController, bubbleBarLayerView, iconFactory, bubble, true /* skipInflation */) val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar( context, bubbleController, bubbleBarLayerView, iconFactory, bubble, true /* skipInflation */ ) assertThat(info).isNotNull() assertThat(info?.shortcutInfo).isNotNull() Loading @@ -194,8 +230,17 @@ class BubbleViewInfoTest : ShellTestCase() { private fun createBubbleWithShortcut(): Bubble { val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build() return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL, "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, mainExecutor, metadataFlagListener) return Bubble( "mockKey", shortcutInfo, 1000, Resources.ID_NULL, "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, mainExecutor, metadataFlagListener ) } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +18 −14 Original line number Diff line number Diff line Loading @@ -173,7 +173,7 @@ public class BubbleController implements ConfigurationChangeListener, private final Context mContext; private final BubblesImpl mImpl = new BubblesImpl(); private Bubbles.BubbleExpandListener mExpandListener; @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private final WindowManagerShellWrapper mWindowManagerShellWrapper; Loading @@ -197,12 +197,12 @@ public class BubbleController implements ConfigurationChangeListener, private final Handler mMainHandler; private final ShellExecutor mBackgroundExecutor; private BubbleLogger mLogger; private BubbleData mBubbleData; private final BubbleLogger mLogger; private final BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @Nullable private BubbleBarLayerView mLayerView; private BubbleIconFactory mBubbleIconFactory; private BubblePositioner mBubblePositioner; private final BubblePositioner mBubblePositioner; private Bubbles.SysuiProxy mSysuiProxy; // Tracks the id of the current (foreground) user. Loading Loading @@ -232,13 +232,17 @@ public class BubbleController implements ConfigurationChangeListener, /** Whether or not the BubbleStackView has been added to the WindowManager. */ private boolean mAddedToWindowManager = false; /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */ /** * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}. */ private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED; /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/ private Rect mScreenBounds = new Rect(); /** * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}. */ private final Rect mScreenBounds = new Rect(); /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */ private float mFontScale = 0; /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */ Loading @@ -253,9 +257,9 @@ public class BubbleController implements ConfigurationChangeListener, private boolean mIsStatusBarShade = true; /** One handed mode controller to register transition listener. */ private Optional<OneHandedController> mOneHandedOptional; private final Optional<OneHandedController> mOneHandedOptional; /** Drag and drop controller to register listener for onDragStarted. */ private Optional<DragAndDropController> mDragAndDropController; private final Optional<DragAndDropController> mDragAndDropController; /** Used to send bubble events to launcher. */ private Bubbles.BubbleStateListener mBubbleStateListener; Loading Loading @@ -731,9 +735,11 @@ public class BubbleController implements ConfigurationChangeListener, } } else { if (mStackView == null) { BubbleStackViewManager bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(this); mStackView = new BubbleStackView( mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor); mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor); mStackView.onOrientationChanged(); if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); Loading Loading @@ -893,7 +899,6 @@ public class BubbleController implements ConfigurationChangeListener, * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been * added in the meantime. */ @VisibleForTesting public void onAllBubblesAnimatedOut() { if (mStackView != null) { mStackView.setVisibility(INVISIBLE); Loading Loading @@ -1047,7 +1052,6 @@ public class BubbleController implements ConfigurationChangeListener, return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow(); } @VisibleForTesting public boolean isStackExpanded() { return mBubbleData.isExpanded(); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +16 −10 Original line number Diff line number Diff line Loading @@ -204,7 +204,7 @@ public class BubbleStackView extends FrameLayout Choreographer.getInstance().postFrameCallback(frameCallback); } }; private final BubbleController mBubbleController; private final BubbleStackViewManager mManager; private final BubbleData mBubbleData; private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider; private StackViewState mStackViewState = new StackViewState(); Loading Loading @@ -858,6 +858,7 @@ public class BubbleStackView extends FrameLayout private BubbleOverflow mBubbleOverflow; private StackEducationView mStackEduView; private StackEducationView.Manager mStackEducationViewManager; private ManageEducationView mManageEduView; private DismissView mDismissView; Loading @@ -873,15 +874,16 @@ public class BubbleStackView extends FrameLayout private BubblePositioner mPositioner; @SuppressLint("ClickableViewAccessibility") public BubbleStackView(Context context, BubbleController bubbleController, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager, BubblePositioner bubblePositioner, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, Bubbles.SysuiProxy.Provider sysuiProxyProvider, ShellExecutor mainExecutor) { super(context); mMainExecutor = mainExecutor; mBubbleController = bubbleController; mManager = bubbleStackViewManager; mBubbleData = data; mSysuiProxyProvider = sysuiProxyProvider; Loading @@ -893,7 +895,7 @@ public class BubbleStackView extends FrameLayout mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mPositioner = mBubbleController.getPositioner(); mPositioner = bubblePositioner; final TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.dialogCornerRadius}); Loading @@ -903,7 +905,7 @@ public class BubbleStackView extends FrameLayout final Runnable onBubbleAnimatedOut = () -> { if (getBubbleCount() == 0) { mExpandedViewTemporarilyHidden = false; mBubbleController.onAllBubblesAnimatedOut(); mManager.onAllBubblesAnimatedOut(); } }; mStackAnimationController = new StackAnimationController( Loading Loading @@ -1383,7 +1385,9 @@ public class BubbleStackView extends FrameLayout return false; } if (mStackEduView == null) { mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); mStackEducationViewManager = mManager::updateWindowFlagsForBackpress; mStackEduView = new StackEducationView(mContext, mPositioner, mStackEducationViewManager); addView(mStackEduView); } return showStackEdu(); Loading Loading @@ -1412,7 +1416,9 @@ public class BubbleStackView extends FrameLayout private void updateUserEdu() { if (isStackEduVisible() && !mStackEduView.isHiding()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); mStackEducationViewManager = mManager::updateWindowFlagsForBackpress; mStackEduView = new StackEducationView(mContext, mPositioner, mStackEducationViewManager); addView(mStackEduView); showStackEdu(); } Loading Loading @@ -2106,7 +2112,7 @@ public class BubbleStackView extends FrameLayout logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> { mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> { if (!notifPanelExpanded && mIsExpanded) { startMonitoringSwipeUpGesture(); } Loading Loading @@ -2227,7 +2233,7 @@ public class BubbleStackView extends FrameLayout */ void hideCurrentInputMethod() { mPositioner.setImeVisible(false, 0); mBubbleController.hideCurrentInputMethod(); mManager.hideCurrentInputMethod(); } /** Set the stack position to whatever the positioner says. */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.bubbles import java.util.function.Consumer /** Defines callbacks from [BubbleStackView] to its manager. */ interface BubbleStackViewManager { /** Notifies that all bubbles animated out. */ fun onAllBubblesAnimatedOut() /** Notifies whether backpress should be intercepted. */ fun updateWindowFlagsForBackpress(interceptBack: Boolean) /** * Checks the current expansion state of the notification panel, and invokes [callback] with the * result. */ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) /** Requests to hide the current input method. */ fun hideCurrentInputMethod() companion object { @JvmStatic fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager { override fun onAllBubblesAnimatedOut() { controller.onAllBubblesAnimatedOut() } override fun updateWindowFlagsForBackpress(interceptBack: Boolean) { controller.updateWindowFlagsForBackpress(interceptBack) } override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) { controller.isNotificationPanelExpanded(callback) } override fun hideCurrentInputMethod() { controller.hideCurrentInputMethod() } } } }
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +10 −4 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ import com.android.wm.shell.animation.Interpolators class StackEducationView( context: Context, private val positioner: BubblePositioner, private val controller: BubbleController private val manager: Manager ) : LinearLayout(context) { companion object { Loading @@ -44,6 +44,12 @@ class StackEducationView( private const val ANIMATE_DURATION_SHORT: Long = 40 } /** Callbacks to notify managers of [StackEducationView] about events. */ interface Manager { /** Notifies whether backpress should be intercepted. */ fun updateWindowFlagsForBackpress(interceptBack: Boolean) } private val view by lazy { requireViewById<View>(R.id.stack_education_layout) } private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) } private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) } Loading Loading @@ -93,7 +99,7 @@ class StackEducationView( override fun onDetachedFromWindow() { super.onDetachedFromWindow() setOnKeyListener(null) controller.updateWindowFlagsForBackpress(false /* interceptBack */) manager.updateWindowFlagsForBackpress(false /* interceptBack */) } private fun setTextColor() { Loading Loading @@ -124,7 +130,7 @@ class StackEducationView( isHiding = false if (visibility == VISIBLE) return false controller.updateWindowFlagsForBackpress(true /* interceptBack */) manager.updateWindowFlagsForBackpress(true /* interceptBack */) layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape) context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width) Loading Loading @@ -185,7 +191,7 @@ class StackEducationView( if (visibility != VISIBLE || isHiding) return isHiding = true controller.updateWindowFlagsForBackpress(false /* interceptBack */) manager.updateWindowFlagsForBackpress(false /* interceptBack */) animate() .alpha(0f) .setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION) Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +73 −28 Original line number Diff line number Diff line Loading @@ -56,9 +56,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever /** * Tests for loading / inflating views & icons for a bubble. */ /** Tests for loading / inflating views & icons for a bubble. */ @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) Loading @@ -76,25 +74,33 @@ class BubbleViewInfoTest : ShellTestCase() { @Before fun setup() { metadataFlagListener = Bubbles.BubbleMetadataFlagListener {} iconFactory = BubbleIconFactory(context, iconFactory = BubbleIconFactory( context, 60, 30, Color.RED, mContext.resources.getDimensionPixelSize( R.dimen.importance_ring_stroke_width)) mContext.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width) ) mainExecutor = TestShellExecutor() val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) val bubblePositioner = BubblePositioner(context, windowManager) val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner, BubbleEducationController(context), mainExecutor) val bubbleData = BubbleData( context, mock<BubbleLogger>(), bubblePositioner, BubbleEducationController(context), mainExecutor ) val surfaceSynchronizer = { obj: Runnable -> obj.run() } bubbleController = BubbleController( bubbleController = BubbleController( context, shellInit, shellCommandHandler, Loading Loading @@ -122,18 +128,36 @@ class BubbleViewInfoTest : ShellTestCase() { mock<Transitions>(), mock<SyncTransactionQueue>(), mock<IWindowManager>(), mock<BubbleProperties>()) mock<BubbleProperties>() ) bubbleStackView = BubbleStackView(context, bubbleController, bubbleData, surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor) val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController) bubbleStackView = BubbleStackView( context, bubbleStackViewManager, bubblePositioner, bubbleData, surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor ) bubbleBarLayerView = BubbleBarLayerView(context, bubbleController) } @Test fun testPopulate() { bubble = createBubbleWithShortcut() val info = BubbleViewInfoTask.BubbleViewInfo.populate(context, bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */) val info = BubbleViewInfoTask.BubbleViewInfo.populate( context, bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */ ) assertThat(info!!).isNotNull() assertThat(info.imageView).isNotNull() Loading @@ -151,9 +175,15 @@ class BubbleViewInfoTest : ShellTestCase() { @Test fun testPopulateForBubbleBar() { bubble = createBubbleWithShortcut() val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context, bubbleController, bubbleBarLayerView, iconFactory, bubble, false /* skipInflation */) val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar( context, bubbleController, bubbleBarLayerView, iconFactory, bubble, false /* skipInflation */ ) assertThat(info!!).isNotNull() assertThat(info.imageView).isNull() Loading @@ -176,12 +206,18 @@ class BubbleViewInfoTest : ShellTestCase() { // exception here if the app has an issue loading the shortcut icon; we default to // the app icon in that case / none of the icons will be null. val mockIconFactory = mock<BubbleIconFactory>() whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any())).doThrow(RuntimeException()) whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo), any())) .doThrow(RuntimeException()) val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context, bubbleController, bubbleBarLayerView, iconFactory, bubble, true /* skipInflation */) val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar( context, bubbleController, bubbleBarLayerView, iconFactory, bubble, true /* skipInflation */ ) assertThat(info).isNotNull() assertThat(info?.shortcutInfo).isNotNull() Loading @@ -194,8 +230,17 @@ class BubbleViewInfoTest : ShellTestCase() { private fun createBubbleWithShortcut(): Bubble { val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build() return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL, "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, mainExecutor, metadataFlagListener) return Bubble( "mockKey", shortcutInfo, 1000, Resources.ID_NULL, "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, mainExecutor, metadataFlagListener ) } }