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

Commit 936238b3 authored by Ats Jenk's avatar Ats Jenk Committed by Android (Google) Code Review
Browse files

Merge changes Id370a0e5,I4fcb68bd into main

* changes:
  Log event when dragging bubble bar to dismiss
  Log event when dragging exp view to dismiss
parents 705c4de6 188cba77
Loading
Loading
Loading
Loading
+57 −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 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
        }
    }
}
+293 −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.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()
            }
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -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 {
@@ -1759,6 +1759,9 @@ public class BubbleController implements ConfigurationChangeListener,
    @MainThread
    public void removeAllBubbles(@Bubbles.DismissReason int reason) {
        mBubbleData.dismissAll(reason);
        if (reason == Bubbles.DISMISS_USER_GESTURE) {
            mLogger.log(BubbleLogger.Event.BUBBLE_BAR_DISMISSED_DRAG_BAR);
        }
    }

    private void onEntryAdded(BubbleEntry entry) {
+8 −2
Original line number Diff line number Diff line
@@ -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());
+7 −6
Original line number Diff line number Diff line
@@ -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()
Loading