diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt index c45c66741d..7f9d8a390e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt @@ -27,6 +27,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.animation.Interpolator +import android.window.OnBackInvokedDispatcher import androidx.core.view.updateLayoutParams import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE @@ -66,11 +67,14 @@ constructor( /** Container where the tooltip's body should be inflated. */ lateinit var content: ViewGroup private set + private lateinit var arrow: View /** Callback invoked when the tooltip is being closed. */ var onCloseCallback: () -> Unit = {} private var openCloseAnimator: AnimatorSet? = null + /** Used to set whether users can tap outside the current tooltip window to dismiss it */ + var allowTouchDismissal = true /** Animates the tooltip into view. */ fun show() { @@ -134,14 +138,25 @@ constructor( override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_EDUCATION_DIALOG != 0 override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean { - if (ev?.action == ACTION_DOWN && !activityContext.dragLayer.isEventOverView(this, ev)) { + if ( + ev?.action == ACTION_DOWN && + !activityContext.dragLayer.isEventOverView(this, ev) && + allowTouchDismissal + ) { close(true) } return false } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + findOnBackInvokedDispatcher() + ?.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, this) + } + override fun onDetachedFromWindow() { super.onDetachedFromWindow() + findOnBackInvokedDispatcher()?.unregisterOnBackInvokedCallback(this) Settings.Secure.putInt(mContext.contentResolver, LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt index 5cbd5c95f0..d57c4838d7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt @@ -86,10 +86,13 @@ open class TaskbarEduTooltipController(context: Context) : !activityContext.isPhoneMode && !activityContext.isTinyTaskbar } + private val isOpen: Boolean get() = tooltip?.isOpen ?: false + val isBeforeTooltipFeaturesStep: Boolean get() = isTooltipEnabled && tooltipStep <= TOOLTIP_STEP_FEATURES + private lateinit var controllers: TaskbarControllers // Keep track of whether the user has seen the Search Edu @@ -152,6 +155,7 @@ open class TaskbarEduTooltipController(context: Context) : tooltipStep = TOOLTIP_STEP_NONE inflateTooltip(R.layout.taskbar_edu_features) tooltip?.run { + allowTouchDismissal = false val splitscreenAnim = requireViewById(R.id.splitscreen_animation) val suggestionsAnim = requireViewById(R.id.suggestions_animation) val pinningAnim = requireViewById(R.id.pinning_animation) @@ -216,6 +220,7 @@ open class TaskbarEduTooltipController(context: Context) : inflateTooltip(R.layout.taskbar_edu_pinning) tooltip?.run { + allowTouchDismissal = true requireViewById(R.id.standalone_pinning_animation) .supportLightTheme() @@ -260,6 +265,7 @@ open class TaskbarEduTooltipController(context: Context) : userHasSeenSearchEdu = true inflateTooltip(R.layout.taskbar_edu_search) tooltip?.run { + allowTouchDismissal = true requireViewById(R.id.search_edu_animation).supportLightTheme() val eduSubtitle: TextView = requireViewById(R.id.search_edu_text) showDisclosureText(eduSubtitle) @@ -332,7 +338,9 @@ open class TaskbarEduTooltipController(context: Context) : } /** Closes the current [tooltip]. */ - fun hide() = tooltip?.close(true) + fun hide() { + tooltip?.close(true) + } /** Initializes [tooltip] with content from [contentResId]. */ private fun inflateTooltip(@LayoutRes contentResId: Int) { diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index c42882714c..b564fa752a 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -35,7 +35,6 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Flags; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherInitListener; @@ -213,10 +212,10 @@ public final class LauncherActivityInterface extends if (launcher.isStarted() && (isInLiveTileMode() || launcher.hasBeenResumed())) { return launcher; } - if (Flags.useActivityOverlay() - && SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()) { + if (isInMinusOne()) { return launcher; } + return null; } @@ -293,6 +292,15 @@ public final class LauncherActivityInterface extends && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask(); } + private boolean isInMinusOne() { + QuickstepLauncher launcher = getCreatedContainer(); + + return launcher != null + && launcher.getStateManager().getState() == NORMAL + && !launcher.isStarted() + && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask(); + } + @Override public void onLaunchTaskFailed() { QuickstepLauncher launcher = getCreatedContainer(); diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 13e98444f4..18461a6440 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -132,6 +132,13 @@ public final class RecentsActivity extends StatefulActivity implem * Init drag layer and overview panel views. */ protected void setupViews() { + SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this); + // SplitSelectStateController needs to be created before setContentView() + mSplitSelectStateController = + new SplitSelectStateController(this, mHandler, getStateManager(), + null /* depthController */, getStatsLogManager(), + systemUiProxy, RecentsModel.INSTANCE.get(this), + null /*activityBackCallback*/); inflateRootView(R.layout.fallback_recents_activity); setContentView(getRootView()); mDragLayer = findViewById(R.id.drag_layer); @@ -139,12 +146,6 @@ public final class RecentsActivity extends StatefulActivity implem mFallbackRecentsView = findViewById(R.id.overview_panel); mActionsView = findViewById(R.id.overview_actions_view); getRootView().getSysUiScrim().getSysUIProgress().updateValue(0); - SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this); - mSplitSelectStateController = - new SplitSelectStateController(this, mHandler, getStateManager(), - null /* depthController */, getStatsLogManager(), - systemUiProxy, RecentsModel.INSTANCE.get(this), - null /*activityBackCallback*/); mDragLayer.recreateControllers(); if (enableDesktopWindowingMode()) { mDesktopRecentsTransitionController = new DesktopRecentsTransitionController( diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index fb54241592..ba33c62d9d 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -17,6 +17,7 @@ package com.android.quickstep; import static com.android.app.animation.Interpolators.ACCELERATE_1_5; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; import static com.android.launcher3.PagedView.INVALID_PAGE; import android.animation.Animator; @@ -449,7 +450,7 @@ public abstract class SwipeUpAnimationLogic implements float alpha = mAnimationFactory.getWindowAlpha(progress); mHomeAnim.setPlayFraction(progress); - if (mTargetTaskView == null) { + if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) { mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); mLocalTransformParams @@ -464,10 +465,15 @@ public abstract class SwipeUpAnimationLogic implements mLocalTransformParams.applySurfaceParams( mLocalTransformParams.createSurfaceParams(this)); - mAnimationFactory.update( - currentRect, progress, mMatrix.mapRadius(cornerRadius), (int) (alpha * 255)); - if (mTargetTaskView == null) { + mAnimationFactory.update( + currentRect, + progress, + mMatrix.mapRadius(cornerRadius), + !enableAdditionalHomeAnimations() || mTargetTaskView == null + ? 0 : (int) (alpha * 255)); + + if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) { return; } if (mAnimationFactory.isAnimatingIntoIcon() && mAnimationFactory.isAnimationReady()) { @@ -506,7 +512,7 @@ public abstract class SwipeUpAnimationLogic implements public void onAnimationStart(Animator animation) { setUp(); mHomeAnim.dispatchOnStart(); - if (mTargetTaskView == null) { + if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) { return; } Rect thumbnailBounds = new Rect(); @@ -521,7 +527,7 @@ public abstract class SwipeUpAnimationLogic implements } private void setUp() { - if (mTargetTaskView == null) { + if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) { return; } RecentsView recentsView = mTargetTaskView.getRecentsView(); @@ -542,7 +548,7 @@ public abstract class SwipeUpAnimationLogic implements } private void cleanUp() { - if (mTargetTaskView == null) { + if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) { return; } RecentsView recentsView = mTargetTaskView.getRecentsView(); diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index fd141c3229..d18c86ea8e 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -492,18 +492,8 @@ public interface TaskShortcutFactory { TaskContainer taskContainer) { boolean isTablet = container.getDeviceProfile().isTablet; boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview(); - // Extra conditions if it's not grid-only overview if (!isGridOnlyOverview) { - RecentsOrientedState orientedState = taskContainer.getTaskView().getOrientedState(); - boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed() - && orientedState.getTouchRotation() != ROTATION_0; - if (!isFakeLandscape) { - return null; - } - // Disallow "Select" when swiping up from landscape due to rotated thumbnail. - if (orientedState.getDisplayRotation() != ROTATION_0) { - return null; - } + return null; } SystemShortcut modalStateSystemShortcut = diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java index 758a737bec..eeacee1cf6 100644 --- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java +++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java @@ -259,7 +259,8 @@ public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements return new Pair<>(translationX, translationY); } - bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL); + bannerParams.gravity = + BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL); // Set correct width if (desiredTaskId == splitBounds.leftTopTaskId) { diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt index 85238ed826..7e51fcfedc 100644 --- a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt +++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt @@ -50,7 +50,7 @@ private constructor( private val disappearanceDurationMs: Long, private val interpolator: Interpolator, ) { - private val borderAnimationProgress = AnimatedFloat { updateOutline() } + private val borderAnimationProgress = AnimatedFloat { _ -> updateOutline() } private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = borderColor @@ -224,6 +224,7 @@ private constructor( val borderWidth: Float get() = borderWidthPx * animationProgress + val alignmentAdjustment: Float // Outset the border by half the width to create an outwards-growth animation get() = -borderWidth / 2f + alignmentAdjustmentInset diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 4f802c95a6..d7c7857653 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -31,9 +31,11 @@ import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.app.animation.Interpolators.OVERSHOOT_0_75; import static com.android.app.animation.Interpolators.clampToProgress; +import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; +import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; import static com.android.launcher3.Flags.enableGridOnlyOverview; import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; @@ -130,6 +132,7 @@ import androidx.annotation.UiThread; import androidx.core.graphics.ColorUtils; import com.android.internal.jank.Cuj; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Flags; @@ -2688,6 +2691,7 @@ public abstract class RecentsView { setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); @@ -3801,7 +3805,7 @@ public abstract class RecentsView + focusTransitionScaleAndDim.value = + FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v) + } + .animateToValue(1f, 0f) private var iconAndDimAnimator: ObjectAnimator? = null // The current background requests to load the task thumbnail and icon @@ -1700,16 +1701,6 @@ constructor( override fun get(taskView: TaskView) = taskView.focusTransitionProgress } - @JvmField - val SCALE_AND_DIM_OUT: FloatProperty = - object : FloatProperty("scaleAndDimFastOut") { - override fun setValue(taskView: TaskView, v: Float) { - taskView.focusTransitionScaleAndDimOut = v - } - - override fun get(taskView: TaskView) = taskView.focusTransitionScaleAndDimOut - } - private val SPLIT_SELECT_TRANSLATION_X: FloatProperty = object : FloatProperty("splitSelectTranslationX") { override fun setValue(taskView: TaskView, v: Float) { diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index ba34f598e7..2a472227b9 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -305,9 +305,7 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView { @Override public int getScrollBarTop() { - return ActivityContext.lookupContext(getContext()).getAppsView().isSearchSupported() - ? getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding) - : 0; + return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding); } @Override diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java index b414ab6e35..44411640fb 100644 --- a/src/com/android/launcher3/anim/AnimatedFloat.java +++ b/src/com/android/launcher3/anim/AnimatedFloat.java @@ -20,6 +20,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.util.FloatProperty; +import java.util.function.Consumer; + /** * A mutable float which allows animating the value */ @@ -38,9 +40,9 @@ public class AnimatedFloat { } }; - private static final Runnable NO_OP = () -> { }; + private static final Consumer NO_OP = t -> { }; - private final Runnable mUpdateCallback; + private final Consumer mUpdateCallback; private ObjectAnimator mValueAnimator; // Only non-null when an animation is playing to this value. private Float mEndValue; @@ -52,6 +54,10 @@ public class AnimatedFloat { } public AnimatedFloat(Runnable updateCallback) { + this(v -> updateCallback.run()); + } + + public AnimatedFloat(Consumer updateCallback) { mUpdateCallback = updateCallback; } @@ -60,6 +66,11 @@ public class AnimatedFloat { value = initialValue; } + public AnimatedFloat(Consumer updateCallback, float initialValue) { + this(updateCallback); + value = initialValue; + } + /** * Returns an animation from the current value to the given value. */ @@ -99,7 +110,7 @@ public class AnimatedFloat { public void updateValue(float v) { if (Float.compare(v, value) != 0) { value = v; - mUpdateCallback.run(); + mUpdateCallback.accept(value); } } diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index e58890f7ca..47a2bdd6de 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -59,6 +59,13 @@ public class PendingAnimation extends AnimatedPropertySetter { add(anim, springProperty); } + /** + * Utility method to sent an interpolator on an animation and add it to the list + */ + public void add(Animator anim, TimeInterpolator interpolator) { + add(anim, interpolator, SpringProperty.DEFAULT); + } + @Override public void add(Animator anim) { add(anim, SpringProperty.DEFAULT); diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index dc6968c7d9..312c6f412a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -209,7 +209,7 @@ public class LoaderTask implements Runnable { mApp.getContext().getContentResolver(), "launcher_broadcast_installed_apps", /* def= */ 0); - if (launcherBroadcastInstalledApps == 1) { + if (launcherBroadcastInstalledApps == 1 && mIsRestoreFromBackup) { List broadcastModels = FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( mPmHelper, diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java index 325c1cdc6f..f90a3e4dd1 100644 --- a/src/com/android/launcher3/views/ClipIconView.java +++ b/src/com/android/launcher3/views/ClipIconView.java @@ -16,6 +16,7 @@ package com.android.launcher3.views; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; import static com.android.launcher3.Utilities.boundToRange; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; @@ -97,6 +98,9 @@ public class ClipIconView extends View implements ClipPathView { * within the clip bounds of this view. */ public void setTaskViewArtist(TaskViewArtist taskViewArtist) { + if (!enableAdditionalHomeAnimations()) { + return; + } mTaskViewArtist = taskViewArtist; invalidate(); } diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 1d5a9dcb97..1e577bec36 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -18,6 +18,7 @@ package com.android.launcher3.views; import static android.view.Gravity.LEFT; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; import static com.android.launcher3.Utilities.getFullDrawable; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -164,7 +165,12 @@ public class FloatingIconView extends FrameLayout implements */ public void update(float alpha, RectF rect, float progress, float shapeProgressStart, float cornerRadius, boolean isOpening, int taskViewDrawAlpha) { - setAlpha(isLaidOut() ? alpha : 0f); + // The non-running task home animation has some very funky first few frames because this + // FIV hasn't fully laid out. During those frames, hide this FIV and continue drawing the + // TaskView directly while transforming it in the place of this FIV. However, if we fade + // the TaskView at all, we need to display this FIV regardless. + setAlpha(!enableAdditionalHomeAnimations() || isLaidOut() || taskViewDrawAlpha < 255 + ? alpha : 0f); mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, this, mLauncher.getDeviceProfile(), taskViewDrawAlpha); diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt index 28a001f990..d16674c6c2 100644 --- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt +++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt @@ -1,10 +1,13 @@ package com.android.launcher3.model import android.appwidget.AppWidgetManager +import android.content.Intent import android.os.UserHandle import android.platform.test.flag.junit.SetFlagsRule +import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.launcher3.Flags import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.LauncherAppState @@ -14,6 +17,7 @@ import com.android.launcher3.icons.IconCache import com.android.launcher3.icons.cache.CachingLogic import com.android.launcher3.icons.cache.IconCacheUpdateHandler import com.android.launcher3.pm.UserCache +import com.android.launcher3.provider.RestoreDbTask import com.android.launcher3.ui.TestViewHelpers import com.android.launcher3.util.Executors.MODEL_EXECUTOR import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext @@ -21,21 +25,30 @@ import com.android.launcher3.util.LooperIdleLock import com.android.launcher3.util.UserIconInfo import com.google.common.truth.Truth import java.util.concurrent.CountDownLatch +import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyList +import org.mockito.ArgumentMatchers.anyMap import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.times -import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.MockitoSession import org.mockito.Spy +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql" @@ -43,6 +56,20 @@ private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql" @RunWith(AndroidJUnit4::class) class LoaderTaskTest { private var context = SandboxModelContext() + private val expectedBroadcastModel = + FirstScreenBroadcastModel( + installerPackage = "installerPackage", + pendingCollectionItems = mutableSetOf("pendingCollectionItem"), + pendingWidgetItems = mutableSetOf("pendingWidgetItem"), + pendingHotseatItems = mutableSetOf("pendingHotseatItem"), + pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"), + installedHotseatItems = mutableSetOf("installedHotseatItem"), + installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"), + firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"), + secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget") + ) + private lateinit var mockitoSession: MockitoSession + @Mock private lateinit var app: LauncherAppState @Mock private lateinit var bgAllAppsList: AllAppsList @Mock private lateinit var modelDelegate: ModelDelegate @@ -61,7 +88,11 @@ class LoaderTaskTest { @Before fun setup() { MockitoAnnotations.initMocks(this) - + mockitoSession = + ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(FirstScreenBroadcastHelper::class.java) + .startMocking() val idp = InvariantDeviceProfile().apply { numRows = 5 @@ -90,6 +121,7 @@ class LoaderTaskTest { @After fun tearDown() { context.onDestroy() + mockitoSession.finishMocking() } @Test @@ -166,6 +198,141 @@ class LoaderTaskTest { verify(bgAllAppsList, Mockito.never()) .setFlags(BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED, true) } + + @Test + fun `When launcher_broadcast_installed_apps and is restore then send installed item broadcast`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) + RestoreDbTask.setPending(spyContext) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java) + verify(spyContext).sendBroadcast(argumentCaptor.capture()) + val actualBroadcastIntent = argumentCaptor.value + assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`) + assertEquals( + ArrayList(expectedBroadcastModel.installedWorkspaceItems), + actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems") + ) + assertEquals( + ArrayList(expectedBroadcastModel.installedHotseatItems), + actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems") + ) + assertEquals( + ArrayList( + expectedBroadcastModel.firstScreenInstalledWidgets + + expectedBroadcastModel.secondaryScreenInstalledWidgets + ), + actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingCollectionItems), + actualBroadcastIntent.getStringArrayListExtra("folderItem") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingWorkspaceItems), + actualBroadcastIntent.getStringArrayListExtra("workspaceItem") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingHotseatItems), + actualBroadcastIntent.getStringArrayListExtra("hotseatItem") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingWidgetItems), + actualBroadcastIntent.getStringArrayListExtra("widgetItem") + ) + } + + @Test + fun `When not a restore then installed item broadcast not sent`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java)) + } + + @Test + fun `When launcher_broadcast_installed_apps false then installed item broadcast not sent`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0) + RestoreDbTask.setPending(spyContext) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java)) + } } private fun LoaderTask.runSyncOnBackgroundThread() {