summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--quickstep/src/com/android/launcher3/statehandlers/DepthController.java90
-rw-r--r--quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt105
2 files changed, 162 insertions, 33 deletions
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 360210b010..d9808308e8 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -30,6 +30,8 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -46,8 +48,8 @@ import java.util.function.Consumer;
*/
public class DepthController extends BaseDepthController implements StateHandler<LauncherState>,
BaseActivity.MultiWindowModeChangedListener {
-
- private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw;
+ @VisibleForTesting
+ final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw;
private final Consumer<Boolean> mCrossWindowBlurListener = this::setCrossWindowBlursEnabled;
@@ -58,6 +60,10 @@ public class DepthController extends BaseDepthController implements StateHandler
private View.OnAttachStateChangeListener mOnAttachListener;
+ // Ensure {@link mOnDrawListener} is added only once to avoid spamming DragLayer's mRunQueue
+ // via {@link View#post(Runnable)}
+ private boolean mIsOnDrawListenerAdded = false;
+
public DepthController(Launcher l) {
super(l);
}
@@ -66,33 +72,37 @@ public class DepthController extends BaseDepthController implements StateHandler
View view = mLauncher.getDragLayer();
ViewRootImpl viewRootImpl = view.getViewRootImpl();
setBaseSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
- view.post(() -> view.getViewTreeObserver().removeOnDrawListener(mOnDrawListener));
+ view.post(this::removeOnDrawListener);
}
private void ensureDependencies() {
- if (mLauncher.getRootView() != null && mOnAttachListener == null) {
- View rootView = mLauncher.getRootView();
- mOnAttachListener = new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View view) {
- UI_HELPER_EXECUTOR.execute(() ->
- CrossWindowBlurListeners.getInstance().addListener(
- mLauncher.getMainExecutor(), mCrossWindowBlurListener));
- mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
-
- // To handle the case where window token is invalid during last setDepth call.
- applyDepthAndBlur();
- }
-
- @Override
- public void onViewDetachedFromWindow(View view) {
- removeSecondaryListeners();
- }
- };
- rootView.addOnAttachStateChangeListener(mOnAttachListener);
- if (rootView.isAttachedToWindow()) {
- mOnAttachListener.onViewAttachedToWindow(rootView);
+ View rootView = mLauncher.getRootView();
+ if (rootView == null) {
+ return;
+ }
+ if (mOnAttachListener != null) {
+ return;
+ }
+ mOnAttachListener = new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ UI_HELPER_EXECUTOR.execute(() ->
+ CrossWindowBlurListeners.getInstance().addListener(
+ mLauncher.getMainExecutor(), mCrossWindowBlurListener));
+ mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
+
+ // To handle the case where window token is invalid during last setDepth call.
+ applyDepthAndBlur();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ removeSecondaryListeners();
}
+ };
+ rootView.addOnAttachStateChangeListener(mOnAttachListener);
+ if (rootView.isAttachedToWindow()) {
+ mOnAttachListener.onViewAttachedToWindow(rootView);
}
}
@@ -109,11 +119,9 @@ public class DepthController extends BaseDepthController implements StateHandler
}
private void removeSecondaryListeners() {
- if (mCrossWindowBlurListener != null) {
- UI_HELPER_EXECUTOR.execute(() ->
- CrossWindowBlurListeners.getInstance()
- .removeListener(mCrossWindowBlurListener));
- }
+ UI_HELPER_EXECUTOR.execute(() ->
+ CrossWindowBlurListeners.getInstance()
+ .removeListener(mCrossWindowBlurListener));
if (mOpaquenessListener != null) {
mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
}
@@ -124,9 +132,9 @@ public class DepthController extends BaseDepthController implements StateHandler
*/
public void setActivityStarted(boolean isStarted) {
if (isStarted) {
- mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+ addOnDrawListener();
} else {
- mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+ removeOnDrawListener();
setBaseSurface(null);
}
}
@@ -139,7 +147,7 @@ public class DepthController extends BaseDepthController implements StateHandler
stateDepth.setValue(toState.getDepth(mLauncher));
if (toState == LauncherState.BACKGROUND_APP) {
- mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+ addOnDrawListener();
}
}
@@ -165,7 +173,23 @@ public class DepthController extends BaseDepthController implements StateHandler
@Override
protected void onInvalidSurface() {
// Lets wait for surface to become valid again
+ addOnDrawListener();
+ }
+
+ private void addOnDrawListener() {
+ if (mIsOnDrawListenerAdded) {
+ return;
+ }
mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+ mIsOnDrawListenerAdded = true;
+ }
+
+ private void removeOnDrawListener() {
+ if (!mIsOnDrawListenerAdded) {
+ return;
+ }
+ mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+ mIsOnDrawListenerAdded = false;
}
@Override
diff --git a/quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt b/quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt
new file mode 100644
index 0000000000..17cca0b7d9
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/statehandlers/DepthControllerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 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.launcher3.statehandlers
+
+import android.content.res.Resources
+import android.view.ViewTreeObserver
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Launcher
+import com.android.launcher3.R
+import com.android.launcher3.dragndrop.DragLayer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.same
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DepthControllerTest {
+
+ private lateinit var underTest: DepthController
+ @Mock private lateinit var launcher: Launcher
+ @Mock private lateinit var resource: Resources
+ @Mock private lateinit var dragLayer: DragLayer
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ `when`(launcher.resources).thenReturn(resource)
+ `when`(resource.getInteger(R.integer.max_depth_blur_radius)).thenReturn(30)
+ `when`(launcher.dragLayer).thenReturn(dragLayer)
+ `when`(dragLayer.viewTreeObserver).thenReturn(viewTreeObserver)
+
+ underTest = DepthController(launcher)
+ }
+
+ @Test
+ fun setActivityStarted_add_onDrawListener() {
+ underTest.setActivityStarted(true)
+
+ verify(viewTreeObserver).addOnDrawListener(same(underTest.mOnDrawListener))
+ }
+
+ @Test
+ fun setActivityStopped_not_remove_onDrawListener() {
+ underTest.setActivityStarted(false)
+
+ // Because underTest.mOnDrawListener is never added
+ verifyNoMoreInteractions(viewTreeObserver)
+ }
+
+ @Test
+ fun setActivityStared_then_stopped_remove_onDrawListener() {
+ underTest.setActivityStarted(true)
+ reset(viewTreeObserver)
+
+ underTest.setActivityStarted(false)
+
+ verify(viewTreeObserver).removeOnDrawListener(same(underTest.mOnDrawListener))
+ }
+
+ @Test
+ fun setActivityStared_then_stopped_multiple_times_remove_onDrawListener_once() {
+ underTest.setActivityStarted(true)
+ reset(viewTreeObserver)
+
+ underTest.setActivityStarted(false)
+ underTest.setActivityStarted(false)
+ underTest.setActivityStarted(false)
+
+ // Should just remove mOnDrawListener once
+ verify(viewTreeObserver).removeOnDrawListener(same(underTest.mOnDrawListener))
+ }
+
+ @Test
+ fun test_onInvalidSurface_multiple_times_add_onDrawListener_once() {
+ underTest.onInvalidSurface()
+ underTest.onInvalidSurface()
+ underTest.onInvalidSurface()
+
+ // We should only call addOnDrawListener 1 time
+ verify(viewTreeObserver).addOnDrawListener(same(underTest.mOnDrawListener))
+ }
+}