diff options
13 files changed, 265 insertions, 158 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/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java index ab147bbcd9..515cfe2af0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java @@ -47,7 +47,6 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.app.animation.Interpolators; import com.android.internal.jank.Cuj; -import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; @@ -180,17 +179,6 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { mIsRtl = Utilities.isRtl(resources); - if (Flags.taskbarOverflow()) { - initializeScrollArrows(); - - if (mIsRtl) { - mStartScrollArrow.setContentDescription( - resources.getString(R.string.quick_switch_scroll_arrow_right)); - mEndScrollArrow.setContentDescription( - resources.getString(R.string.quick_switch_scroll_arrow_left)); - } - } - TypefaceUtils.setTypeface( mNoRecentItemsPane.findViewById(R.id.no_recent_items_text), FontFamily.GSF_LABEL_LARGE); @@ -359,9 +347,21 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { }); } - private void initializeScrollArrows() { + + void enableScrollArrowSupport() { + if (mSupportsScrollArrows) { + return; + } mSupportsScrollArrows = true; + if (mIsRtl) { + mStartScrollArrow.setContentDescription( + getResources().getString(R.string.quick_switch_scroll_arrow_right)); + mEndScrollArrow.setContentDescription( + getResources().getString(R.string.quick_switch_scroll_arrow_left)); + } + + mStartScrollArrow.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index b5f2532482..a312d5f70b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.internal.jank.Cuj; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorListeners; @@ -119,6 +120,10 @@ public class KeyboardQuickSwitchViewController { mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut; mWasOpenedFromTaskbar = wasOpenedFromTaskbar; + if (Flags.taskbarOverflow() && wasOpenedFromTaskbar) { + mKeyboardQuickSwitchView.enableScrollArrowSupport(); + } + mKeyboardQuickSwitchView.applyLoadPlan( mOverlayContext, tasks, diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index f342fa52b3..cc6cc645f5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -348,13 +348,17 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas controllers.bubbleControllers.isPresent && controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible() var insetsIsTouchableRegion = true + // Prevents the taskbar from taking touches and conflicting with setup wizard if ( context.isPhoneButtonNavMode && + context.isUserSetupComplete && (!controllers.navbarButtonsViewController.isImeVisible || !controllers.navbarButtonsViewController.isImeRenderingNavButtons) ) { insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME) insetsIsTouchableRegion = false + debugTouchableRegion.lastSetTouchableReason = + "Phone button nav mode: Fullscreen touchable, IME not affecting nav buttons" } else if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) { // Let touches pass through us. insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index f15ef8c52c..ac9d2ba8bf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -496,7 +496,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar float finalScale; TaskbarSharedState sharedState = mControllers.getSharedState(); - if (sharedState != null || mControllers.getSharedState().startTaskbarVariantIsTransient) { + if (sharedState != null && sharedState.startTaskbarVariantIsTransient) { finalScale = mapRange(scale, 1f, ((float) mPersistentIconSize / mTransientIconSize)); } else { finalScale = mapRange(scale, ((float) mTransientIconSize / mPersistentIconSize), 1f); diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt index e487f9fd40..3712a76eab 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt @@ -27,7 +27,6 @@ import android.widget.Space import com.android.launcher3.DeviceProfile import com.android.launcher3.R import com.android.launcher3.Utilities -import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter /** @@ -48,7 +47,7 @@ abstract class AbstractNavButtonLayoutter( protected val startContextualContainer: ViewGroup, protected val imeSwitcher: ImageView?, protected val a11yButton: ImageView?, - protected val space: Space? + protected val space: Space?, ) : NavButtonLayoutter { protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home) protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps) @@ -69,26 +68,34 @@ abstract class AbstractNavButtonLayoutter( val params = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams.MATCH_PARENT, ) params.gravity = Gravity.CENTER return params } + /** + * Adjusts the layout parameters of the nav bar container for setup in phone mode. + * + * @param nearestTouchFrameLayoutParams The layout parameters of the navButtonsView, which is + * the ViewGroup that contains start, end, nav button ViewGroups + * @param deviceProfile The device profile containing information about the device's + * configuration. + */ fun adjustForSetupInPhoneMode( - navButtonsLayoutParams: FrameLayout.LayoutParams, - navButtonsViewLayoutParams: FrameLayout.LayoutParams, - deviceProfile: DeviceProfile + nearestTouchFrameLayoutParams: FrameLayout.LayoutParams, + deviceProfile: DeviceProfile, ) { val phoneOrPortraitSetupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin) - navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin - navButtonsLayoutParams.bottomMargin = + nearestTouchFrameLayoutParams.marginStart = phoneOrPortraitSetupMargin + nearestTouchFrameLayoutParams.bottomMargin = if (!deviceProfile.isLandscape) 0 else phoneOrPortraitSetupMargin - - resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2 - navButtonsViewLayoutParams.height = + resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2 + + nearestTouchFrameLayoutParams.height = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height) } @@ -97,7 +104,7 @@ abstract class AbstractNavButtonLayoutter( buttonSize: Int, barAxisMarginStart: Int, barAxisMarginEnd: Int, - gravity: Int + gravity: Int, ) { val contextualContainerParams = FrameLayout.LayoutParams(buttonSize, ViewGroup.LayoutParams.MATCH_PARENT) diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt index 2497fbb98e..a199dba0bd 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt @@ -66,7 +66,7 @@ class NavButtonLayoutFactory { isInSetup: Boolean, isThreeButtonNav: Boolean, phoneMode: Boolean, - @Rotation surfaceRotation: Int + @Rotation surfaceRotation: Int, ): NavButtonLayoutter { val navButtonContainer = navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS) @@ -77,6 +77,18 @@ class NavButtonLayoutFactory { val isPhoneNavMode = phoneMode && isThreeButtonNav val isPhoneGestureMode = phoneMode && !isThreeButtonNav return when { + isInSetup -> { + SetupNavLayoutter( + resources, + navButtonsView, + navButtonContainer, + endContextualContainer, + startContextualContainer, + imeSwitcher, + a11yButton, + space, + ) + } isPhoneNavMode -> { if (!deviceProfile.isLandscape) { navButtonsView.setIsVertical(false) @@ -87,7 +99,7 @@ class NavButtonLayoutFactory { startContextualContainer, imeSwitcher, a11yButton, - space + space, ) } else if (surfaceRotation == ROTATION_90) { navButtonsView.setIsVertical(true) @@ -98,7 +110,7 @@ class NavButtonLayoutFactory { startContextualContainer, imeSwitcher, a11yButton, - space + space, ) } else { navButtonsView.setIsVertical(true) @@ -109,36 +121,23 @@ class NavButtonLayoutFactory { startContextualContainer, imeSwitcher, a11yButton, - space + space, ) } } isPhoneGestureMode -> { PhoneGestureLayoutter( resources, - navButtonsView, navButtonContainer, endContextualContainer, startContextualContainer, imeSwitcher, a11yButton, - space + space, ) } deviceProfile.isTaskbarPresent -> { return when { - isInSetup -> { - SetupNavLayoutter( - resources, - navButtonsView, - navButtonContainer, - endContextualContainer, - startContextualContainer, - imeSwitcher, - a11yButton, - space - ) - } isKidsMode -> { KidsNavLayoutter( resources, @@ -147,7 +146,7 @@ class NavButtonLayoutFactory { startContextualContainer, imeSwitcher, a11yButton, - space + space, ) } else -> @@ -158,7 +157,7 @@ class NavButtonLayoutFactory { startContextualContainer, imeSwitcher, a11yButton, - space + space, ) } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt index 390ec342e1..e0f2a22b2c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt @@ -17,25 +17,21 @@ package com.android.launcher3.taskbar.navbutton import android.content.res.Resources -import android.view.Gravity import android.view.ViewGroup -import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.Space -import com.android.launcher3.DeviceProfile import com.android.launcher3.taskbar.TaskbarActivityContext /** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */ class PhoneGestureLayoutter( resources: Resources, - navButtonsView: NearestTouchFrame, navBarContainer: LinearLayout, endContextualContainer: ViewGroup, startContextualContainer: ViewGroup, imeSwitcher: ImageView?, a11yButton: ImageView?, - space: Space? + space: Space?, ) : AbstractNavButtonLayoutter( resources, @@ -44,33 +40,10 @@ class PhoneGestureLayoutter( startContextualContainer, imeSwitcher, a11yButton, - space + space, ) { - private val mNavButtonsView = navButtonsView override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) { - // TODO: look into if we should use SetupNavLayoutter instead. - if (!context.isUserSetupComplete) { - // Since setup wizard only has back button enabled, it looks strange to be - // end-aligned, so start-align instead. - val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams - val navButtonsViewLayoutParams = - mNavButtonsView.layoutParams as FrameLayout.LayoutParams - val deviceProfile: DeviceProfile = context.deviceProfile - - navButtonsLayoutParams.marginEnd = 0 - navButtonsLayoutParams.gravity = Gravity.START - context.setTaskbarWindowSize(context.setupWindowSize) - - adjustForSetupInPhoneMode( - navButtonsLayoutParams, - navButtonsViewLayoutParams, - deviceProfile - ) - mNavButtonsView.layoutParams = navButtonsViewLayoutParams - navButtonContainer.layoutParams = navButtonsLayoutParams - } - endContextualContainer.removeAllViews() startContextualContainer.removeAllViews() } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt index e032430c1d..eb3fdeb99e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt @@ -29,12 +29,15 @@ import com.android.launcher3.DeviceProfile import com.android.launcher3.R import com.android.launcher3.taskbar.TaskbarActivityContext +const val SUW_THEME_SYSTEM_PROPERTY = "setupwizard.theme" +const val GLIF_EXPRESSIVE_THEME = "glif_expressive" +const val GLIF_EXPRESSIVE_LIGHT_THEME = "glif_expressive_light" const val SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95 const val SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05 class SetupNavLayoutter( resources: Resources, - navButtonsView: NearestTouchFrame, + nearestTouchFrame: NearestTouchFrame, navButtonContainer: LinearLayout, endContextualContainer: ViewGroup, startContextualContainer: ViewGroup, @@ -51,17 +54,19 @@ class SetupNavLayoutter( a11yButton, space, ) { - private val mNavButtonsView = navButtonsView + // mNearestTouchFrame is a ViewGroup that contains start, end, nav button ViewGroups + private val mNearestTouchFrame = nearestTouchFrame override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) { - val SUWTheme = SystemProperties.get("setupwizard.theme", "") - if (SUWTheme == "glif_expressive" || SUWTheme == "glif_expressive_light") { + val SUWTheme = SystemProperties.get(SUW_THEME_SYSTEM_PROPERTY, "") + if (SUWTheme == GLIF_EXPRESSIVE_THEME || SUWTheme == GLIF_EXPRESSIVE_LIGHT_THEME) { return } // Since setup wizard only has back button enabled, it looks strange to be // end-aligned, so start-align instead. val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams - val navButtonsViewLayoutParams = mNavButtonsView.layoutParams as FrameLayout.LayoutParams + val navButtonsOverallViewGroupLayoutParams = + mNearestTouchFrame.layoutParams as FrameLayout.LayoutParams val deviceProfile: DeviceProfile = context.deviceProfile navButtonsLayoutParams.marginEnd = 0 @@ -77,18 +82,14 @@ class SetupNavLayoutter( ) { navButtonsLayoutParams.marginStart = resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin) - navButtonsViewLayoutParams.bottomMargin = + navButtonsOverallViewGroupLayoutParams.bottomMargin = resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin) navButtonsLayoutParams.height = resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height) } else { - adjustForSetupInPhoneMode( - navButtonsLayoutParams, - navButtonsViewLayoutParams, - deviceProfile, - ) + adjustForSetupInPhoneMode(navButtonsOverallViewGroupLayoutParams, deviceProfile) } - mNavButtonsView.layoutParams = navButtonsViewLayoutParams + mNearestTouchFrame.layoutParams = navButtonsOverallViewGroupLayoutParams navButtonContainer.layoutParams = navButtonsLayoutParams endContextualContainer.removeAllViews() 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)) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt index 61a697503e..8f26795045 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +++ b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt @@ -316,43 +316,6 @@ class BubbleBarViewAnimatorTest { } @Test - fun animateBubbleInForStashed_showAnimationCanceled() { - setUpBubbleBar() - - val handle = View(context) - val handleAnimator = PhysicsAnimator.getInstance(handle) - bubbleStashController.handleAnimator = handleAnimator - - val animator = - BubbleBarViewAnimator( - bubbleBarView, - bubbleStashController, - flyoutController, - bubbleBarParentViewController, - onExpanded = emptyRunnable, - onBubbleBarVisible = emptyRunnable, - animatorScheduler, - ) - - InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleInForStashed(bubble, isExpanding = false) - } - - // wait for the animation to start - InstrumentationRegistry.getInstrumentation().runOnMainSync {} - PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } - - handleAnimator.assertIsRunning() - assertThat(animator.isAnimating).isTrue() - assertThat(animatorScheduler.delayedBlock).isNotNull() - - handleAnimator.cancel() - handleAnimator.assertIsNotRunning() - assertThat(animator.isAnimating).isFalse() - assertThat(animatorScheduler.delayedBlock).isNull() - } - - @Test fun animateBubbleInForStashed_autoExpanding() { setUpBubbleBar() diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 8f116bbd6b..fd8e2f7838 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -45,6 +45,7 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; @@ -421,7 +422,8 @@ public class LoaderCursor extends CursorWrapper { ) { boolean isPreArchived = Flags.enableSupportForArchiving() && Flags.restoreArchivedAppIconsFromDb() - && info.isInactiveArchive(); + && info.isInactiveArchive() + && LauncherPrefs.get(mContext).get(LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE); boolean preArchivedIconNotFound = isPreArchived && !loadIconFromDb(info); if (preArchivedIconNotFound) { Log.d(TAG, "loadIconFromDb failed for pre-archived icon, loading from cache." diff --git a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java index b848d27cd9..d1292cf0aa 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -21,6 +21,7 @@ import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType import static androidx.test.InstrumentationRegistry.getContext; +import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE; import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID; import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER; import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE; @@ -69,6 +70,7 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.ItemInfo; @@ -96,6 +98,7 @@ public class LoaderCursorTest { private LauncherModelHelper mModelHelper; private LauncherAppState mApp; + private LauncherPrefs mPrefs; private MatrixCursor mCursor; private InvariantDeviceProfile mIDP; @@ -113,6 +116,7 @@ public class LoaderCursorTest { public void setup() { mModelHelper = new LauncherModelHelper(); mContext = mModelHelper.sandboxContext; + mPrefs = LauncherPrefs.get(mContext); mIDP = InvariantDeviceProfile.INSTANCE.get(mContext); mApp = LauncherAppState.getInstance(mContext); @@ -131,6 +135,7 @@ public class LoaderCursorTest { @After public void tearDown() { + mPrefs.putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false)); mCursor.close(); mModelHelper.destroy(); } @@ -253,8 +258,9 @@ public class LoaderCursorTest { @Test @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) - public void ifArchivedWithFlag_whenloadWorkspaceTitleAndIcon_thenLoadIconFromDb() { + public void ifArchivedWithFlagAndRestore_whenloadWorkspaceTitleAndIcon_thenLoadIconFromDb() { // Given + mPrefs.putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true)); initCursor(ITEM_TYPE_APPLICATION, "title"); assertTrue(mLoaderCursor.moveToNext()); WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(); @@ -262,6 +268,7 @@ public class LoaderCursorTest { Bitmap expectedBitmap = LauncherIcons.obtain(mContext) .createIconBitmap(decodeByteArray(sTestBlob, 0, sTestBlob.length)) .icon; + // When mLoaderCursor.loadWorkspaceTitleAndIcon(false, true, itemInfo); // Then @@ -271,6 +278,23 @@ public class LoaderCursorTest { @Test @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) + public void ifArchivedWithFlagAndNotRestore_whenloadWorkspaceTitleAndIcon_thenLoadIconFromDb() { + // Given + mPrefs.putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false)); + initCursor(ITEM_TYPE_APPLICATION, "title"); + assertTrue(mLoaderCursor.moveToNext()); + WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(); + BitmapInfo original = itemInfo.bitmap; + itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED; + + // When + mLoaderCursor.loadWorkspaceTitleAndIcon(false, true, itemInfo); + // Then + assertThat(itemInfo.bitmap).isEqualTo(original); + } + + @Test + @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) public void ifArchivedWithFlag_whenLoadIconFromDb_thenLoadIconFromBlob() { // Given initCursor(ITEM_TYPE_APPLICATION, "title"); |