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

Commit 59b3e0bc authored by Mady Mellor's avatar Mady Mellor
Browse files

Use region sampling to properly tint the bubble expanded view handle

* Create a RegionSamplingHelper when the taskview is shown
* Sampling only occurs while the view is visible and not being
  dragged
* RegionSamplingHelper is destroyed when the task is removed or the
  view is detached
* Added tests for BubbleBarExpandedView verifying when the helper
  is started / stopped appropriately
* Don't animate the handle color if it's the same

Minor unrelated change:
* Apply the caption insets when the TaskView is created, not on
  every layout pass

Test: atest BubbleBarExpandedViewTest
Test: manual - open a bubble, check that the handle is the right
               color, do something in the bubble that would change
               the handle color (e.g. put maps in a bubble and open
               an image of a location, check that handle color changes
               appropriately)
             - repeat above after dragging the bubble to the other
               side
             - repeat above after expanding / collapsing the bubble
Flag: com.android.wm.shell.enable_bubble_bar
Bug: 353160491
Change-Id: Ib3db46c416ec040cdad656df9be1841aed4b18e3
parent e7d6f63e
Loading
Loading
Loading
Loading
+324 −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.graphics.Insets
import android.graphics.Rect
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.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleData
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
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.DeviceConfig
import com.android.wm.shell.bubbles.RegionSamplingProvider
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.util.Collections
import java.util.concurrent.Executor

/** Tests for [BubbleBarExpandedViewTest] */
@SmallTest
@RunWith(AndroidJUnit4::class)
class BubbleBarExpandedViewTest {
    companion object {
        const val SCREEN_WIDTH = 2000
        const val SCREEN_HEIGHT = 1000
    }

    private val context = ApplicationProvider.getApplicationContext<Context>()
    private val windowManager = context.getSystemService(WindowManager::class.java)

    private lateinit var mainExecutor: TestExecutor
    private lateinit var bgExecutor: TestExecutor

    private lateinit var expandedViewManager: BubbleExpandedViewManager
    private lateinit var positioner: BubblePositioner
    private lateinit var bubbleTaskView: BubbleTaskView

    private lateinit var bubbleExpandedView: BubbleBarExpandedView
    private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null
    private var regionSamplingProvider: TestRegionSamplingProvider? = null

    @Before
    fun setUp() {
        ProtoLog.REQUIRE_PROTOLOGTOOL = false
        mainExecutor = TestExecutor()
        bgExecutor = TestExecutor()
        positioner = BubblePositioner(context, windowManager)
        positioner.setShowingInBubbleBar(true)
        val deviceConfig =
            DeviceConfig(
                windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
                isLargeScreen = true,
                isSmallTablet = false,
                isLandscape = true,
                isRtl = false,
                insets = Insets.of(10, 20, 30, 40)
            )
        positioner.update(deviceConfig)

        expandedViewManager = createExpandedViewManager()
        bubbleTaskView = FakeBubbleTaskViewFactory().create()

        val inflater = LayoutInflater.from(context)

        regionSamplingProvider = TestRegionSamplingProvider()

        bubbleExpandedView = (inflater.inflate(
            R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
        ) as BubbleBarExpandedView)
        bubbleExpandedView.initialize(
            expandedViewManager,
            positioner,
            false /* isOverflow */,
            bubbleTaskView,
            mainExecutor,
            bgExecutor,
            regionSamplingProvider
        )

        getInstrumentation().runOnMainSync(Runnable {
            bubbleExpandedView.onAttachedToWindow()
            // Helper should be created once attached to window
            testableRegionSamplingHelper = regionSamplingProvider!!.helper
        })
    }

    @After
    fun tearDown() {
        testableRegionSamplingHelper?.stopAndDestroy()
    }

    @Test
    fun testCreateSamplingHelper_onAttach() {
        assertThat(testableRegionSamplingHelper).isNotNull()
    }

    @Test
    fun testDestroySamplingHelper_onDetach() {
        bubbleExpandedView.onDetachedFromWindow()
        assertThat(testableRegionSamplingHelper!!.isDestroyed).isTrue()
    }

    @Test
    fun testStopSampling_onDragStart() {
        bubbleExpandedView.setContentVisibility(true)
        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()

        bubbleExpandedView.setDragging(true)
        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
    }

    @Test
    fun testStartSampling_onDragEnd() {
        bubbleExpandedView.setDragging(true)
        bubbleExpandedView.setContentVisibility(true)
        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()

        bubbleExpandedView.setDragging(false)
        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
    }

    @Test
    fun testStartSampling_onContentVisible() {
        bubbleExpandedView.setContentVisibility(true)
        assertThat(testableRegionSamplingHelper!!.setWindowVisible).isTrue()
        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
    }

    @Test
    fun testStopSampling_onContentInvisible() {
        bubbleExpandedView.setContentVisibility(false)

        assertThat(testableRegionSamplingHelper!!.setWindowInvisible).isTrue()
        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
    }

    @Test
    fun testSampling_startStopAnimating_visible() {
        bubbleExpandedView.isAnimating = true
        bubbleExpandedView.setContentVisibility(true)
        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()

        bubbleExpandedView.isAnimating = false
        assertThat(testableRegionSamplingHelper!!.isStarted).isTrue()
    }

    @Test
    fun testSampling_startStopAnimating_invisible() {
        bubbleExpandedView.isAnimating = true
        bubbleExpandedView.setContentVisibility(false)
        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
        testableRegionSamplingHelper!!.reset()

        bubbleExpandedView.isAnimating = false
        assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
    }

    private inner class FakeBubbleTaskViewFactory : 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 inner class TestRegionSamplingProvider : RegionSamplingProvider {

        lateinit var helper: TestableRegionSamplingHelper

        override fun createHelper(
            sampledView: View?,
            callback: RegionSamplingHelper.SamplingCallback?,
            backgroundExecutor: Executor?,
            mainExecutor: Executor?
        ): RegionSamplingHelper {
            helper = TestableRegionSamplingHelper(sampledView, callback, backgroundExecutor,
                mainExecutor)
            return helper
        }
    }

    private inner class TestableRegionSamplingHelper(
        sampledView: View?,
        samplingCallback: SamplingCallback?,
        backgroundExecutor: Executor?,
        mainExecutor: Executor?
    ) : RegionSamplingHelper(sampledView, samplingCallback, backgroundExecutor, mainExecutor) {

        var isStarted = false
        var isStopped = false
        var isDestroyed = false
        var setWindowVisible = false
        var setWindowInvisible = false

        override fun start(initialSamplingBounds: Rect) {
            super.start(initialSamplingBounds)
            isStarted = true
        }

        override fun stop() {
            super.stop()
            isStopped = true
        }

        override fun stopAndDestroy() {
            super.stopAndDestroy()
            isDestroyed = true
        }

        override fun setWindowVisible(visible: Boolean) {
            super.setWindowVisible(visible)
            if (visible) {
                setWindowVisible = true
            } else {
                setWindowInvisible = true
            }
        }

        fun reset() {
            isStarted = false
            isStopped = false
            isDestroyed = false
            setWindowVisible = false
            setWindowInvisible = false
        }
    }

    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
    }
}
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -329,7 +329,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
        /**
         * Get the sampled region of interest from the sampled view
         * @param sampledView The view that this helper is attached to for convenience
         * @return the region to be sampled in sceen coordinates. Return {@code null} to avoid
         * @return the region to be sampled in screen coordinates. Return {@code null} to avoid
         * sampling in this frame
         */
        Rect getSampledRegion(View sampledView);
+2 −1
Original line number Diff line number Diff line
@@ -609,7 +609,8 @@ public class Bubble implements BubbleViewProvider {
                            callback.onBubbleViewsReady(bubble);
                        }
                    },
                    mMainExecutor);
                    mMainExecutor,
                    mBgExecutor);
            if (mInflateSynchronously) {
                mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground());
            } else {
+4 −1
Original line number Diff line number Diff line
@@ -80,7 +80,10 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
                expandedViewManager,
                positioner,
                /* isOverflow= */ true,
                /* bubbleTaskView= */ null
                /* bubbleTaskView= */ null,
                /* mainExecutor= */ null,
                /* backgroundExecutor= */ null,
                /* regionSamplingProvider= */ null
            )
    }

+4 −0
Original line number Diff line number Diff line
@@ -60,6 +60,9 @@ public class BubbleTaskViewHelper {

        /** Called when back is pressed on the task root. */
        void onBackPressed();

        /** Called when task removal has started. */
        void onTaskRemovalStarted();
    }

    private final Context mContext;
@@ -190,6 +193,7 @@ public class BubbleTaskViewHelper {
                ((ViewGroup) mParentView).removeView(mTaskView);
                mTaskView = null;
            }
            mListener.onTaskRemovalStarted();
        }

        @Override
Loading