Snap for 12129055 from 72054af897 to 24Q4-release

Change-Id: Ia49f83819d4568729ba35092805013548c937a31
This commit is contained in:
Android Build Coastguard Worker
2024-07-23 23:21:25 +00:00
30 changed files with 710 additions and 98 deletions
@@ -19,10 +19,9 @@ import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.os.Debug;
import android.os.SystemProperties;
import android.util.Log;
import android.view.View;
@@ -145,7 +144,7 @@ public class DesktopVisibilityController {
notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
}
if (!enableDesktopWindowingWallpaperActivity() && wasVisible != isVisible) {
if (!WALLPAPER_ACTIVITY.isEnabled(mLauncher) && wasVisible != isVisible) {
// TODO: b/333533253 - Remove after flag rollout
if (mVisibleDesktopTasksCount > 0) {
setLauncherViewsVisibility(View.INVISIBLE);
@@ -189,7 +188,7 @@ public class DesktopVisibilityController {
notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
}
if (enableDesktopWindowingWallpaperActivity()) {
if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
return;
}
// TODO: b/333533253 - Clean up after flag rollout
@@ -289,7 +288,7 @@ public class DesktopVisibilityController {
* TODO: b/333533253 - Remove after flag rollout
*/
private void setLauncherViewsVisibility(int visibility) {
if (enableDesktopWindowingWallpaperActivity()) {
if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
return;
}
if (DEBUG) {
@@ -314,7 +313,7 @@ public class DesktopVisibilityController {
* TODO: b/333533253 - Remove after flag rollout
*/
private void markLauncherPaused() {
if (enableDesktopWindowingWallpaperActivity()) {
if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
return;
}
if (DEBUG) {
@@ -331,7 +330,7 @@ public class DesktopVisibilityController {
* TODO: b/333533253 - Remove after flag rollout
*/
private void markLauncherResumed() {
if (enableDesktopWindowingWallpaperActivity()) {
if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
return;
}
if (DEBUG) {
@@ -21,7 +21,7 @@ import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTI
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IGNORE_IN_APP;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -214,7 +214,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (!enableDesktopWindowingWallpaperActivity()
if (!WALLPAPER_ACTIVITY.isEnabled(mLauncher)
&& desktopController != null
&& desktopController.areDesktopTasksVisible()) {
// TODO: b/333533253 - Remove after flag rollout
@@ -315,6 +315,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
new TaskbarTranslationController(this),
new TaskbarSpringOnStashController(this),
new TaskbarRecentAppsController(
this,
RecentsModel.INSTANCE.get(this),
LauncherActivityInterface.INSTANCE::getDesktopVisibilityController),
TaskbarEduTooltipController.newInstance(this),
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.ItemInfo
@@ -26,8 +27,8 @@ import com.android.launcher3.util.CancellableTask
import com.android.quickstep.RecentsModel
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.window.flags.Flags.enableDesktopWindowingMode
import com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
import java.io.PrintWriter
/**
@@ -36,6 +37,7 @@ import java.io.PrintWriter
* - When in Desktop Mode: show the currently running (open) Tasks
*/
class TaskbarRecentAppsController(
context: Context,
private val recentsModel: RecentsModel,
// Pass a provider here instead of the actual DesktopVisibilityController instance since that
// instance might not be available when this constructor is called.
@@ -44,7 +46,7 @@ class TaskbarRecentAppsController(
// TODO(b/335401172): unify DesktopMode checks in Launcher.
var canShowRunningApps =
enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()
DESKTOP_WINDOWING_MODE.isEnabled(context) && enableDesktopWindowingTaskbarRunningApps()
@VisibleForTesting
set(isEnabledFromTest) {
field = isEnabledFromTest
@@ -64,10 +64,10 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FALLBACK;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -201,6 +201,8 @@ import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import kotlin.Unit;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -213,8 +215,6 @@ import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import kotlin.Unit;
public class QuickstepLauncher extends Launcher implements RecentsViewContainer {
private static final boolean TRACE_LAYOUTS =
SystemProperties.getBoolean("persist.debug.trace_layouts", false);
@@ -276,7 +276,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer
// TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher
OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver(
asContext(), deviceState);
if (enableDesktopWindowingMode()) {
if (DESKTOP_WINDOWING_MODE.isEnabled(this)) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
getDepthController());
@@ -296,7 +296,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
if (enableDesktopWindowingMode()) {
if (DESKTOP_WINDOWING_MODE.isEnabled(this)) {
mDesktopVisibilityController = new DesktopVisibilityController(this);
mDesktopVisibilityController.registerSystemUiListener();
mSplitSelectStateController.initSplitFromDesktopController(this,
@@ -1004,7 +1004,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer
@Override
public void setResumed() {
if (!enableDesktopWindowingWallpaperActivity()
if (!WALLPAPER_ACTIVITY.isEnabled(this)
&& mDesktopVisibilityController != null
&& mDesktopVisibilityController.areDesktopTasksVisible()
&& !mDesktopVisibilityController.isRecentsGestureInProgress()) {
@@ -62,6 +62,8 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -152,6 +154,8 @@ import com.android.window.flags.Flags;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
import kotlin.Unit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -161,8 +165,6 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Consumer;
import kotlin.Unit;
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -952,7 +954,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
if (targets.hasDesktopTasks()) {
if (targets.hasDesktopTasks(mContext)) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
int untrimmedAppCount = mRemoteTargetHandles.length;
@@ -1272,8 +1274,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
TaskView currentPageTaskView = mRecentsView != null
? mRecentsView.getCurrentPageTaskView() : null;
if (Flags.enableDesktopWindowingMode()
&& !(Flags.enableDesktopWindowingWallpaperActivity()
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
&& !(WALLPAPER_ACTIVITY.isEnabled(mContext)
&& Flags.enableDesktopWindowingQuickSwitch())) {
if ((nextPageTaskView instanceof DesktopTaskView
|| currentPageTaskView instanceof DesktopTaskView)
@@ -1445,8 +1447,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
setClampScrollOffset(false);
};
if (Flags.enableDesktopWindowingMode()
&& !(Flags.enableDesktopWindowingWallpaperActivity()
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
&& !(WALLPAPER_ACTIVITY.isEnabled(mContext)
&& Flags.enableDesktopWindowingQuickSwitch())) {
if (mRecentsView != null && (mRecentsView.getCurrentPageTaskView() != null
&& !(mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView))) {
@@ -2293,8 +2295,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
mRecentsAnimationController, mRecentsAnimationTargets);
});
if (Flags.enableDesktopWindowingMode()
&& !(Flags.enableDesktopWindowingWallpaperActivity()
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
&& !(WALLPAPER_ACTIVITY.isEnabled(mContext)
&& Flags.enableDesktopWindowingQuickSwitch())) {
if (mRecentsView.getNextPageTaskView() instanceof DesktopTaskView
|| mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView) {
@@ -20,13 +20,14 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.Process;
import android.os.RemoteException;
import android.util.SparseBooleanArray;
@@ -58,6 +59,7 @@ public class RecentTasksList {
private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
private final Context mContext;
private final KeyguardManager mKeyguardManager;
private final LooperExecutor mMainThreadExecutor;
private final SystemUiProxy mSysUiProxy;
@@ -76,8 +78,10 @@ public class RecentTasksList {
// Tasks are stored in order of least recently launched to most recently launched.
private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
SystemUiProxy sysUiProxy, TopTaskTracker topTaskTracker) {
public RecentTasksList(Context context, LooperExecutor mainThreadExecutor,
KeyguardManager keyguardManager, SystemUiProxy sysUiProxy,
TopTaskTracker topTaskTracker) {
mContext = context;
mMainThreadExecutor = mainThreadExecutor;
mKeyguardManager = keyguardManager;
mChangeId = 1;
@@ -325,9 +329,9 @@ public class RecentTasksList {
int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
if (rawTask.getType() == TYPE_FREEFORM) {
// TYPE_FREEFORM tasks is only created when enableDesktopWindowingMode() is true,
// TYPE_FREEFORM tasks is only created whenDESKTOP_WINDOWING_MODE.isEnabled is true,
// leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
if (enableDesktopWindowingMode()) {
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)) {
GroupTask desktopTask = createDesktopTask(rawTask);
if (desktopTask != null) {
allTasks.add(desktopTask);
@@ -27,7 +27,7 @@ import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_O
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -147,7 +147,7 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> implem
mActionsView = findViewById(R.id.overview_actions_view);
getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
mDragLayer.recreateControllers();
if (enableDesktopWindowingMode()) {
if (DESKTOP_WINDOWING_MODE.isEnabled(this)) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
null /* depthController */
@@ -18,9 +18,10 @@ package com.android.quickstep;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
@@ -54,8 +55,8 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets {
*
* @return {@code true} if at least one target app is a desktop task
*/
public boolean hasDesktopTasks() {
if (!enableDesktopWindowingMode()) {
public boolean hasDesktopTasks(Context context) {
if (!DESKTOP_WINDOWING_MODE.isEnabled(context)) {
return false;
}
for (RemoteAnimationTarget target : apps) {
@@ -90,7 +90,9 @@ public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
private RecentsModel(Context context, IconProvider iconProvider) {
this(context,
new RecentTasksList(MAIN_EXECUTOR,
new RecentTasksList(
context,
MAIN_EXECUTOR,
context.getSystemService(KeyguardManager.class),
SystemUiProxy.INSTANCE.get(context),
TopTaskTracker.INSTANCE.get(context)),
@@ -23,8 +23,8 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -1444,7 +1444,8 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable {
private boolean shouldEnableRunningTasksForDesktopMode() {
// TODO(b/335401172): unify DesktopMode checks in Launcher
return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps();
return DESKTOP_WINDOWING_MODE.isEnabled(mContext)
&& enableDesktopWindowingTaskbarRunningApps();
}
private boolean handleMessageAsync(Message msg) {
@@ -17,18 +17,28 @@
package com.android.quickstep.util;
import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.animation.AnimatorSet;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.view.animation.Interpolator;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.views.RecentsViewContainer;
/**
* Utility class containing methods to help manage animations, interpolators, and timings.
*/
public class AnimUtils {
private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350;
/**
* Fetches device-specific timings for the Overview > Split animation
* (splitscreen initiated from Overview).
@@ -58,6 +68,33 @@ public class AnimUtils {
: SplitAnimationTimings.PHONE_APP_PAIR_LAUNCH;
}
/**
* Synchronizes the timing for the split dismiss animation to the current transition to
* NORMAL (launcher home/workspace)
*/
public static void goToNormalStateWithSplitDismissal(@NonNull StateManager stateManager,
@NonNull RecentsViewContainer container,
@NonNull StatsLogManager.LauncherEvent exitReason,
@NonNull SplitAnimationController animationController) {
StateAnimationConfig config = new StateAnimationConfig();
BaseState startState = stateManager.getState();
long duration = startState.getTransitionDuration(container.asContext(),
false /*isToState*/);
if (duration == 0) {
// Case where we're in contextual on workspace (NORMAL), which by default has 0
// transition duration
duration = DURATION_DEFAULT_SPLIT_DISMISS;
}
config.duration = duration;
AnimatorSet stateAnim = stateManager.createAtomicAnimation(
startState, NORMAL, config);
AnimatorSet dismissAnim = animationController
.createPlaceholderDismissAnim(container, exitReason, duration);
stateAnim.play(dismissAnim);
stateManager.setCurrentAnimation(stateAnim, NORMAL);
stateAnim.start();
}
/**
* Returns a IRemoteCallback which completes the provided list as a result
*/
@@ -26,7 +26,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.annotation.TargetApi;
import android.content.Context;
@@ -54,6 +54,7 @@ import com.android.quickstep.GestureState;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimUtils;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.systemui.shared.recents.model.Task;
@@ -91,10 +92,12 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
protected void handleStartHome(boolean animated) {
StateManager stateManager = getStateManager();
animated &= stateManager.shouldAnimateStateChange();
stateManager.goToState(NORMAL, animated);
if (FeatureFlags.enableSplitContextually()) {
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(mContainer, LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
if (mSplitSelectStateController.isSplitSelectActive()) {
AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer,
LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
mSplitSelectStateController.getSplitAnimationController());
} else {
stateManager.goToState(NORMAL, animated);
}
AbstractFloatingView.closeAllOpenViews(mContainer, animated);
}
@@ -265,7 +268,7 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
super.onGestureAnimationStart(runningTasks, rotationTouchHelper);
DesktopVisibilityController desktopVisibilityController =
mContainer.getDesktopVisibilityController();
if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) {
if (!WALLPAPER_ACTIVITY.isEnabled(mContext) && desktopVisibilityController != null) {
// TODO: b/333533253 - Remove after flag rollout
desktopVisibilityController.setRecentsGestureStart();
}
@@ -288,7 +291,7 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
}
}
super.onGestureAnimationEnd();
if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) {
if (!WALLPAPER_ACTIVITY.isEnabled(mContext) && desktopVisibilityController != null) {
// TODO: b/333533253 - Remove after flag rollout
desktopVisibilityController.setRecentsGestureEnd(endTarget);
}
@@ -5498,7 +5498,7 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
}
RemoteTargetGluer gluer;
if (recentsAnimationTargets.hasDesktopTasks()) {
if (recentsAnimationTargets.hasDesktopTasks(mContext)) {
gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
true /* forDesktop */);
mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
@@ -16,13 +16,11 @@
package com.android.quickstep.views;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
import static com.android.settingslib.widget.theme.R.dimen.settingslib_preferred_minimum_touch_target;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -42,9 +40,8 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.quickstep.util.AnimUtils;
import com.android.quickstep.util.SplitSelectStateController;
/**
@@ -57,7 +54,6 @@ import com.android.quickstep.util.SplitSelectStateController;
public class SplitInstructionsView extends LinearLayout {
private static final int BOUNCE_DURATION = 250;
private static final float BOUNCE_HEIGHT = 20;
private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350;
private final RecentsViewContainer mContainer;
public boolean mIsCurrentlyAnimating = false;
@@ -165,25 +161,11 @@ public class SplitInstructionsView extends LinearLayout {
private void exitSplitSelection() {
RecentsView recentsView = mContainer.getOverviewPanel();
SplitSelectStateController splitSelectController = recentsView.getSplitSelectController();
StateManager stateManager = recentsView.getStateManager();
BaseState startState = stateManager.getState();
long duration = startState.getTransitionDuration(mContainer.asContext(), false);
if (duration == 0) {
// Case where we're in contextual on workspace (NORMAL), which by default has 0
// transition duration
duration = DURATION_DEFAULT_SPLIT_DISMISS;
}
StateAnimationConfig config = new StateAnimationConfig();
config.duration = duration;
AnimatorSet stateAnim = stateManager.createAtomicAnimation(
startState, NORMAL, config);
AnimatorSet dismissAnim = splitSelectController.getSplitAnimationController()
.createPlaceholderDismissAnim(mContainer,
LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, duration);
stateAnim.play(dismissAnim);
stateManager.setCurrentAnimation(stateAnim, NORMAL);
stateAnim.start();
AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer,
LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON,
splitSelectController.getSplitAnimationController());
}
void ensureProperRotation() {
@@ -18,6 +18,7 @@ package com.android.launcher3.taskbar
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Process
import android.os.UserHandle
@@ -56,6 +57,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
@Mock private lateinit var mockIconCache: TaskIconCache
@Mock private lateinit var mockRecentsModel: RecentsModel
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
private var taskListChangeId: Int = 1
@@ -71,7 +73,9 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
whenever(mockRecentsModel.iconCache).thenReturn(mockIconCache)
recentAppsController =
TaskbarRecentAppsController(mockRecentsModel) { mockDesktopVisibilityController }
TaskbarRecentAppsController(mockContext, mockRecentsModel) {
mockDesktopVisibilityController
}
recentAppsController.init(taskbarControllers)
recentAppsController.canShowRunningApps = true
recentAppsController.canShowRecentApps = true
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.Context;
import androidx.test.filters.SmallTest;
@@ -54,7 +55,9 @@ import java.util.stream.Collectors;
public class RecentTasksListTest {
@Mock
private SystemUiProxy mockSystemUiProxy;
private Context mContext;
@Mock
private SystemUiProxy mSystemUiProxy;
@Mock
private TopTaskTracker mTopTaskTracker;
@@ -66,14 +69,14 @@ public class RecentTasksListTest {
MockitoAnnotations.initMocks(this);
LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager,
mockSystemUiProxy, mTopTaskTracker);
mRecentTasksList = new RecentTasksList(mContext, mockMainThreadExecutor,
mockKeyguardManager, mSystemUiProxy, mTopTaskTracker);
}
@Test
public void onRecentTasksChanged_doesNotFetchTasks() throws Exception {
mRecentTasksList.onRecentTasksChanged();
verify(mockSystemUiProxy, times(0))
verify(mSystemUiProxy, times(0))
.getRecentTasks(anyInt(), anyInt());
}
@@ -81,7 +84,7 @@ public class RecentTasksListTest {
public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception {
GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(
new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null);
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
@@ -94,7 +97,7 @@ public class RecentTasksListTest {
@Test
public void loadTasksInBackground_GetRecentTasksException() throws Exception {
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed"));
RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground(
@@ -113,7 +116,7 @@ public class RecentTasksListTest {
task2.taskDescription = new ActivityManager.TaskDescription();
GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(task1, task2,
null);
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
@@ -132,7 +135,7 @@ public class RecentTasksListTest {
createRecentTaskInfo(5 /* taskId */)};
GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forFreeformTasks(
tasks, Collections.emptySet() /* minimizedTaskIds */);
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
@@ -158,7 +161,7 @@ public class RecentTasksListTest {
Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
GroupedRecentTaskInfo recentTaskInfos =
GroupedRecentTaskInfo.forFreeformTasks(tasks, minimizedTaskIds);
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
-3
View File
@@ -22,8 +22,5 @@
<stroke
android:width="1dp"
android:color="?attr/materialColorSurfaceContainerLow" />
<padding
android:left="@dimen/rounded_button_padding"
android:right="@dimen/rounded_button_padding" />
</shape>
+1 -2
View File
@@ -44,8 +44,7 @@
<FrameLayout
android:layout_width="@dimen/rounded_button_width"
android:layout_height="@dimen/rounded_button_width"
android:background="@drawable/rounded_action_button"
android:padding="@dimen/rounded_button_padding">
android:background="@drawable/rounded_action_button">
<ImageButton
android:id="@+id/action_btn"
android:layout_width="@dimen/x_icon_size"
-2
View File
@@ -172,8 +172,6 @@
<dimen name="padded_rounded_button_height">48dp</dimen>
<dimen name="rounded_button_height">48dp</dimen>
<dimen name="rounded_button_radius">200dp</dimen>
<dimen name="rounded_button_padding">8dp</dimen>
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
+2 -4
View File
@@ -746,7 +746,8 @@ public final class Utilities {
* | +--+ |
* | |
* +----------------+
* This would be case delta % 4 == 2:
* This would be case delta % 4 == 2: // This is case was reverted to previous behaviour which
* doesn't match the illustration due to b/353965234
* +-------------+
* | |
* | |
@@ -768,7 +769,6 @@ public final class Utilities {
int delta) {
int rdelta = ((delta % 4) + 4) % 4;
int origLeft = inOutBounds.left;
int origTop = inOutBounds.top;
switch (rdelta) {
case 0:
return;
@@ -780,8 +780,6 @@ public final class Utilities {
return;
case 2:
inOutBounds.left = parentWidth - inOutBounds.right;
inOutBounds.top = parentHeight - inOutBounds.bottom;
inOutBounds.bottom = parentHeight - origTop;
inOutBounds.right = parentWidth - origLeft;
return;
case 3:
@@ -262,11 +262,12 @@ public class AllAppsStore<T extends Context & ActivityContext> {
writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length);
for (int i = 0; i < mApps.length; i++) {
writer.println(String.format(Locale.getDefault(),
"%s\tPackage index, name, and class: " + "%d/%s:%s",
"%s\tPackage index, name, class, and description: %d/%s:%s, %s",
prefix,
i,
mApps[i].componentName.getPackageName(),
mApps[i].componentName.getClassName()));
mApps[i].componentName.getClassName(),
mApps[i].contentDescription));
}
}
}
@@ -32,7 +32,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.PackageUserKey;
import java.lang.ref.WeakReference;
@@ -183,6 +183,11 @@ public class UserCache implements SafeCloseable {
mUserToSerialMap.put(userHandle, info);
}
@VisibleForTesting
public void putToPreInstallCache(UserHandle userHandle, List<String> preInstalledApps) {
mUserToPreInstallAppMap.put(userHandle, preInstalledApps);
}
/**
* @see UserManager#getUserProfiles()
*/
@@ -76,4 +76,5 @@ public class LogConfig {
* When turned on, we enable zero state web data loader related logging.
*/
public static final String ZERO_WEB_DATA_LOADER = "ZeroStateWebDataLoaderLog";
public static final String SEARCH_TARGET_UTIL_LOG = "SearchTargetUtilLog";
}
@@ -376,9 +376,10 @@ class UtilitiesTest {
Utilities.rotateBounds(rect, 100, 100, 1)
assertEquals(Rect(70, 40, 80, 80), rect)
rect = Rect(20, 70, 60, 80)
Utilities.rotateBounds(rect, 100, 100, 2)
assertEquals(Rect(40, 20, 80, 30), rect)
// case removed for b/28435189
// rect = Rect(20, 70, 60, 80)
// Utilities.rotateBounds(rect, 100, 100, 2)
// assertEquals(Rect(40, 20, 80, 30), rect)
rect = Rect(20, 70, 60, 80)
Utilities.rotateBounds(rect, 100, 100, 3)
@@ -23,6 +23,7 @@ import android.os.Handler
import android.os.Looper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
@@ -114,6 +115,7 @@ class SimpleBroadcastReceiverTest {
underTest = SimpleBroadcastReceiver(Handler(Looper.getMainLooper()), intentConsumer)
underTest.register(context, completionRunnable, 1, "test_action_1", "test_action_2")
getInstrumentation().waitForIdleSync()
verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture(), eq(1))
verify(completionRunnable).run()
@@ -136,6 +138,7 @@ class SimpleBroadcastReceiverTest {
underTest = SimpleBroadcastReceiver(Handler(Looper.getMainLooper()), intentConsumer)
underTest.unregisterReceiverSafely(context)
getInstrumentation().waitForIdleSync()
verify(context).unregisterReceiver(same(underTest))
}
@@ -0,0 +1,201 @@
/*
* 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.launcher3.folder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.folder.FolderNameInfos.*
import org.junit.Test
import org.junit.runner.RunWith
data class Label(val index: Int, val label: String, val score: Float)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FolderNameInfosTest {
companion object {
val statusList =
listOf(
SUCCESS,
HAS_PRIMARY,
HAS_SUGGESTIONS,
ERROR_NO_PROVIDER,
ERROR_APP_LOOKUP_FAILED,
ERROR_ALL_APP_LOOKUP_FAILED,
ERROR_NO_LABELS_GENERATED,
ERROR_LABEL_LOOKUP_FAILED,
ERROR_ALL_LABEL_LOOKUP_FAILED,
ERROR_NO_PACKAGES,
)
}
@Test
fun status() {
assertStatus(statusList)
assertStatus(
listOf(
ERROR_NO_PROVIDER,
ERROR_APP_LOOKUP_FAILED,
ERROR_ALL_APP_LOOKUP_FAILED,
ERROR_NO_LABELS_GENERATED,
ERROR_LABEL_LOOKUP_FAILED,
ERROR_ALL_LABEL_LOOKUP_FAILED,
ERROR_NO_PACKAGES,
)
)
assertStatus(
listOf(
SUCCESS,
HAS_PRIMARY,
HAS_SUGGESTIONS,
)
)
assertStatus(
listOf(
SUCCESS,
HAS_PRIMARY,
HAS_SUGGESTIONS,
)
)
}
fun assertStatus(statusList: List<Int>) {
var infos = FolderNameInfos()
statusList.forEach { infos.setStatus(it) }
assert(infos.status() == statusList.sum()) {
"There is an overlap on the status constants!"
}
}
@Test
fun hasPrimary() {
assertHasPrimary(
createNameInfos(listOf(Label(0, "label", 1f)), statusList),
hasPrimary = true
)
assertHasPrimary(
createNameInfos(listOf(Label(1, "label", 1f)), statusList),
hasPrimary = false
)
assertHasPrimary(
createNameInfos(
listOf(Label(0, "label", 1f)),
listOf(
ERROR_NO_PROVIDER,
ERROR_APP_LOOKUP_FAILED,
ERROR_ALL_APP_LOOKUP_FAILED,
ERROR_NO_LABELS_GENERATED,
ERROR_LABEL_LOOKUP_FAILED,
ERROR_ALL_LABEL_LOOKUP_FAILED,
ERROR_NO_PACKAGES,
)
),
hasPrimary = false
)
}
private fun assertHasPrimary(nameInfos: FolderNameInfos, hasPrimary: Boolean) =
assert(nameInfos.hasPrimary() == hasPrimary)
private fun createNameInfos(labels: List<Label>?, statusList: List<Int>?): FolderNameInfos {
val infos = FolderNameInfos()
labels?.forEach { infos.setLabel(it.index, it.label, it.score) }
statusList?.forEach { infos.setStatus(it) }
return infos
}
@Test
fun hasSuggestions() {
assertHasSuggestions(
createNameInfos(listOf(Label(0, "label", 1f)), null),
hasSuggestions = true
)
assertHasSuggestions(createNameInfos(null, null), hasSuggestions = false)
// There is a max of 4 suggestions
assertHasSuggestions(
createNameInfos(listOf(Label(5, "label", 1f)), null),
hasSuggestions = false
)
assertHasSuggestions(
createNameInfos(
listOf(
Label(0, "label", 1f),
Label(1, "label", 1f),
Label(2, "label", 1f),
Label(3, "label", 1f)
),
null
),
hasSuggestions = true
)
}
private fun assertHasSuggestions(nameInfos: FolderNameInfos, hasSuggestions: Boolean) =
assert(nameInfos.hasSuggestions() == hasSuggestions)
@Test
fun hasContains() {
assertContains(
createNameInfos(
listOf(
Label(0, "label1", 1f),
Label(1, "label2", 1f),
Label(2, "label3", 1f),
Label(3, "label4", 1f)
),
null
),
label = Label(-1, "label3", -1f),
contains = true
)
assertContains(
createNameInfos(
listOf(
Label(0, "label1", 1f),
Label(1, "label2", 1f),
Label(2, "label3", 1f),
Label(3, "label4", 1f)
),
null
),
label = Label(-1, "label5", -1f),
contains = false
)
assertContains(
createNameInfos(null, null),
label = Label(-1, "label1", -1f),
contains = false
)
assertContains(
createNameInfos(
listOf(
Label(0, "label1", 1f),
Label(1, "label2", 1f),
Label(2, "lAbel3", 1f),
Label(3, "lEbel4", 1f)
),
null
),
label = Label(-1, "LaBEl3", -1f),
contains = true
)
}
private fun assertContains(nameInfos: FolderNameInfos, label: Label, contains: Boolean) =
assert(nameInfos.contains(label.label) == contains)
}
@@ -0,0 +1,228 @@
/*
* 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.launcher3.pm
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.os.Build
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.PackageUserKey
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class InstallSessionTrackerTest {
@get:Rule val setFlagsRule = SetFlagsRule()
private val mockInstallSessionHelper: InstallSessionHelper = mock()
private val mockCallback: InstallSessionTracker.Callback = mock()
private val mockPackageInstaller: PackageInstaller = mock()
private val launcherModelHelper = LauncherModelHelper()
private val sandboxContext = launcherModelHelper.sandboxContext
lateinit var launcherApps: LauncherApps
lateinit var installSessionTracker: InstallSessionTracker
@Before
fun setup() {
launcherApps = sandboxContext.spyService(LauncherApps::class.java)
installSessionTracker =
InstallSessionTracker(
mockInstallSessionHelper,
mockCallback,
mockPackageInstaller,
launcherApps
)
}
@After
fun teardown() {
launcherModelHelper.destroy()
}
@Test
fun `onCreated triggers callbacks for setting up new install session`() {
// Given
val expectedSessionId = 1
val expectedSession =
PackageInstaller.SessionInfo().apply {
sessionId = expectedSessionId
appPackageName = "appPackageName"
userId = 0
}
val expectedPackageKey = PackageUserKey("appPackageName", UserHandle(0))
whenever(mockInstallSessionHelper.getVerifiedSessionInfo(expectedSessionId))
.thenReturn(expectedSession)
// When
installSessionTracker.onCreated(expectedSessionId)
// Then
verify(mockCallback).onInstallSessionCreated(any())
verify(mockCallback).onUpdateSessionDisplay(expectedPackageKey, expectedSession)
verify(mockInstallSessionHelper).tryQueuePromiseAppIcon(expectedSession)
}
@Test
@EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
fun `onCreated for unarchival triggers onPackageStateChanged`() {
// Given
val expectedSessionId = 1
val expectedSession =
spy(PackageInstaller.SessionInfo()).apply {
sessionId = expectedSessionId
appPackageName = "appPackageName"
userId = 0
whenever(isUnarchival).thenReturn(true)
}
whenever(mockInstallSessionHelper.getVerifiedSessionInfo(expectedSessionId))
.thenReturn(expectedSession)
// When
installSessionTracker.onCreated(expectedSessionId)
// Then
verify(mockCallback).onPackageStateChanged(any())
}
@Test
fun `onFinished triggers onPackageStateChanged if session found in cache`() {
// Given
val expectedSessionId = 1
val expectedSession =
PackageInstaller.SessionInfo().apply {
sessionId = expectedSessionId
appPackageName = "appPackageName"
userId = 0
}
val expectedPackageKey = PackageUserKey("appPackageName", UserHandle(0))
whenever(mockInstallSessionHelper.getVerifiedSessionInfo(expectedSessionId))
.thenReturn(expectedSession)
whenever(mockInstallSessionHelper.activeSessions)
.thenReturn(hashMapOf(expectedPackageKey to expectedSession))
// When
installSessionTracker.onFinished(expectedSessionId, /* success */ true)
// Then
verify(mockCallback).onPackageStateChanged(any())
}
@Test
fun `onFinished failure calls onSessionFailure and promise icon removal for existing icon`() {
// Given
val expectedSessionId = 1
val expectedPackage = "appPackageName"
val expectedSession =
PackageInstaller.SessionInfo().apply {
sessionId = expectedSessionId
appPackageName = expectedPackage
userId = 0
}
val expectedPackageKey = PackageUserKey(expectedPackage, UserHandle(0))
whenever(mockInstallSessionHelper.getVerifiedSessionInfo(expectedSessionId))
.thenReturn(expectedSession)
whenever(mockInstallSessionHelper.activeSessions)
.thenReturn(hashMapOf(expectedPackageKey to expectedSession))
whenever(mockInstallSessionHelper.promiseIconAddedForId(expectedSessionId)).thenReturn(true)
// When
installSessionTracker.onFinished(expectedSessionId, /* success */ false)
// Then
verify(mockCallback).onSessionFailure(expectedPackage, expectedPackageKey.mUser)
verify(mockInstallSessionHelper).removePromiseIconId(expectedSessionId)
}
@Test
fun `onProgressChanged triggers onPackageStateChanged if verified session found`() {
// Given
val expectedSessionId = 1
val expectedSession =
PackageInstaller.SessionInfo().apply {
sessionId = expectedSessionId
appPackageName = "appPackageName"
userId = 0
}
whenever(mockInstallSessionHelper.getVerifiedSessionInfo(expectedSessionId))
.thenReturn(expectedSession)
// When
installSessionTracker.onProgressChanged(expectedSessionId, /* progress */ 50f)
// Then
verify(mockCallback).onPackageStateChanged(any())
}
@Test
fun `onBadgingChanged triggers session display update and queues promise icon if verified`() {
// Given
val expectedSessionId = 1
val expectedSession =
PackageInstaller.SessionInfo().apply {
sessionId = expectedSessionId
appPackageName = "appPackageName"
userId = 0
}
val expectedPackageKey = PackageUserKey("appPackageName", UserHandle(0))
whenever(mockInstallSessionHelper.getVerifiedSessionInfo(expectedSessionId))
.thenReturn(expectedSession)
// When
installSessionTracker.onBadgingChanged(expectedSessionId)
// Then
verify(mockCallback).onUpdateSessionDisplay(expectedPackageKey, expectedSession)
verify(mockInstallSessionHelper).tryQueuePromiseAppIcon(expectedSession)
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun `register triggers registerPackageInstallerSessionCallback for versions from Q`() {
// Given
whenever(
launcherApps.registerPackageInstallerSessionCallback(
MODEL_EXECUTOR,
installSessionTracker
)
)
.then { /* no-op */ }
// When
installSessionTracker.register()
// Then
verify(launcherApps)
.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, installSessionTracker)
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun `unregister triggers unregisterPackageInstallerSessionCallback for versions from Q`() {
// Given
whenever(launcherApps.unregisterPackageInstallerSessionCallback(installSessionTracker))
.then { /* no-op */ }
// When
installSessionTracker.unregister()
// Then
verify(launcherApps).unregisterPackageInstallerSessionCallback(installSessionTracker)
}
}
@@ -0,0 +1,140 @@
/*
* 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.launcher3.pm
import android.os.Process.myUserHandle
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class UserCacheTest {
private val launcherModelHelper = LauncherModelHelper()
private val sandboxContext = launcherModelHelper.sandboxContext
private lateinit var userCache: UserCache
@Before
fun setup() {
userCache = UserCache.getInstance(sandboxContext)
}
@After
fun teardown() {
launcherModelHelper.destroy()
}
@Test
fun `getBadgeDrawable only returns a UserBadgeDrawable given a user in the cache`() {
// Given
val expectedIconInfo = UserIconInfo(myUserHandle(), UserIconInfo.TYPE_WORK)
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
userCache.putToCache(myUserHandle(), expectedIconInfo)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// When
val actualDrawable = UserCache.getBadgeDrawable(sandboxContext, myUserHandle())
val unexpectedDrawable = UserCache.getBadgeDrawable(sandboxContext, UserHandle(66))
// Then
assertThat(actualDrawable).isNotNull()
assertThat(unexpectedDrawable).isNull()
}
@Test
fun `getPreInstallApps returns list of pre installed apps given a user`() {
// Given
val expectedApps = listOf("Google")
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
userCache.putToPreInstallCache(myUserHandle(), expectedApps)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// When
val actualApps = userCache.getPreInstallApps(myUserHandle())
// Then
assertThat(actualApps).isEqualTo(expectedApps)
}
@Test
fun `getUserProfiles returns copy of UserCache profiles`() {
// Given
val expectedProfiles = listOf(myUserHandle())
val expectedIconInfo = UserIconInfo(myUserHandle(), UserIconInfo.TYPE_MAIN)
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
userCache.putToCache(myUserHandle(), expectedIconInfo)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// When
val actualProfiles = userCache.userProfiles
// Then
assertThat(actualProfiles).isEqualTo(expectedProfiles)
}
@Test
fun `getUserForSerialNumber returns user key matching given entry serial number`() {
// Given
val expectedSerial = 42L
val expectedProfile = UserHandle(42)
val expectedIconInfo = UserIconInfo(myUserHandle(), UserIconInfo.TYPE_MAIN, expectedSerial)
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
userCache.putToCache(expectedProfile, expectedIconInfo)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// When
val actualProfile = userCache.getUserForSerialNumber(expectedSerial)
// Then
assertThat(actualProfile).isEqualTo(expectedProfile)
}
@Test
fun `getUserInfo returns cached UserIconInfo given user key`() {
// Given
val expectedProfile = UserHandle(1)
val expectedIconInfo = UserIconInfo(myUserHandle(), UserIconInfo.TYPE_WORK)
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
userCache.putToCache(expectedProfile, expectedIconInfo)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// When
val actualIconInfo = userCache.getUserInfo(expectedProfile)
// Then
assertThat(actualIconInfo).isEqualTo(expectedIconInfo)
}
@Test
fun `getSerialNumberForUser returns cached UserIconInfo serial number given user key`() {
// Given
val expectedSerial = 42L
val expectedProfile = UserHandle(1)
val expectedIconInfo = UserIconInfo(myUserHandle(), UserIconInfo.TYPE_WORK, expectedSerial)
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
userCache.putToCache(expectedProfile, expectedIconInfo)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// When
val actualSerial = userCache.getSerialNumberForUser(expectedProfile)
// Then
assertThat(actualSerial).isEqualTo(expectedSerial)
}
}