diff options
| author | 2024-10-16 11:39:57 -0700 | |
|---|---|---|
| committer | 2024-10-28 16:31:17 -0700 | |
| commit | 8d64d4547cb6cf9ce04dc4030188e5652f5fa2eb (patch) | |
| tree | 7846add147bedbdbffdd2d9a7b185ba246155009 | |
| parent | b34540fef192e6af4fde487bca3d3173ee424ac9 (diff) | |
Log event when dragging exp view to dismiss
Bug: 349845968
Test: atest BubbleBarLayerViewTest
Flag: com.android.wm.shell.enable_bubble_bar
Change-Id: I4fcb68bd634e1efd7afc87744f8dc4131b3b4ad6
7 files changed, 387 insertions, 13 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt new file mode 100644 index 000000000000..cb6fb62bf89b --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt @@ -0,0 +1,57 @@ +/* + * 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 android.content.Context +import android.content.pm.ShortcutInfo +import android.content.res.Resources +import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView +import com.google.common.util.concurrent.MoreExecutors.directExecutor + +/** Helper to create a [Bubble] instance */ +class FakeBubbleFactory { + + companion object { + + fun createViewInfo(bubbleExpandedView: BubbleBarExpandedView): BubbleViewInfo { + return BubbleViewInfo().apply { bubbleBarExpandedView = bubbleExpandedView } + } + + fun createChatBubbleWithViewInfo( + context: Context, + key: String = "key", + viewInfo: BubbleViewInfo, + ): Bubble { + val bubble = + Bubble( + key, + ShortcutInfo.Builder(context, "id").build(), + 100, /* desiredHeight */ + Resources.ID_NULL, /* desiredHeightResId */ + "title", + 0, /* taskId */ + null, /* locus */ + true, /* isDismissable */ + directExecutor(), + directExecutor(), + ) {} + bubble.setViewInfo(viewInfo) + return bubble + } + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt new file mode 100644 index 000000000000..2fbf089d99d6 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -0,0 +1,293 @@ +/* + * 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.bar + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.LauncherApps +import android.os.Handler +import android.os.UserManager +import android.view.IWindowManager +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.protolog.ProtoLog +import com.android.internal.statusbar.IStatusBarService +import com.android.wm.shell.R +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.WindowManagerShellWrapper +import com.android.wm.shell.bubbles.Bubble +import com.android.wm.shell.bubbles.BubbleController +import com.android.wm.shell.bubbles.BubbleData +import com.android.wm.shell.bubbles.BubbleDataRepository +import com.android.wm.shell.bubbles.BubbleEducationController +import com.android.wm.shell.bubbles.BubbleExpandedViewManager +import com.android.wm.shell.bubbles.BubbleLogger +import com.android.wm.shell.bubbles.BubblePositioner +import com.android.wm.shell.bubbles.BubbleTaskView +import com.android.wm.shell.bubbles.BubbleTaskViewFactory +import com.android.wm.shell.bubbles.Bubbles.SysuiProxy +import com.android.wm.shell.bubbles.FakeBubbleFactory +import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat +import com.android.wm.shell.bubbles.properties.BubbleProperties +import com.android.wm.shell.bubbles.storage.BubblePersistentRepository +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayInsetsController +import com.android.wm.shell.common.FloatingContentCoordinator +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.shared.TransactionPool +import com.android.wm.shell.shared.bubbles.BubbleBarLocation +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellController +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.taskview.TaskView +import com.android.wm.shell.taskview.TaskViewTaskController +import com.android.wm.shell.taskview.TaskViewTransitions +import com.android.wm.shell.transition.Transitions +import com.google.common.truth.Truth.assertThat +import java.util.Collections +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** Tests for [BubbleBarLayerView] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleBarLayerViewTest { + + private val context = ApplicationProvider.getApplicationContext<Context>() + + private lateinit var bubbleBarLayerView: BubbleBarLayerView + + private lateinit var uiEventLoggerFake: UiEventLoggerFake + + private lateinit var bubble: Bubble + + @Before + fun setUp() { + ProtoLog.REQUIRE_PROTOLOGTOOL = false + ProtoLog.init() + + uiEventLoggerFake = UiEventLoggerFake() + val bubbleLogger = BubbleLogger(uiEventLoggerFake) + + val mainExecutor = TestExecutor() + val bgExecutor = TestExecutor() + + val windowManager = context.getSystemService(WindowManager::class.java) + + val bubblePositioner = BubblePositioner(context, windowManager) + bubblePositioner.setShowingInBubbleBar(true) + + val bubbleData = + BubbleData( + context, + bubbleLogger, + bubblePositioner, + BubbleEducationController(context), + mainExecutor, + bgExecutor, + ) + + val bubbleController = + createBubbleController( + bubbleData, + windowManager, + bubbleLogger, + bubblePositioner, + mainExecutor, + bgExecutor, + ) + bubbleController.asBubbles().setSysuiProxy(mock(SysuiProxy::class.java)) + // Flush so that proxy gets set + mainExecutor.flushAll() + + bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger) + + val expandedViewManager = createExpandedViewManager() + val bubbleTaskView = FakeBubbleTaskViewFactory(mainExecutor).create() + val bubbleBarExpandedView = + (LayoutInflater.from(context) + .inflate(R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */) + as BubbleBarExpandedView) + .apply { + initialize( + expandedViewManager, + bubblePositioner, + bubbleLogger, + false /* isOverflow */, + bubbleTaskView, + mainExecutor, + bgExecutor, + null, /* regionSamplingProvider */ + ) + } + + val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView) + bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo) + } + + private fun createBubbleController( + bubbleData: BubbleData, + windowManager: WindowManager?, + bubbleLogger: BubbleLogger, + bubblePositioner: BubblePositioner, + mainExecutor: TestExecutor, + bgExecutor: TestExecutor, + ): BubbleController { + val shellInit = ShellInit(mainExecutor) + val shellCommandHandler = ShellCommandHandler() + val shellController = + ShellController( + context, + shellInit, + shellCommandHandler, + mock<DisplayInsetsController>(), + mainExecutor, + ) + val surfaceSynchronizer = { obj: Runnable -> obj.run() } + + val bubbleDataRepository = + BubbleDataRepository( + mock<LauncherApps>(), + mainExecutor, + bgExecutor, + BubblePersistentRepository(context), + ) + + return BubbleController( + context, + shellInit, + shellCommandHandler, + shellController, + bubbleData, + surfaceSynchronizer, + FloatingContentCoordinator(), + bubbleDataRepository, + mock<IStatusBarService>(), + windowManager, + WindowManagerShellWrapper(mainExecutor), + mock<UserManager>(), + mock<LauncherApps>(), + bubbleLogger, + mock<TaskStackListenerImpl>(), + mock<ShellTaskOrganizer>(), + bubblePositioner, + mock<DisplayController>(), + null, + null, + mainExecutor, + mock<Handler>(), + bgExecutor, + mock<TaskViewTransitions>(), + mock<Transitions>(), + SyncTransactionQueue(TransactionPool(), mainExecutor), + mock<IWindowManager>(), + mock<BubbleProperties>(), + ) + } + + @Test + fun testEventLogging_dismissExpandedViewViaDrag() { + getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) } + assertThat(bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)).isNotNull() + + bubbleBarLayerView.dragController?.dragListener?.onReleased(true) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) : + BubbleTaskViewFactory { + override fun create(): BubbleTaskView { + val taskViewTaskController = mock<TaskViewTaskController>() + val taskView = TaskView(context, taskViewTaskController) + val taskInfo = mock<ActivityManager.RunningTaskInfo>() + whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) + return BubbleTaskView(taskView, mainExecutor) + } + } + + private fun createExpandedViewManager(): BubbleExpandedViewManager { + return object : BubbleExpandedViewManager { + override val overflowBubbles: List<Bubble> + get() = Collections.emptyList() + + override fun setOverflowListener(listener: BubbleData.Listener) {} + + override fun collapseStack() {} + + override fun updateWindowFlagsForBackpress(intercept: Boolean) {} + + override fun promoteBubbleFromOverflow(bubble: Bubble) {} + + override fun removeBubble(key: String, reason: Int) {} + + override fun dismissBubble(bubble: Bubble, reason: Int) {} + + override fun setAppBubbleTaskId(key: String, taskId: Int) {} + + override fun isStackExpanded(): Boolean { + return true + } + + override fun isShowingAsBubbleBar(): Boolean { + return true + } + + override fun hideCurrentInputMethod() {} + + override fun updateBubbleBarLocation(location: BubbleBarLocation) {} + } + } + + private class TestExecutor : ShellExecutor { + + private val runnables: MutableList<Runnable> = mutableListOf() + + override fun execute(runnable: Runnable) { + runnables.add(runnable) + } + + override fun executeDelayed(runnable: Runnable, delayMillis: Long) { + execute(runnable) + } + + override fun removeCallbacks(runnable: Runnable?) {} + + override fun hasCallback(runnable: Runnable?): Boolean = false + + fun flushAll() { + while (runnables.isNotEmpty()) { + runnables.removeAt(0).run() + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 37e8ead4fc78..3dec977c91e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -833,7 +833,7 @@ public class BubbleController implements ConfigurationChangeListener, // window to show this in, but we use a separate code path. // TODO(b/273312602): consider foldables where we do need a stack view when folded if (mLayerView == null) { - mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData); + mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData, mLogger); mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java index 6d757d26a9bd..cb73ca95e4a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java @@ -165,8 +165,14 @@ public class BubbleLogger { } /** - * @param b Bubble involved in this UI event - * @param e UI event + * Log an UIEvent + */ + public void log(UiEventLogger.UiEventEnum e) { + mUiEventLogger.log(e); + } + + /** + * Log an UIEvent with the given bubble info */ public void log(Bubble b, UiEventLogger.UiEventEnum e) { mUiEventLogger.logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index 07463bb024a2..34259bfb7aaa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar import android.annotation.SuppressLint import android.view.MotionEvent import android.view.View +import androidx.annotation.VisibleForTesting import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.shared.bubbles.DismissView import com.android.wm.shell.shared.bubbles.RelativeTouchListener @@ -32,7 +33,7 @@ class BubbleBarExpandedViewDragController( private val animationHelper: BubbleBarAnimationHelper, private val bubblePositioner: BubblePositioner, private val pinController: BubbleExpandedViewPinController, - private val dragListener: DragListener + @get:VisibleForTesting val dragListener: DragListener, ) { var isStuckToDismiss: Boolean = false @@ -107,7 +108,7 @@ class BubbleBarExpandedViewDragController( viewInitialX: Float, viewInitialY: Float, dx: Float, - dy: Float + dy: Float, ) { if (!isMoving) { isMoving = true @@ -127,7 +128,7 @@ class BubbleBarExpandedViewDragController( dx: Float, dy: Float, velX: Float, - velY: Float + velY: Float, ) { finishDrag() } @@ -152,7 +153,7 @@ class BubbleBarExpandedViewDragController( private inner class MagnetListener : MagnetizedObject.MagnetListener { override fun onStuckToTarget( target: MagnetizedObject.MagneticTarget, - draggedObject: MagnetizedObject<*> + draggedObject: MagnetizedObject<*>, ) { isStuckToDismiss = true pinController.onStuckToDismissTarget() @@ -163,7 +164,7 @@ class BubbleBarExpandedViewDragController( draggedObject: MagnetizedObject<*>, velX: Float, velY: Float, - wasFlungOut: Boolean + wasFlungOut: Boolean, ) { isStuckToDismiss = false animationHelper.animateUnstuckFromDismissView(target) @@ -171,7 +172,7 @@ class BubbleBarExpandedViewDragController( override fun onReleasedInTarget( target: MagnetizedObject.MagneticTarget, - draggedObject: MagnetizedObject<*> + draggedObject: MagnetizedObject<*>, ) { dragListener.onReleased(inDismiss = true) pinController.onDragEnd() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 1367b7e24bc7..402818c80b01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -34,10 +34,12 @@ import android.view.WindowManager; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; +import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; @@ -69,6 +71,7 @@ public class BubbleBarLayerView extends FrameLayout private final BubbleController mBubbleController; private final BubbleData mBubbleData; private final BubblePositioner mPositioner; + private final BubbleLogger mBubbleLogger; private final BubbleBarAnimationHelper mAnimationHelper; private final BubbleEducationViewController mEducationViewController; private final View mScrimView; @@ -93,11 +96,13 @@ public class BubbleBarLayerView extends FrameLayout private TouchDelegate mHandleTouchDelegate; private final Rect mHandleTouchBounds = new Rect(); - public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) { + public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData, + BubbleLogger bubbleLogger) { super(context); mBubbleController = controller; mBubbleData = bubbleData; mPositioner = mBubbleController.getPositioner(); + mBubbleLogger = bubbleLogger; mAnimationHelper = new BubbleBarAnimationHelper(context, this, mPositioner); @@ -233,6 +238,11 @@ public class BubbleBarLayerView extends FrameLayout DragListener dragListener = inDismiss -> { if (inDismiss && mExpandedBubble != null) { mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE); + if (mExpandedBubble instanceof Bubble) { + // Only a bubble can be dragged to dismiss + mBubbleLogger.log((Bubble) mExpandedBubble, + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); + } } }; mDragController = new BubbleBarExpandedViewDragController( @@ -413,4 +423,10 @@ public class BubbleBarLayerView extends FrameLayout } } + @Nullable + @VisibleForTesting + public BubbleBarExpandedViewDragController getDragController() { + return mDragController; + } + } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index 4ac066e4ffb0..1f2eaa6757e8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -99,10 +99,11 @@ class BubbleViewInfoTest : ShellTestCase() { val shellController = ShellController(context, shellInit, shellCommandHandler, mock<DisplayInsetsController>(), mainExecutor) bubblePositioner = BubblePositioner(context, windowManager) + val bubbleLogger = mock<BubbleLogger>() val bubbleData = BubbleData( context, - mock<BubbleLogger>(), + bubbleLogger, bubblePositioner, BubbleEducationController(context), mainExecutor, @@ -125,7 +126,7 @@ class BubbleViewInfoTest : ShellTestCase() { WindowManagerShellWrapper(mainExecutor), mock<UserManager>(), mock<LauncherApps>(), - mock<BubbleLogger>(), + bubbleLogger, mock<TaskStackListenerImpl>(), ShellTaskOrganizer(mainExecutor), bubblePositioner, @@ -154,7 +155,7 @@ class BubbleViewInfoTest : ShellTestCase() { bubbleController, mainExecutor ) - bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData) + bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger) } @Test |