summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt324
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java210
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java10
11 files changed, 576 insertions, 61 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
new file mode 100644
index 000000000000..35d459f27534
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -0,0 +1,324 @@
+/*
+ * 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
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
index b92b8ef657a3..a06cf78d0898 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
@@ -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);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 0c95934abf93..169361ad5f6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -609,7 +609,8 @@ public class Bubble implements BubbleViewProvider {
callback.onBubbleViewsReady(bubble);
}
},
- mMainExecutor);
+ mMainExecutor,
+ mBgExecutor);
if (mInflateSynchronously) {
mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground());
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index f32974e1765d..68c4657f2b68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -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
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5f8f0fd0c54c..0c0fd7b10f6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -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
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 13855f73fb4a..3982a237dd3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -38,6 +38,7 @@ import android.graphics.drawable.Icon;
import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
+import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
@@ -47,6 +48,7 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -222,7 +224,16 @@ public class BubbleViewInfoTask {
ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing bubble bar expanded view key=%s",
mBubble.getKey());
viewInfo.bubbleBarExpandedView.initialize(mExpandedViewManager.get(),
- mPositioner.get(), false /* isOverflow */, viewInfo.taskView);
+ mPositioner.get(), false /* isOverflow */, viewInfo.taskView,
+ mMainExecutor, mBgExecutor, new RegionSamplingProvider() {
+ @Override
+ public RegionSamplingHelper createHelper(View sampledView,
+ RegionSamplingHelper.SamplingCallback callback,
+ Executor backgroundExecutor, Executor mainExecutor) {
+ return RegionSamplingProvider.super.createHelper(sampledView,
+ callback, backgroundExecutor, mainExecutor);
+ }
+ });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
index 5cfebf8f1647..1b7bb0db6516 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
@@ -38,6 +38,7 @@ import android.os.AsyncTask;
import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
+import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
@@ -46,6 +47,7 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -85,6 +87,7 @@ public class BubbleViewInfoTaskLegacy extends
private boolean mSkipInflation;
private Callback mCallback;
private Executor mMainExecutor;
+ private Executor mBackgroundExecutor;
/**
* Creates a task to load information for the provided {@link Bubble}. Once all info
@@ -100,7 +103,8 @@ public class BubbleViewInfoTaskLegacy extends
BubbleIconFactory factory,
boolean skipInflation,
Callback c,
- Executor mainExecutor) {
+ Executor mainExecutor,
+ Executor backgroundExecutor) {
mBubble = b;
mContext = new WeakReference<>(context);
mExpandedViewManager = new WeakReference<>(expandedViewManager);
@@ -112,6 +116,7 @@ public class BubbleViewInfoTaskLegacy extends
mSkipInflation = skipInflation;
mCallback = c;
mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
}
@Override
@@ -123,7 +128,7 @@ public class BubbleViewInfoTaskLegacy extends
if (mLayerView.get() != null) {
return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
- mBubble, mSkipInflation);
+ mBubble, mSkipInflation, mMainExecutor, mBackgroundExecutor);
} else {
return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
@@ -188,7 +193,9 @@ public class BubbleViewInfoTaskLegacy extends
BubbleBarLayerView layerView,
BubbleIconFactory iconFactory,
Bubble b,
- boolean skipInflation) {
+ boolean skipInflation,
+ Executor mainExecutor,
+ Executor backgroundExecutor) {
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
@@ -197,7 +204,16 @@ public class BubbleViewInfoTaskLegacy extends
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
info.bubbleBarExpandedView.initialize(
- expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
+ expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView,
+ mainExecutor, backgroundExecutor, new RegionSamplingProvider() {
+ @Override
+ public RegionSamplingHelper createHelper(View sampledView,
+ RegionSamplingHelper.SamplingCallback callback,
+ Executor backgroundExecutor, Executor mainExecutor) {
+ return RegionSamplingProvider.super.createHelper(sampledView,
+ callback, backgroundExecutor, mainExecutor);
+ }
+ });
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java
new file mode 100644
index 000000000000..30f5c8fd56c3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RegionSamplingProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.view.View;
+
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper to provide a {@link com.android.wm.shell.shared.handles.RegionSamplingHelper} to allow
+ * testing it.
+ */
+public interface RegionSamplingProvider {
+
+ /** Creates and returns the region sampling helper */
+ default RegionSamplingHelper createHelper(View sampledView,
+ RegionSamplingHelper.SamplingCallback callback,
+ Executor backgroundExecutor,
+ Executor mainExecutor) {
+ return new RegionSamplingHelper(sampledView,
+ callback, backgroundExecutor, mainExecutor);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 565fde0a853c..74c3748dccaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -253,6 +253,7 @@ public class BubbleBarAnimationHelper {
return;
}
setDragPivot(bbev);
+ bbev.setDragging(true);
// Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE;
@@ -329,6 +330,7 @@ public class BubbleBarAnimationHelper {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
bbev.resetPivot();
+ bbev.setDragging(false);
}
});
startNewDragAnimation(animatorSet);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index f90b2aa95555..ec235a5d84ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -19,10 +19,7 @@ package com.android.wm.shell.bubbles.bar;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -37,6 +34,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
@@ -46,9 +44,12 @@ import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.bubbles.RegionSamplingProvider;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
import com.android.wm.shell.taskview.TaskView;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/** Expanded view of a bubble when it's part of the bubble bar. */
@@ -92,16 +93,35 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
private boolean mIsOverflow;
private BubbleTaskViewHelper mBubbleTaskViewHelper;
private BubbleBarMenuViewController mMenuViewController;
- private @Nullable Supplier<Rect> mLayerBoundsSupplier;
- private @Nullable Listener mListener;
+ @Nullable
+ private Supplier<Rect> mLayerBoundsSupplier;
+ @Nullable
+ private Listener mListener;
private BubbleBarHandleView mHandleView;
- private @Nullable TaskView mTaskView;
- private @Nullable BubbleOverflowContainerView mOverflowView;
+ @Nullable
+ private TaskView mTaskView;
+ @Nullable
+ private BubbleOverflowContainerView mOverflowView;
+ /**
+ * The handle shown in the caption area is tinted based on the background color of the area.
+ * This can vary so we sample the caption region and update the handle color based on that.
+ * If we're showing the overflow, the helper and executors will be null.
+ */
+ @Nullable
+ private RegionSamplingHelper mRegionSamplingHelper;
+ @Nullable
+ private RegionSamplingProvider mRegionSamplingProvider;
+ @Nullable
+ private Executor mMainExecutor;
+ @Nullable
+ private Executor mBackgroundExecutor;
+ private final Rect mSampleRect = new Rect();
+ private final int[] mLoc = new int[2];
+
+ /** Height of the caption inset at the top of the TaskView */
private int mCaptionHeight;
-
- private int mBackgroundColor;
/** Corner radius used when view is resting */
private float mRestingCornerRadius = 0f;
/** Corner radius applied while dragging */
@@ -116,6 +136,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
*/
private boolean mIsContentVisible = false;
private boolean mIsAnimating;
+ private boolean mIsDragging;
public BubbleBarExpandedView(Context context) {
this(context, null);
@@ -154,21 +175,20 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
setOnTouchListener((v, event) -> true);
}
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- // Hide manage menu when view disappears
- mMenuViewController.hideMenu(false /* animated */);
- }
-
/** Initializes the view, must be called before doing anything else. */
public void initialize(BubbleExpandedViewManager expandedViewManager,
BubblePositioner positioner,
boolean isOverflow,
- @Nullable BubbleTaskView bubbleTaskView) {
+ @Nullable BubbleTaskView bubbleTaskView,
+ @Nullable Executor mainExecutor,
+ @Nullable Executor backgroundExecutor,
+ @Nullable RegionSamplingProvider regionSamplingProvider) {
mManager = expandedViewManager;
mPositioner = positioner;
mIsOverflow = isOverflow;
+ mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
+ mRegionSamplingProvider = regionSamplingProvider;
if (mIsOverflow) {
mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
@@ -191,6 +211,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mTaskView.setEnableSurfaceClipping(true);
mTaskView.setCornerRadius(mCurrentCornerRadius);
mTaskView.setVisibility(VISIBLE);
+ mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
// Handle view needs to draw on top of task view.
bringChildToFront(mHandleView);
@@ -245,32 +266,40 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
return mHandleView;
}
- // TODO (b/275087636): call this when theme/config changes
/** Updates the view based on the current theme. */
public void applyThemeAttrs() {
+ mCaptionHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
mRestingCornerRadius = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_corner_radius
- );
+ R.dimen.bubble_bar_expanded_view_corner_radius);
mDraggedCornerRadius = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_corner_radius_dragged
- );
+ R.dimen.bubble_bar_expanded_view_corner_radius_dragged);
mCurrentCornerRadius = mRestingCornerRadius;
- final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
- android.R.attr.colorBackgroundFloating});
- mBackgroundColor = ta.getColor(0, Color.WHITE);
- ta.recycle();
- mCaptionHeight = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_caption_height);
-
if (mTaskView != null) {
mTaskView.setCornerRadius(mCurrentCornerRadius);
- updateHandleColor(true /* animated */);
+ mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
}
@Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Hide manage menu when view disappears
+ mMenuViewController.hideMenu(false /* animated */);
+ if (mRegionSamplingHelper != null) {
+ mRegionSamplingHelper.stopAndDestroy();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ recreateRegionSamplingHelper();
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mTaskView != null) {
@@ -284,16 +313,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mTaskView != null) {
- mTaskView.layout(l, t, r,
- t + mTaskView.getMeasuredHeight());
- mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
+ mTaskView.layout(l, t, r, t + mTaskView.getMeasuredHeight());
}
}
@Override
public void onTaskCreated() {
setContentVisibility(true);
- updateHandleColor(false /* animated */);
if (mListener != null) {
mListener.onTaskCreated();
}
@@ -305,11 +331,70 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
@Override
+ public void onTaskRemovalStarted() {
+ if (mRegionSamplingHelper != null) {
+ mRegionSamplingHelper.stopAndDestroy();
+ }
+ }
+
+ @Override
public void onBackPressed() {
if (mListener == null) return;
mListener.onBackPressed();
}
+ /**
+ * Set whether this view is currently being dragged.
+ *
+ * When dragging, the handle is hidden and content shouldn't be sampled. When dragging has
+ * ended we should start again.
+ */
+ public void setDragging(boolean isDragging) {
+ if (isDragging != mIsDragging) {
+ mIsDragging = isDragging;
+ updateSamplingState();
+ }
+ }
+
+ /** Returns whether region sampling should be enabled, i.e. if task view content is visible. */
+ private boolean shouldSampleRegion() {
+ return mTaskView != null
+ && mTaskView.getTaskInfo() != null
+ && !mIsDragging
+ && !mIsAnimating
+ && mIsContentVisible;
+ }
+
+ /**
+ * Handles starting or stopping the region sampling helper based on
+ * {@link #shouldSampleRegion()}.
+ */
+ private void updateSamplingState() {
+ if (mRegionSamplingHelper == null) return;
+ boolean shouldSample = shouldSampleRegion();
+ if (shouldSample) {
+ mRegionSamplingHelper.start(getCaptionSampleRect());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
+ /** Returns the current area of the caption bar, in screen coordinates. */
+ Rect getCaptionSampleRect() {
+ if (mTaskView == null) return null;
+ mTaskView.getLocationOnScreen(mLoc);
+ mSampleRect.set(mLoc[0], mLoc[1],
+ mLoc[0] + mTaskView.getWidth(),
+ mLoc[1] + mCaptionHeight);
+ return mSampleRect;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ public RegionSamplingHelper getRegionSamplingHelper() {
+ return mRegionSamplingHelper;
+ }
+
/** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
mMenuViewController.hideMenu(false /* animated */);
@@ -394,27 +479,14 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (!mIsAnimating) {
mTaskView.setAlpha(visible ? 1f : 0f);
+ if (mRegionSamplingHelper != null) {
+ mRegionSamplingHelper.setWindowVisible(visible);
+ }
+ updateSamplingState();
}
}
/**
- * Updates the handle color based on the task view status bar or background color; if those
- * are transparent it defaults to the background color pulled from system theme attributes.
- */
- private void updateHandleColor(boolean animated) {
- if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
- int color = mBackgroundColor;
- ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
- if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
- color = taskDescription.getStatusBarColor();
- } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
- color = taskDescription.getBackgroundColor();
- }
- final boolean isRegionDark = Color.luminance(color) <= 0.5;
- mHandleView.updateHandleColor(isRegionDark, animated);
- }
-
- /**
* Sets the alpha of both this view and the task view.
*/
public void setTaskViewAlpha(float alpha) {
@@ -442,6 +514,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
*/
public void setAnimating(boolean animating) {
mIsAnimating = animating;
+ if (mIsAnimating) {
+ // Stop sampling while animating -- when animating is done setContentVisibility will
+ // re-trigger sampling if we're visible.
+ updateSamplingState();
+ }
// If we're done animating, apply the correct visibility.
if (!animating) {
setContentVisibility(mIsContentVisible);
@@ -481,6 +558,37 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
}
+ private void recreateRegionSamplingHelper() {
+ if (mRegionSamplingHelper != null) {
+ mRegionSamplingHelper.stopAndDestroy();
+ }
+ if (mMainExecutor == null || mBackgroundExecutor == null
+ || mRegionSamplingProvider == null) {
+ // Null when it's the overflow / don't need sampling then.
+ return;
+ }
+ mRegionSamplingHelper = mRegionSamplingProvider.createHelper(this,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ if (mHandleView != null) {
+ mHandleView.updateHandleColor(isRegionDark,
+ true /* animated */);
+ }
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ return getCaptionSampleRect();
+ }
+
+ @Override
+ public boolean isSamplingEnabled() {
+ return shouldSampleRegion();
+ }
+ }, mMainExecutor, mBackgroundExecutor);
+ }
+
private class HandleViewAccessibilityDelegate extends AccessibilityDelegate {
@Override
public void onInitializeAccessibilityNodeInfo(@NonNull View host,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index c91567d7d8be..e781c07f01a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -42,7 +42,9 @@ public class BubbleBarHandleView extends View {
private final @ColorInt int mHandleLightColor;
private final @ColorInt int mHandleDarkColor;
- private @Nullable ObjectAnimator mColorChangeAnim;
+ private @ColorInt int mCurrentColor;
+ @Nullable
+ private ObjectAnimator mColorChangeAnim;
public BubbleBarHandleView(Context context) {
this(context, null /* attrs */);
@@ -88,13 +90,17 @@ public class BubbleBarHandleView extends View {
*
* @param isRegionDark Whether the background behind the handle is dark, and thus the handle
* should be light (and vice versa).
- * @param animated Whether to animate the change, or apply it immediately.
+ * @param animated Whether to animate the change, or apply it immediately.
*/
public void updateHandleColor(boolean isRegionDark, boolean animated) {
int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
+ if (newColor == mCurrentColor) {
+ return;
+ }
if (mColorChangeAnim != null) {
mColorChangeAnim.cancel();
}
+ mCurrentColor = newColor;
if (animated) {
mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
mColorChangeAnim.addListener(new AnimatorListenerAdapter() {