Snap for 12667701 from 15d689df48 to 25Q1-release
Change-Id: Ib4f2bf4328cbf5eb15e0168f9f634124acada522
This commit is contained in:
@@ -217,6 +217,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
public static final int CONTENT_ALPHA_DURATION = 217;
|
||||
public static final int TRANSIENT_TASKBAR_TRANSITION_DURATION = 417;
|
||||
public static final int PINNED_TASKBAR_TRANSITION_DURATION = 600;
|
||||
public static final int TASKBAR_TO_APP_DURATION = 600;
|
||||
// TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
|
||||
// is solved.
|
||||
@@ -1745,8 +1746,21 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
return new AnimatorBackState(rectFSpringAnim, anim);
|
||||
}
|
||||
|
||||
public static int getTaskbarToHomeDuration() {
|
||||
if (enableScalingRevealHomeAnimation()) {
|
||||
/** Get animation duration for taskbar for going to home. */
|
||||
public static int getTaskbarToHomeDuration(boolean isPinnedTaskbar) {
|
||||
return getTaskbarToHomeDuration(false, isPinnedTaskbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get animation duration for taskbar for going to home.
|
||||
*
|
||||
* @param shouldOverrideToFastAnimation should overwrite scaling reveal home animation duration
|
||||
*/
|
||||
public static int getTaskbarToHomeDuration(boolean shouldOverrideToFastAnimation,
|
||||
boolean isPinnedTaskbar) {
|
||||
if (isPinnedTaskbar) {
|
||||
return PINNED_TASKBAR_TRANSITION_DURATION;
|
||||
} else if (enableScalingRevealHomeAnimation() && !shouldOverrideToFastAnimation) {
|
||||
return TASKBAR_TO_HOME_DURATION_SLOW;
|
||||
} else {
|
||||
return TASKBAR_TO_HOME_DURATION_FAST;
|
||||
|
||||
@@ -488,6 +488,15 @@ public class DesktopVisibilityController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onEnterDesktopModeTransitionStarted(int transitionDuration) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExitDesktopModeTransitionStarted(int transitionDuration) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** A listener for Taskbar in Desktop Mode. */
|
||||
|
||||
@@ -212,8 +212,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
private int getTaskbarAnimationDuration(boolean isVisible) {
|
||||
if (isVisible && !mLauncher.getPredictiveBackToHomeInProgress()) {
|
||||
return getTaskbarToHomeDuration();
|
||||
// fast animation duration since we will not be playing workspace reveal animation.
|
||||
boolean shouldOverrideToFastAnimation =
|
||||
!isHotseatIconOnTopWhenAligned() || mLauncher.getPredictiveBackToHomeInProgress();
|
||||
boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mLauncher);
|
||||
if (isVisible || isPinnedTaskbar) {
|
||||
return getTaskbarToHomeDuration(shouldOverrideToFastAnimation, isPinnedTaskbar);
|
||||
} else {
|
||||
return DisplayController.isTransientTaskbar(mLauncher)
|
||||
? TRANSIENT_TASKBAR_TRANSITION_DURATION
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
|
||||
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT;
|
||||
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH;
|
||||
@@ -222,7 +223,9 @@ public class TaskbarLauncherStateController {
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
|
||||
if (!mShouldDelayLauncherStateAnim) {
|
||||
if (toState == LauncherState.NORMAL) {
|
||||
applyState(QuickstepTransitionManager.getTaskbarToHomeDuration());
|
||||
applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
|
||||
DisplayController.isPinnedTaskbar(
|
||||
mControllers.taskbarActivityContext)));
|
||||
} else {
|
||||
applyState();
|
||||
}
|
||||
@@ -459,9 +462,12 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
|
||||
final boolean isInLauncher = isInLauncher();
|
||||
final boolean isInOverview = mControllers.uiController.isInOverviewUi();
|
||||
final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat();
|
||||
final float toAlignment = isIconAlignedWithHotseat ? 1 : 0;
|
||||
boolean handleOpenFloatingViews = false;
|
||||
boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(
|
||||
mControllers.taskbarActivityContext);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher
|
||||
+ ", mLauncherState: " + mLauncherState
|
||||
@@ -573,10 +579,17 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
|
||||
float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1;
|
||||
AnimatedFloat taskbarBgOffset =
|
||||
mControllers.taskbarDragLayerController.getTaskbarBackgroundOffset();
|
||||
boolean showTaskbar = !isInLauncher || isInOverview;
|
||||
float taskbarBgOffsetEnd = showTaskbar ? 0f : 1f;
|
||||
float taskbarBgOffsetStart = showTaskbar ? 1f : 0f;
|
||||
|
||||
// Don't animate if background has reached desired value.
|
||||
if (mTaskbarBackgroundAlpha.isAnimating()
|
||||
|| mTaskbarBackgroundAlpha.value != backgroundAlpha) {
|
||||
|| mTaskbarBackgroundAlpha.value != backgroundAlpha
|
||||
|| taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)
|
||||
|| taskbarBgOffset.value != taskbarBgOffsetEnd) {
|
||||
mTaskbarBackgroundAlpha.cancelAnimation();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
|
||||
@@ -587,25 +600,35 @@ public class TaskbarLauncherStateController {
|
||||
boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat;
|
||||
boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat;
|
||||
boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
|
||||
// When Hotseat icons are not on top don't change duration or add start delay.
|
||||
// This will keep the duration in sync for icon alignment and background fade in/out.
|
||||
// For example, launching app from launcher all apps.
|
||||
boolean isHotseatIconOnTopWhenAligned =
|
||||
mControllers.uiController.isHotseatIconOnTopWhenAligned();
|
||||
|
||||
float startDelay = 0;
|
||||
// We want to delay the background from fading in so that the icons have time to move
|
||||
// into the bounds of the background before it appears.
|
||||
if (isInLauncherIconNotAligned) {
|
||||
startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
|
||||
} else if (notInLauncherIconNotAligned) {
|
||||
} else if (notInLauncherIconNotAligned && isHotseatIconOnTopWhenAligned) {
|
||||
startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
|
||||
}
|
||||
float newDuration = duration - startDelay;
|
||||
if (isInLauncherIconIsAligned) {
|
||||
if (isInLauncherIconIsAligned && isHotseatIconOnTopWhenAligned) {
|
||||
// Make the background fade out faster so that it is gone by the time the
|
||||
// icons move outside of the bounds of the background.
|
||||
newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT;
|
||||
}
|
||||
Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha
|
||||
.animateToValue(backgroundAlpha)
|
||||
.setDuration((long) newDuration);
|
||||
taskbarBackgroundAlpha.setStartDelay((long) startDelay);
|
||||
Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha.animateToValue(
|
||||
backgroundAlpha);
|
||||
if (isPinnedTaskbar) {
|
||||
setupPinnedTaskbarAnimation(animatorSet, showTaskbar, taskbarBgOffset,
|
||||
taskbarBgOffsetStart, taskbarBgOffsetEnd, duration, taskbarBackgroundAlpha);
|
||||
} else {
|
||||
taskbarBackgroundAlpha.setDuration((long) newDuration);
|
||||
taskbarBackgroundAlpha.setStartDelay((long) startDelay);
|
||||
}
|
||||
animatorSet.play(taskbarBackgroundAlpha);
|
||||
}
|
||||
|
||||
@@ -671,15 +694,18 @@ public class TaskbarLauncherStateController {
|
||||
+ mIconAlignment.value
|
||||
+ " -> " + toAlignment + ": " + duration);
|
||||
}
|
||||
if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
|
||||
iconAlignAnim.setInterpolator(FINAL_FRAME);
|
||||
} else {
|
||||
animatorSet.play(iconAlignAnim);
|
||||
if (!isPinnedTaskbar) {
|
||||
if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
|
||||
iconAlignAnim.setInterpolator(FINAL_FRAME);
|
||||
} else {
|
||||
animatorSet.play(iconAlignAnim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Interpolator interpolator = enableScalingRevealHomeAnimation()
|
||||
Interpolator interpolator = enableScalingRevealHomeAnimation() && !isPinnedTaskbar
|
||||
? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED;
|
||||
|
||||
animatorSet.setInterpolator(interpolator);
|
||||
|
||||
if (start) {
|
||||
@@ -688,6 +714,49 @@ public class TaskbarLauncherStateController {
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
private void setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar,
|
||||
AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd,
|
||||
long duration, Animator taskbarBackgroundAlpha) {
|
||||
float targetAlpha = !showTaskbar ? 1 : 0;
|
||||
mLauncher.getHotseat().setIconsAlpha(targetAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
if (mIsQsbInline) {
|
||||
mLauncher.getHotseat().setQsbAlpha(targetAlpha,
|
||||
ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
}
|
||||
|
||||
if ((taskbarBgOffset.value != taskbarBgOffsetEnd && !taskbarBgOffset.isAnimating())
|
||||
|| taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)) {
|
||||
taskbarBgOffset.cancelAnimation();
|
||||
Animator taskbarIconAlpha = mTaskbarAlphaForHome.animateToValue(
|
||||
showTaskbar ? 1f : 0f);
|
||||
AnimatedFloat taskbarIconTranslationYForHome =
|
||||
mControllers.taskbarViewController.mTaskbarIconTranslationYForHome;
|
||||
ObjectAnimator taskbarBackgroundOffset = taskbarBgOffset.animateToValue(
|
||||
taskbarBgOffsetStart,
|
||||
taskbarBgOffsetEnd);
|
||||
ObjectAnimator taskbarIconsYTranslation = null;
|
||||
float taskbarHeight =
|
||||
mControllers.taskbarActivityContext.getDeviceProfile().taskbarHeight;
|
||||
if (showTaskbar) {
|
||||
taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(
|
||||
taskbarHeight, 0);
|
||||
} else {
|
||||
taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(0,
|
||||
taskbarHeight);
|
||||
}
|
||||
|
||||
taskbarIconAlpha.setDuration(duration);
|
||||
taskbarIconsYTranslation.setDuration(duration);
|
||||
taskbarBackgroundOffset.setDuration(duration);
|
||||
|
||||
animatorSet.play(taskbarIconAlpha);
|
||||
animatorSet.play(taskbarIconsYTranslation);
|
||||
animatorSet.play(taskbarBackgroundOffset);
|
||||
}
|
||||
taskbarBackgroundAlpha.setInterpolator(showTaskbar ? INSTANT : FINAL_FRAME);
|
||||
taskbarBackgroundAlpha.setDuration(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the taskbar is aligned with the hotseat in the current/target launcher state.
|
||||
*
|
||||
@@ -940,7 +1009,12 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
|
||||
endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/);
|
||||
endGestureStateOverride(!controller.getFinishTargetIsLauncher(),
|
||||
controller.getLauncherIsVisibleAtFinish(), false /*canceled*/);
|
||||
}
|
||||
|
||||
private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
|
||||
endGestureStateOverride(finishedToApp, finishedToApp, canceled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -950,10 +1024,13 @@ public class TaskbarLauncherStateController {
|
||||
*
|
||||
* @param finishedToApp {@code true} if the recents animation finished to showing an app and
|
||||
* not workspace or overview
|
||||
* @param canceled {@code true} if the recents animation was canceled instead of finishing
|
||||
* to completion
|
||||
* @param launcherIsVisible {code true} if launcher is visible at finish
|
||||
* @param canceled {@code true} if the recents animation was canceled instead of
|
||||
* finishing
|
||||
* to completion
|
||||
*/
|
||||
private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
|
||||
private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible,
|
||||
boolean canceled) {
|
||||
mCallbacks.removeListener(this);
|
||||
mTaskBarRecentsAnimationListener = null;
|
||||
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
|
||||
@@ -962,17 +1039,27 @@ public class TaskbarLauncherStateController {
|
||||
mSkipNextRecentsAnimEnd = false;
|
||||
return;
|
||||
}
|
||||
updateStateForUserFinishedToApp(finishedToApp);
|
||||
updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visible state immediately to ensure a seamless handoff.
|
||||
* @param finishedToApp True iff user is in an app.
|
||||
* @see #updateStateForUserFinishedToApp(boolean, boolean)
|
||||
*/
|
||||
private void updateStateForUserFinishedToApp(boolean finishedToApp) {
|
||||
updateStateForUserFinishedToApp(finishedToApp, !finishedToApp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visible state immediately to ensure a seamless handoff.
|
||||
*
|
||||
* @param finishedToApp True iff user is in an app.
|
||||
* @param launcherIsVisible True iff launcher is still visible (ie. transparent app)
|
||||
*/
|
||||
private void updateStateForUserFinishedToApp(boolean finishedToApp,
|
||||
boolean launcherIsVisible) {
|
||||
// Update the visible state immediately to ensure a seamless handoff
|
||||
boolean launcherVisible = !finishedToApp;
|
||||
boolean launcherVisible = !finishedToApp || launcherIsVisible;
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
|
||||
updateStateForFlag(FLAG_VISIBLE, launcherVisible);
|
||||
applyState();
|
||||
@@ -981,7 +1068,7 @@ public class TaskbarLauncherStateController {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
|
||||
}
|
||||
controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
|
||||
controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible);
|
||||
controller.applyState();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.internal.jank.InteractionJankMonitor.Configuration;
|
||||
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.PINNED_TASKBAR_TRANSITION_DURATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
|
||||
@@ -398,6 +399,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* Returns how long the stash/unstash animation should play.
|
||||
*/
|
||||
public long getStashDuration() {
|
||||
if (DisplayController.isPinnedTaskbar(mActivity)) {
|
||||
return PINNED_TASKBAR_TRANSITION_DURATION;
|
||||
}
|
||||
return DisplayController.isTransientTaskbar(mActivity)
|
||||
? TRANSIENT_TASKBAR_STASH_DURATION
|
||||
: TASKBAR_STASH_DURATION;
|
||||
|
||||
@@ -120,7 +120,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final MultiValueAlpha mTaskbarIconAlpha;
|
||||
private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
|
||||
private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
|
||||
public final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
@@ -796,6 +796,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
*/
|
||||
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
|
||||
PendingAnimation setter = new PendingAnimation(100);
|
||||
// icon alignment not needed for pinned taskbar.
|
||||
if (DisplayController.isPinnedTaskbar(mActivity)) return setter.createPlaybackController();
|
||||
mOnControllerPreCreateCallback.run();
|
||||
DeviceProfile taskbarDp = mActivity.getDeviceProfile();
|
||||
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
|
||||
|
||||
+5
-2
@@ -95,6 +95,7 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
|
||||
StateAnimationConfig config) {
|
||||
RecentsView overview = mContainer.getOverviewPanel();
|
||||
boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContainer);
|
||||
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
|
||||
overview.switchToScreenshot(() ->
|
||||
overview.finishRecentsAnimation(true /* toRecents */, null));
|
||||
@@ -109,7 +110,8 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
// We sync the scrim fade with the taskbar animation duration to avoid any flickers for
|
||||
// taskbar icons disappearing before hotseat icons show up.
|
||||
float scrimUpperBoundFromSplit =
|
||||
QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration;
|
||||
QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
|
||||
/ (float) config.duration;
|
||||
scrimUpperBoundFromSplit = Math.min(scrimUpperBoundFromSplit, 1f);
|
||||
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
|
||||
config.setInterpolator(ANIM_SCRIM_FADE,
|
||||
@@ -139,7 +141,8 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
// Sync scroll so that it ends before or at the same time as the taskbar animation.
|
||||
if (mContainer.getDeviceProfile().isTaskbarPresent) {
|
||||
config.duration = Math.min(
|
||||
config.duration, QuickstepTransitionManager.getTaskbarToHomeDuration());
|
||||
config.duration,
|
||||
QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar));
|
||||
}
|
||||
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
|
||||
} else {
|
||||
|
||||
@@ -100,6 +100,7 @@ import com.android.internal.jank.Cuj;
|
||||
import com.android.internal.util.LatencyTracker;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
@@ -1373,8 +1374,9 @@ public abstract class AbsSwipeUpHandler<
|
||||
mInputConsumerProxy.enable();
|
||||
}
|
||||
if (endTarget == HOME) {
|
||||
boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContext);
|
||||
duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent
|
||||
? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS
|
||||
? QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
|
||||
: StaggeredWorkspaceAnim.DURATION_MS;
|
||||
ContextualEduStatsManager.INSTANCE.get(mContext).updateEduStats(
|
||||
mGestureState.isTrackpadGesture(), GestureType.HOME);
|
||||
|
||||
@@ -53,6 +53,8 @@ public class RecentsAnimationController {
|
||||
private boolean mFinishRequested = false;
|
||||
// Only valid when mFinishRequested == true.
|
||||
private boolean mFinishTargetIsLauncher;
|
||||
// Only valid when mFinishRequested == true
|
||||
private boolean mLauncherIsVisibleAtFinish;
|
||||
private RunnableList mPendingFinishCallbacks = new RunnableList();
|
||||
|
||||
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
|
||||
@@ -116,14 +118,28 @@ public class RecentsAnimationController {
|
||||
finishController(toRecents, onFinishComplete, sendUserLeaveHint);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void finish(boolean toRecents, boolean launcherIsVisibleAtFinish,
|
||||
Runnable onFinishComplete, boolean sendUserLeaveHint) {
|
||||
Preconditions.assertUIThread();
|
||||
finishController(toRecents, launcherIsVisibleAtFinish, onFinishComplete, sendUserLeaveHint,
|
||||
false);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
|
||||
finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
|
||||
finishController(toRecents, false, callback, sendUserLeaveHint, false /* forceFinish */);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
|
||||
boolean forceFinish) {
|
||||
finishController(toRecents, toRecents, callback, sendUserLeaveHint, forceFinish);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void finishController(boolean toRecents, boolean launcherIsVisibleAtFinish,
|
||||
Runnable callback, boolean sendUserLeaveHint, boolean forceFinish) {
|
||||
mPendingFinishCallbacks.add(callback);
|
||||
if (!forceFinish && mFinishRequested) {
|
||||
// If finish has already been requested, then add the callback to the pending list.
|
||||
@@ -135,6 +151,7 @@ public class RecentsAnimationController {
|
||||
// Finish not yet requested
|
||||
mFinishRequested = true;
|
||||
mFinishTargetIsLauncher = toRecents;
|
||||
mLauncherIsVisibleAtFinish = launcherIsVisibleAtFinish;
|
||||
mOnFinishedListener.accept(this);
|
||||
Runnable finishCb = () -> {
|
||||
mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
|
||||
@@ -211,6 +228,14 @@ public class RecentsAnimationController {
|
||||
return mFinishTargetIsLauncher;
|
||||
}
|
||||
|
||||
/**
|
||||
* RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
|
||||
* the animation was finished to launcher vs an app.
|
||||
*/
|
||||
public boolean getLauncherIsVisibleAtFinish() {
|
||||
return mLauncherIsVisibleAtFinish;
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "RecentsAnimationController:");
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.DynamicResource;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.plugins.ResourceProvider;
|
||||
@@ -63,8 +64,7 @@ public class StaggeredWorkspaceAnim {
|
||||
private static final int APP_CLOSE_ROW_START_DELAY_MS = 10;
|
||||
// Should be used for animations running alongside this StaggeredWorkspaceAnim.
|
||||
public static final int DURATION_MS = 250;
|
||||
public static final int DURATION_TASKBAR_MS =
|
||||
QuickstepTransitionManager.getTaskbarToHomeDuration();
|
||||
private final int mTaskbarDurationInMs;
|
||||
|
||||
private static final float MAX_VELOCITY_PX_PER_S = 22f;
|
||||
|
||||
@@ -81,6 +81,8 @@ public class StaggeredWorkspaceAnim {
|
||||
|
||||
public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
|
||||
boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) {
|
||||
mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration(
|
||||
DisplayController.isPinnedTaskbar(launcher));
|
||||
prepareToAnimate(launcher, animateOverviewScrim);
|
||||
|
||||
mIgnoredView = ignoredView;
|
||||
@@ -93,7 +95,7 @@ public class StaggeredWorkspaceAnim {
|
||||
.getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
|
||||
|
||||
DeviceProfile grid = launcher.getDeviceProfile();
|
||||
long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS;
|
||||
long duration = grid.isTaskbarPresent ? mTaskbarDurationInMs : DURATION_MS;
|
||||
if (staggerWorkspace) {
|
||||
Workspace<?> workspace = launcher.getWorkspace();
|
||||
Hotseat hotseat = launcher.getHotseat();
|
||||
|
||||
@@ -5854,15 +5854,22 @@ public abstract class RecentsView<
|
||||
* Finish recents animation.
|
||||
*/
|
||||
public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete) {
|
||||
finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
|
||||
finishRecentsAnimation(toRecents, false, true /* shouldPip */, onFinishComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish recents animation.
|
||||
*/
|
||||
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
|
||||
@Nullable Runnable onFinishComplete) {
|
||||
finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete);
|
||||
}
|
||||
/**
|
||||
* NOTE: Whatever value gets passed through to the toRecents param may need to also be set on
|
||||
* {@link #mRecentsAnimationController#setWillFinishToHome}.
|
||||
*/
|
||||
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
|
||||
@Nullable Runnable onFinishComplete) {
|
||||
boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) {
|
||||
Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: "
|
||||
+ mRecentsAnimationController);
|
||||
// TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
|
||||
@@ -5894,7 +5901,7 @@ public abstract class RecentsView<
|
||||
tx, null /* overlay */);
|
||||
}
|
||||
}
|
||||
mRecentsAnimationController.finish(toRecents, () -> {
|
||||
mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> {
|
||||
if (onFinishComplete != null) {
|
||||
onFinishComplete.run();
|
||||
}
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@ import android.platform.test.annotations.EnableFlags
|
||||
import android.platform.test.flag.junit.SetFlagsRule
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
|
||||
import com.android.launcher3.QuickstepTransitionManager.PINNED_TASKBAR_TRANSITION_DURATION
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.StashedHandleViewController.ALPHA_INDEX_STASHED
|
||||
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
|
||||
@@ -158,7 +159,7 @@ class TaskbarStashControllerTest {
|
||||
@Test
|
||||
@TaskbarMode(PINNED)
|
||||
fun testGetStashDuration_pinnedMode() {
|
||||
assertThat(stashController.stashDuration).isEqualTo(TASKBAR_STASH_DURATION)
|
||||
assertThat(stashController.stashDuration).isEqualTo(PINNED_TASKBAR_TRANSITION_DURATION)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
|
||||
import static com.android.launcher3.Flags.oneGridSpecs;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
|
||||
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
|
||||
import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
@@ -130,6 +132,20 @@ public class GridSizeMigrationDBController {
|
||||
// Only use this strategy when comparing the previous grid to the new grid and the
|
||||
// columns are the same and the destination has more rows
|
||||
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
|
||||
|
||||
if (oneGridSpecs()) {
|
||||
DbReader destReader = new DbReader(
|
||||
target.getWritableDatabase(), TABLE_NAME, context);
|
||||
boolean shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.getRows());
|
||||
if (shouldShiftCells) {
|
||||
shiftTableByXCells(
|
||||
target.getWritableDatabase(),
|
||||
(destDeviceState.getRows() - srcDeviceState.getRows()),
|
||||
TABLE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
// Save current configuration, so that the migration does not run again.
|
||||
destDeviceState.writeToPrefs(context);
|
||||
return true;
|
||||
}
|
||||
@@ -427,17 +443,22 @@ public class GridSizeMigrationDBController {
|
||||
}
|
||||
}
|
||||
|
||||
static void copyCurrentGridToNewGrid(
|
||||
@NonNull Context context,
|
||||
@NonNull DeviceGridState destDeviceState,
|
||||
@NonNull DatabaseHelper target,
|
||||
@NonNull SQLiteDatabase source) {
|
||||
// Only use this strategy when comparing the previous grid to the new grid and the
|
||||
// columns are the same and the destination has more rows
|
||||
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
|
||||
destDeviceState.writeToPrefs(context);
|
||||
private static boolean shouldShiftCells(final DbReader destReader, final int srcGridRowCount) {
|
||||
List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
|
||||
int firstPageItemsRowPosSum = workspaceItems.stream()
|
||||
.filter(entry -> entry.screenId == 0)
|
||||
.mapToInt(entry -> entry.cellY).sum();
|
||||
int firstPageWorkspaceItemsCount = (int) workspaceItems.stream()
|
||||
.filter(entry -> entry.screenId == 0).count();
|
||||
if (firstPageWorkspaceItemsCount == 0) {
|
||||
return false;
|
||||
}
|
||||
float srcGridMidPoint = srcGridRowCount / 2f;
|
||||
float firstPageItemPosAvg = (float) firstPageItemsRowPosSum / firstPageWorkspaceItemsCount;
|
||||
return (firstPageItemPosAvg >= srcGridMidPoint);
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public static class DbReader {
|
||||
|
||||
|
||||
@@ -21,15 +21,20 @@ import android.graphics.Point
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.launcher3.Flags.oneGridSpecs
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.LauncherPrefs.Companion.get
|
||||
import com.android.launcher3.LauncherPrefs.Companion.getPrefs
|
||||
import com.android.launcher3.LauncherSettings
|
||||
import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME
|
||||
import com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.config.FeatureFlags
|
||||
import com.android.launcher3.model.GridSizeMigrationDBController.DbReader
|
||||
import com.android.launcher3.provider.LauncherDbUtils
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction
|
||||
import com.android.launcher3.provider.LauncherDbUtils.copyTable
|
||||
import com.android.launcher3.provider.LauncherDbUtils.dropTable
|
||||
import com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells
|
||||
import com.android.launcher3.util.CellAndSpan
|
||||
import com.android.launcher3.util.GridOccupancy
|
||||
import com.android.launcher3.util.IntArray
|
||||
@@ -59,27 +64,30 @@ class GridSizeMigrationLogic {
|
||||
// amount of rows we simply copy over the source grid to the destination grid, rather
|
||||
// than undergoing the general grid migration.
|
||||
if (shouldMigrateToStrictlyTallerGrid(isDestNewDb, srcDeviceState, destDeviceState)) {
|
||||
GridSizeMigrationDBController.copyCurrentGridToNewGrid(
|
||||
context,
|
||||
destDeviceState,
|
||||
target,
|
||||
source,
|
||||
)
|
||||
copyTable(source, TABLE_NAME, target.writableDatabase, TABLE_NAME, context)
|
||||
if (oneGridSpecs()) {
|
||||
val destReader = DbReader(target.writableDatabase, TABLE_NAME, context)
|
||||
val shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.rows)
|
||||
if (shouldShiftCells) {
|
||||
shiftTableByXCells(
|
||||
target.writableDatabase,
|
||||
(destDeviceState.rows - srcDeviceState.rows),
|
||||
TABLE_NAME,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Save current configuration, so that the migration does not run again.
|
||||
destDeviceState.writeToPrefs(context)
|
||||
return
|
||||
}
|
||||
LauncherDbUtils.copyTable(
|
||||
source,
|
||||
LauncherSettings.Favorites.TABLE_NAME,
|
||||
target.writableDatabase,
|
||||
LauncherSettings.Favorites.TMP_TABLE,
|
||||
context,
|
||||
)
|
||||
|
||||
copyTable(source, TABLE_NAME, target.writableDatabase, TMP_TABLE, context)
|
||||
|
||||
val migrationStartTime = System.currentTimeMillis()
|
||||
try {
|
||||
SQLiteTransaction(target.writableDatabase).use { t ->
|
||||
val srcReader = DbReader(t.db, LauncherSettings.Favorites.TMP_TABLE, context)
|
||||
val destReader = DbReader(t.db, LauncherSettings.Favorites.TABLE_NAME, context)
|
||||
val srcReader = DbReader(t.db, TMP_TABLE, context)
|
||||
val destReader = DbReader(t.db, TABLE_NAME, context)
|
||||
|
||||
val targetSize = Point(destDeviceState.columns, destDeviceState.rows)
|
||||
|
||||
@@ -95,7 +103,7 @@ class GridSizeMigrationLogic {
|
||||
// Migrate workspace.
|
||||
migrateWorkspace(srcReader, destReader, target, targetSize, idsInUse)
|
||||
|
||||
LauncherDbUtils.dropTable(t.db, LauncherSettings.Favorites.TMP_TABLE)
|
||||
dropTable(t.db, TMP_TABLE)
|
||||
t.commit()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -112,6 +120,19 @@ class GridSizeMigrationLogic {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldShiftCells(destReader: DbReader, srcGridRowCount: Int): Boolean {
|
||||
val workspaceItems = destReader.loadAllWorkspaceEntries()
|
||||
val firstPageItemsRowPosSum =
|
||||
workspaceItems.sumOf { entry -> if (entry.screenId == 0) entry.cellY else 0 }
|
||||
val firstPageWorkspaceItemsCount = workspaceItems.count { entry -> entry.screenId == 0 }
|
||||
if (firstPageWorkspaceItemsCount == 0) {
|
||||
return false
|
||||
}
|
||||
val srcGridMidPoint = srcGridRowCount / 2f
|
||||
val firstPageItemPosAvg = firstPageItemsRowPosSum / firstPageWorkspaceItemsCount.toFloat()
|
||||
return (firstPageItemPosAvg >= srcGridMidPoint)
|
||||
}
|
||||
|
||||
/** Handles hotseat migration. */
|
||||
@VisibleForTesting
|
||||
fun migrateHotseat(
|
||||
|
||||
@@ -131,6 +131,11 @@ object LauncherDbUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shiftTableByXCells(db: SQLiteDatabase, x: Int, toTable: String) {
|
||||
db.run { execSQL("UPDATE $toTable SET cellY = cellY + $x") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. Removes any invalid
|
||||
* shortcut or any shortcut which requires some permission to launch
|
||||
|
||||
@@ -18,18 +18,23 @@ package com.android.launcher3.recyclerview
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.InflateException
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.android.launcher3.BubbleTextView
|
||||
import com.android.launcher3.BuildConfig
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter
|
||||
import com.android.launcher3.config.FeatureFlags
|
||||
import com.android.launcher3.util.CancellableTask
|
||||
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
|
||||
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
|
||||
import com.android.launcher3.util.Themes
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
const val PREINFLATE_ICONS_ROW_COUNT = 4
|
||||
const val EXTRA_ICONS_COUNT = 2
|
||||
@@ -39,10 +44,11 @@ const val EXTRA_ICONS_COUNT = 2
|
||||
* [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
|
||||
* will be added to [RecycledViewPool] on main thread.
|
||||
*/
|
||||
class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
|
||||
class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : ActivityContext {
|
||||
|
||||
var hasWorkProfile = false
|
||||
private var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
|
||||
@VisibleForTesting(otherwise = PROTECTED)
|
||||
var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AllAppsRecyclerViewPool"
|
||||
@@ -53,7 +59,7 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
|
||||
/**
|
||||
* Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
|
||||
*/
|
||||
fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
|
||||
fun preInflateAllAppsViewHolders(context: T) {
|
||||
val appsView = context.appsView ?: return
|
||||
val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
|
||||
val preInflateCount = getPreinflateCount(context)
|
||||
@@ -97,36 +103,65 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
|
||||
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
|
||||
}
|
||||
|
||||
preInflateAllAppsViewHolders(
|
||||
adapter,
|
||||
BaseAllAppsAdapter.VIEW_TYPE_ICON,
|
||||
activeRv,
|
||||
preInflateCount,
|
||||
) {
|
||||
getPreinflateCount(context)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = PROTECTED)
|
||||
fun preInflateAllAppsViewHolders(
|
||||
adapter: RecyclerView.Adapter<*>,
|
||||
viewType: Int,
|
||||
activeRv: RecyclerView,
|
||||
preInflationCount: Int,
|
||||
preInflationCountProvider: () -> Int,
|
||||
) {
|
||||
if (preInflationCount <= 0) {
|
||||
return
|
||||
}
|
||||
mCancellableTask?.cancel()
|
||||
var task: CancellableTask<List<ViewHolder>>? = null
|
||||
task =
|
||||
CancellableTask(
|
||||
{
|
||||
val list: ArrayList<ViewHolder> = ArrayList()
|
||||
for (i in 0 until preInflateCount) {
|
||||
for (i in 0 until preInflationCount) {
|
||||
if (task?.canceled == true) {
|
||||
break
|
||||
}
|
||||
// If activeRv's layout manager has been reset to null on main thread, skip
|
||||
// the preinflation as we cannot generate correct LayoutParams
|
||||
if (activeRv.layoutManager == null) {
|
||||
list.clear()
|
||||
break
|
||||
}
|
||||
try {
|
||||
list.add(adapter.createViewHolder(activeRv, viewType))
|
||||
} catch (e: InflateException) {
|
||||
list.clear()
|
||||
// It's still possible for UI thread to set activeRv's layout manager to
|
||||
// null and we should break the loop and cancel the preinflation.
|
||||
break
|
||||
}
|
||||
list.add(
|
||||
adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
|
||||
)
|
||||
}
|
||||
list
|
||||
},
|
||||
MAIN_EXECUTOR,
|
||||
{ viewHolders ->
|
||||
for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
|
||||
// Run preInflationCountProvider again as the needed VH might have changed
|
||||
val newPreInflationCount = preInflationCountProvider.invoke()
|
||||
for (i in 0 until minOf(viewHolders.size, newPreInflationCount)) {
|
||||
putRecycledView(viewHolders[i])
|
||||
}
|
||||
},
|
||||
)
|
||||
mCancellableTask = task
|
||||
VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask)
|
||||
VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,10 +178,11 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
|
||||
* app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
|
||||
* suffice fast scrolling.
|
||||
*
|
||||
* Note that we need to preinfate extra app icons in size of one all apps pages, so that opening
|
||||
* all apps don't need to inflate app icons.
|
||||
* Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra
|
||||
* app icons in size of one all apps pages, so that opening all apps don't need to inflate app
|
||||
* icons.
|
||||
*/
|
||||
fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
|
||||
fun getPreinflateCount(context: T): Int {
|
||||
var targetPreinflateCount =
|
||||
PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
|
||||
EXTRA_ICONS_COUNT
|
||||
|
||||
@@ -63,7 +63,6 @@ filegroup {
|
||||
"src/com/android/launcher3/dragging/TaplDragTest.java",
|
||||
"src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
|
||||
"src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
|
||||
"src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
|
||||
"src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
|
||||
],
|
||||
}
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.recyclerview
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.util.Executors
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
|
||||
|
||||
private lateinit var underTest: AllAppsRecyclerViewPool<T>
|
||||
private lateinit var adapter: RecyclerView.Adapter<*>
|
||||
|
||||
@Mock private lateinit var parent: RecyclerView
|
||||
@Mock private lateinit var itemView: View
|
||||
@Mock private lateinit var layoutManager: LayoutManager
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
underTest = spy(AllAppsRecyclerViewPool())
|
||||
adapter =
|
||||
object : RecyclerView.Adapter<ViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
object : ViewHolder(itemView) {}
|
||||
|
||||
override fun getItemCount() = 0
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
|
||||
}
|
||||
underTest.setMaxRecycledViews(VIEW_TYPE, 20)
|
||||
`when`(parent.layoutManager).thenReturn(layoutManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preinflate_success() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
|
||||
|
||||
awaitTasksCompleted()
|
||||
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preinflate_not_triggered() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 0) { 0 }
|
||||
|
||||
awaitTasksCompleted()
|
||||
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preinflate_cancel_before_runOnMainThread() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
|
||||
assertThat(underTest.mCancellableTask!!.canceled).isFalse()
|
||||
|
||||
underTest.clear()
|
||||
|
||||
awaitTasksCompleted()
|
||||
verify(underTest, never()).putRecycledView(any(ViewHolder::class.java))
|
||||
assertThat(underTest.mCancellableTask!!.canceled).isTrue()
|
||||
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preinflate_cancel_after_run() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
|
||||
assertThat(underTest.mCancellableTask!!.canceled).isFalse()
|
||||
awaitTasksCompleted()
|
||||
|
||||
underTest.clear()
|
||||
|
||||
verify(underTest, times(10)).putRecycledView(any(ViewHolder::class.java))
|
||||
assertThat(underTest.mCancellableTask!!.canceled).isTrue()
|
||||
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
|
||||
}
|
||||
|
||||
private fun awaitTasksCompleted() {
|
||||
Executors.VIEW_PREINFLATION_EXECUTOR.submit<Any> { null }.get()
|
||||
Executors.MAIN_EXECUTOR.submit<Any> { null }.get()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val VIEW_TYPE: Int = 4
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Process;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
@@ -53,7 +50,6 @@ import org.junit.rules.TestRule;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
@@ -216,38 +212,6 @@ public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher>
|
||||
}, mLauncher, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast receiver which blocks until the result is received.
|
||||
*/
|
||||
public class BlockingBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private Intent mIntent;
|
||||
|
||||
public BlockingBroadcastReceiver(String action) {
|
||||
mTargetContext.registerReceiver(this, new IntentFilter(action),
|
||||
Context.RECEIVER_EXPORTED/*UNAUDITED*/);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
mIntent = intent;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public Intent blockingGetIntent() throws InterruptedException {
|
||||
assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS));
|
||||
mTargetContext.unregisterReceiver(this);
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
public Intent blockingGetExtraIntent() throws InterruptedException {
|
||||
Intent intent = blockingGetIntent();
|
||||
return intent == null ? null : (Intent) intent.getParcelableExtra(
|
||||
Intent.EXTRA_INTENT);
|
||||
}
|
||||
}
|
||||
|
||||
public static void startAppFast(String packageName) {
|
||||
startIntent(
|
||||
getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
|
||||
|
||||
+66
-51
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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
|
||||
* 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
|
||||
* 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.
|
||||
* 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.ui.widget;
|
||||
|
||||
@@ -24,23 +24,34 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Process;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.testcomponent.WidgetConfigActivity;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
|
||||
import com.android.launcher3.ui.TestViewHelpers;
|
||||
import com.android.launcher3.util.BaseLauncherActivityTest;
|
||||
import com.android.launcher3.util.BlockingBroadcastReceiver;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.ShellCommandRule;
|
||||
import com.android.launcher3.views.OptionsPopupView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
import com.android.launcher3.widget.WidgetCell;
|
||||
import com.android.launcher3.widget.picker.WidgetsFullSheet;
|
||||
import com.android.launcher3.widget.picker.WidgetsListAdapter;
|
||||
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -52,7 +63,7 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
public class AddConfigWidgetTest extends BaseLauncherActivityTest<Launcher> {
|
||||
|
||||
@Rule
|
||||
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
|
||||
@@ -62,12 +73,10 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
|
||||
private int mWidgetId;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mWidgetInfo = TestViewHelpers.findWidgetProvider(true /* hasConfigureScreen */);
|
||||
mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
|
||||
mAppWidgetManager = AppWidgetManager.getInstance(targetContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -82,80 +91,86 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
runTest(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param acceptConfig accept the config activity
|
||||
*/
|
||||
private void runTest(boolean acceptConfig) throws Throwable {
|
||||
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
|
||||
new FavoriteItemsTransaction(targetContext()).commit();
|
||||
loadLauncherSync();
|
||||
|
||||
// Drag widget to homescreen
|
||||
// Add widget to homescreen
|
||||
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
|
||||
mLauncher.getWorkspace()
|
||||
.openAllWidgets()
|
||||
.getWidget(mWidgetInfo.getLabel())
|
||||
.dragToWorkspace(true, false);
|
||||
executeOnLauncher(OptionsPopupView::openWidgets);
|
||||
uiDevice.waitForIdle();
|
||||
|
||||
// Select the widget header
|
||||
Context testContext = getInstrumentation().getContext();
|
||||
String packageName = testContext.getPackageName();
|
||||
executeOnLauncher(l -> {
|
||||
WidgetsRecyclerView wrv = WidgetsFullSheet.getWidgetsView(l);
|
||||
WidgetsListAdapter adapter = (WidgetsListAdapter) wrv.getAdapter();
|
||||
int pos = adapter.getItems().indexOf(
|
||||
adapter.getItems().stream()
|
||||
.filter(entry -> packageName.equals(entry.mPkgItem.packageName))
|
||||
.findFirst()
|
||||
.get());
|
||||
wrv.getLayoutManager().scrollToPosition(pos);
|
||||
adapter.onHeaderClicked(true, new PackageUserKey(packageName, Process.myUserHandle()));
|
||||
});
|
||||
uiDevice.waitForIdle();
|
||||
|
||||
View widgetView = getOnceNotNull("Widget not found", l -> searchView(l.getDragLayer(), v ->
|
||||
v instanceof WidgetCell
|
||||
&& v.getTag() instanceof PendingAddWidgetInfo pawi
|
||||
&& mWidgetInfo.provider.equals(pawi.componentName)));
|
||||
addToWorkspace(widgetView);
|
||||
|
||||
// Widget id for which the config activity was opened
|
||||
mWidgetId = monitor.getWidgetId();
|
||||
|
||||
// Verify that the widget id is valid and bound
|
||||
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
|
||||
setResult(acceptConfig);
|
||||
|
||||
setResultAndWaitForAnimation(acceptConfig);
|
||||
if (acceptConfig) {
|
||||
Wait.atMost("", new WidgetSearchCondition(), mLauncher);
|
||||
getOnceNotNull("Widget was not added", l -> {
|
||||
// Close the resize frame before searching for widget
|
||||
AbstractFloatingView.closeAllOpenViews(l);
|
||||
return l.getWorkspace().getFirstMatch(new WidgetSearchCondition());
|
||||
});
|
||||
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
|
||||
} else {
|
||||
// Verify that the widget id is deleted.
|
||||
Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
|
||||
mLauncher);
|
||||
Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setResult(boolean success) {
|
||||
private void setResult(boolean success) {
|
||||
getInstrumentation().getTargetContext().sendBroadcast(
|
||||
WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
|
||||
success ? "clickOK" : "clickCancel"));
|
||||
}
|
||||
|
||||
private void setResultAndWaitForAnimation(boolean success) {
|
||||
if (mLauncher.isLauncher3()) {
|
||||
setResult(success);
|
||||
} else {
|
||||
mLauncher.executeAndWaitForWallpaperAnimation(
|
||||
() -> setResult(success),
|
||||
"setting widget coinfig result");
|
||||
}
|
||||
uiDevice.waitForIdle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition for searching widget id
|
||||
*/
|
||||
private class WidgetSearchCondition implements Wait.Condition, ItemOperator {
|
||||
|
||||
@Override
|
||||
public boolean isTrue() throws Throwable {
|
||||
return mMainThreadExecutor.submit(() -> {
|
||||
Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedContext();
|
||||
return l != null && l.getWorkspace().getFirstMatch(this) != null;
|
||||
}).get();
|
||||
}
|
||||
private class WidgetSearchCondition implements ItemOperator {
|
||||
|
||||
@Override
|
||||
public boolean evaluate(ItemInfo info, View view) {
|
||||
return info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
|
||||
mWidgetInfo.provider.getClassName())
|
||||
&& ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
|
||||
return info instanceof LauncherAppWidgetInfo lawi
|
||||
&& lawi.providerName.equals(mWidgetInfo.provider)
|
||||
&& lawi.appWidgetId == mWidgetId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast receiver for receiving widget config activity status.
|
||||
*/
|
||||
private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
|
||||
private static class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
|
||||
|
||||
public WidgetConfigStartupMonitor() {
|
||||
WidgetConfigStartupMonitor() {
|
||||
super(WidgetConfigActivity.class.getName());
|
||||
}
|
||||
|
||||
+52
-43
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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
|
||||
* 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
|
||||
* 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.
|
||||
* 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.ui.widget;
|
||||
|
||||
@@ -22,12 +22,12 @@ import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVID
|
||||
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.TestUtil.getOnUiThread;
|
||||
import static com.android.launcher3.util.Wait.atMost;
|
||||
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ComponentName;
|
||||
@@ -36,6 +36,7 @@ import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -49,13 +50,12 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.tapl.Widget;
|
||||
import com.android.launcher3.tapl.Workspace;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.ui.TestViewHelpers;
|
||||
import com.android.launcher3.util.TestUtil;
|
||||
import com.android.launcher3.util.BaseLauncherActivityTest;
|
||||
import com.android.launcher3.util.rule.ShellCommandRule;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.PendingAppWidgetHostView;
|
||||
import com.android.launcher3.widget.WidgetManagerHelper;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -67,6 +67,7 @@ import org.junit.runner.RunWith;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Tests for bind widget flow.
|
||||
@@ -75,7 +76,7 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
public class BindWidgetTest extends BaseLauncherActivityTest<Launcher> {
|
||||
|
||||
@Rule
|
||||
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
|
||||
@@ -87,11 +88,9 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
|
||||
private LauncherModel mModel;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mModel = LauncherAppState.getInstance(mTargetContext).getModel();
|
||||
mModel = LauncherAppState.getInstance(targetContext()).getModel();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -101,7 +100,7 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
}
|
||||
|
||||
if (mSessionId > -1) {
|
||||
mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
|
||||
targetContext().getPackageManager().getPackageInstaller().abandonSession(mSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,13 +121,12 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
|
||||
item -> item.appWidgetId = -33);
|
||||
|
||||
final Workspace workspace = mLauncher.getWorkspace();
|
||||
// Item deleted from db
|
||||
mCursor = queryItem();
|
||||
assertEquals(0, mCursor.getCount());
|
||||
|
||||
// The view does not exist
|
||||
assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null);
|
||||
verifyItemEventuallyNull("Widget exists", widgetProvider(info));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,18 +152,19 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
// Widget has a valid Id now.
|
||||
assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
|
||||
& FLAG_ID_NOT_VALID);
|
||||
assertNotNull(AppWidgetManager.getInstance(mTargetContext)
|
||||
assertNotNull(AppWidgetManager.getInstance(targetContext())
|
||||
.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
|
||||
LauncherSettings.Favorites.APPWIDGET_ID))));
|
||||
|
||||
// send OPTION_APPWIDGET_RESTORE_COMPLETED
|
||||
int appWidgetId = mCursor.getInt(
|
||||
mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID));
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext);
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(targetContext());
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true);
|
||||
RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready);
|
||||
RemoteViews remoteViews = new RemoteViews(
|
||||
targetContext().getPackageName(), R.layout.appwidget_not_ready);
|
||||
appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
|
||||
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
|
||||
|
||||
@@ -175,15 +174,14 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
|
||||
executeOnLauncher(l -> l.getAppWidgetHolder().startListening());
|
||||
verifyWidgetPresent(info);
|
||||
assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100));
|
||||
verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingWidget_notRestored_removed() {
|
||||
addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
|
||||
|
||||
assertTrue("Pending widget exists",
|
||||
mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
|
||||
verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider());
|
||||
// Item deleted from db
|
||||
mCursor = queryItem();
|
||||
assertEquals(0, mCursor.getCount());
|
||||
@@ -216,7 +214,7 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
// Create an active installer session
|
||||
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
|
||||
params.setAppPackageName(item.providerName.getPackageName());
|
||||
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller installer = targetContext().getPackageManager().getPackageInstaller();
|
||||
mSessionId = installer.createSession(params);
|
||||
|
||||
addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
|
||||
@@ -234,36 +232,47 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
}
|
||||
|
||||
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
|
||||
final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label,
|
||||
TestUtil.DEFAULT_UI_TIMEOUT);
|
||||
assertTrue("Widget is not present",
|
||||
widget != null);
|
||||
getOnceNotNull("Widget is not present", widgetProvider(info));
|
||||
}
|
||||
|
||||
private void verifyPendingWidgetPresent() {
|
||||
final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(
|
||||
TestUtil.DEFAULT_UI_TIMEOUT);
|
||||
assertTrue("Pending widget is not present",
|
||||
widget != null);
|
||||
getOnceNotNull("Widget is not present", pendingWidgetProvider());
|
||||
}
|
||||
|
||||
private Function<Launcher, Object> pendingWidgetProvider() {
|
||||
return l -> l.getWorkspace().getFirstMatch(
|
||||
(item, view) -> view instanceof PendingAppWidgetHostView);
|
||||
}
|
||||
|
||||
private Function<Launcher, Object> widgetProvider(LauncherAppWidgetProviderInfo info) {
|
||||
return l -> l.getWorkspace().getFirstMatch((item, view) ->
|
||||
view instanceof LauncherAppWidgetHostView
|
||||
&& TextUtils.equals(info.label, view.getContentDescription()));
|
||||
}
|
||||
|
||||
private void verifyItemEventuallyNull(String message, Function<Launcher, Object> provider) {
|
||||
atMost(message, () -> getFromLauncher(provider) == null);
|
||||
}
|
||||
|
||||
private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
|
||||
item.restoreStatus = restoreStatus;
|
||||
item.screenId = FIRST_SCREEN_ID;
|
||||
commitTransactionAndLoadHome(
|
||||
new FavoriteItemsTransaction(mTargetContext).addItem(() -> item));
|
||||
new FavoriteItemsTransaction(targetContext()).addItem(() -> item).commit();
|
||||
loadLauncherSync();
|
||||
}
|
||||
|
||||
private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
|
||||
boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
|
||||
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)
|
||||
new FavoriteItemsTransaction(targetContext())
|
||||
.addItem(() -> {
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
|
||||
LauncherAppWidgetInfo item =
|
||||
createWidgetInfo(info, targetContext(), bindWidget);
|
||||
item.screenId = FIRST_SCREEN_ID;
|
||||
itemOverride.accept(item);
|
||||
return item;
|
||||
}));
|
||||
}).commit();
|
||||
loadLauncherSync();
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -277,13 +286,13 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
|
||||
|
||||
Set<String> activePackage = getOnUiThread(() -> {
|
||||
Set<String> packages = new HashSet<>();
|
||||
InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions()
|
||||
InstallSessionHelper.INSTANCE.get(targetContext()).getActiveSessions()
|
||||
.keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName));
|
||||
return packages;
|
||||
});
|
||||
while (true) {
|
||||
try {
|
||||
mTargetContext.getPackageManager().getPackageInfo(
|
||||
targetContext().getPackageManager().getPackageInfo(
|
||||
pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
|
||||
} catch (Exception e) {
|
||||
if (!activePackage.contains(pkg)) {
|
||||
+54
-64
@@ -1,34 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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
|
||||
* 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
|
||||
* 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.
|
||||
* 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.ui.widget;
|
||||
|
||||
import static android.app.PendingIntent.FLAG_MUTABLE;
|
||||
import static android.app.PendingIntent.FLAG_ONE_SHOT;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
import static java.util.regex.Pattern.CASE_INSENSITIVE;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.BySelector;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
@@ -37,14 +44,13 @@ import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.tapl.AddToHomeScreenPrompt;
|
||||
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
|
||||
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
|
||||
import com.android.launcher3.testcomponent.RequestPinItemActivity;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.util.BaseLauncherActivityTest;
|
||||
import com.android.launcher3.util.BlockingBroadcastReceiver;
|
||||
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.Wait.Condition;
|
||||
import com.android.launcher3.util.TestUtil;
|
||||
import com.android.launcher3.util.rule.ShellCommandRule;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -53,25 +59,27 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Test to verify pin item request flow.
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
|
||||
public class RequestPinItemTest extends BaseLauncherActivityTest<Launcher> {
|
||||
|
||||
@Rule
|
||||
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
|
||||
|
||||
@Rule
|
||||
public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
|
||||
|
||||
private String mCallbackAction;
|
||||
private String mShortcutId;
|
||||
private int mAppWidgetId;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mCallbackAction = UUID.randomUUID().toString();
|
||||
mShortcutId = UUID.randomUUID().toString();
|
||||
}
|
||||
@@ -81,9 +89,9 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
|
||||
|
||||
@Test
|
||||
public void testPinWidgetNoConfig() throws Throwable {
|
||||
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
|
||||
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
|
||||
((LauncherAppWidgetInfo) info).providerName.getClassName()
|
||||
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId
|
||||
&& ((LauncherAppWidgetInfo) info).providerName.getClassName()
|
||||
.equals(AppWidgetNoConfig.class.getName()));
|
||||
}
|
||||
|
||||
@@ -94,18 +102,18 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
|
||||
RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
|
||||
RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
|
||||
|
||||
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
|
||||
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
|
||||
((LauncherAppWidgetInfo) info).providerName.getClassName()
|
||||
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId
|
||||
&& ((LauncherAppWidgetInfo) info).providerName.getClassName()
|
||||
.equals(AppWidgetNoConfig.class.getName()), command);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPinWidgetWithConfig() throws Throwable {
|
||||
runTest("pinWidgetWithConfig", true,
|
||||
(info, view) -> info instanceof LauncherAppWidgetInfo &&
|
||||
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
|
||||
((LauncherAppWidgetInfo) info).providerName.getClassName()
|
||||
(info, view) -> info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId
|
||||
&& ((LauncherAppWidgetInfo) info).providerName.getClassName()
|
||||
.equals(AppWidgetWithConfig.class.getName()));
|
||||
}
|
||||
|
||||
@@ -119,47 +127,48 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
|
||||
runTest("pinShortcut", false, new ItemOperator() {
|
||||
@Override
|
||||
public boolean evaluate(ItemInfo info, View view) {
|
||||
return info instanceof WorkspaceItemInfo &&
|
||||
info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
|
||||
ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
|
||||
return info instanceof WorkspaceItemInfo
|
||||
&& info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
&& ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
|
||||
}
|
||||
}, command);
|
||||
}
|
||||
|
||||
private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
|
||||
Intent... commandIntents) throws Throwable {
|
||||
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
|
||||
new FavoriteItemsTransaction(targetContext()).commit();
|
||||
loadLauncherSync();
|
||||
|
||||
// Open Pin item activity
|
||||
BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
|
||||
RequestPinItemActivity.class.getName());
|
||||
mLauncher.
|
||||
getWorkspace().
|
||||
switchToAllApps().
|
||||
getAppIcon("Test Pin Item").
|
||||
launch(getAppPackageName());
|
||||
Context testContext = getInstrumentation().getContext();
|
||||
startAppFast(
|
||||
testContext.getPackageName(),
|
||||
new Intent(testContext, RequestPinItemActivity.class));
|
||||
assertNotNull(openMonitor.blockingGetExtraIntent());
|
||||
|
||||
// Set callback
|
||||
PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
|
||||
new Intent(mCallbackAction).setPackage(mTargetContext.getPackageName()),
|
||||
PendingIntent callback = PendingIntent.getBroadcast(targetContext(), 0,
|
||||
new Intent(mCallbackAction).setPackage(targetContext().getPackageName()),
|
||||
FLAG_ONE_SHOT | FLAG_MUTABLE);
|
||||
mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
|
||||
targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent(
|
||||
RequestPinItemActivity.class, "setCallback").putExtra(
|
||||
RequestPinItemActivity.EXTRA_PARAM + "0", callback));
|
||||
|
||||
for (Intent command : commandIntents) {
|
||||
mTargetContext.sendBroadcast(command);
|
||||
targetContext().sendBroadcast(command);
|
||||
}
|
||||
|
||||
// call the requested method to start the flow
|
||||
mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
|
||||
targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent(
|
||||
RequestPinItemActivity.class, activityMethod));
|
||||
final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt();
|
||||
|
||||
// Accept confirmation:
|
||||
BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
|
||||
addToHomeScreenPrompt.addAutomatically();
|
||||
BySelector selector = By.text(Pattern.compile("^Add to home screen$", CASE_INSENSITIVE))
|
||||
.pkg(targetContext().getPackageName());
|
||||
uiDevice.wait(device -> device.findObject(selector), TestUtil.DEFAULT_UI_TIMEOUT).click();
|
||||
Intent result = resultReceiver.blockingGetIntent();
|
||||
assertNotNull(result);
|
||||
mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
|
||||
@@ -167,28 +176,9 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
|
||||
assertNotSame(-1, mAppWidgetId);
|
||||
}
|
||||
|
||||
// Go back to home
|
||||
mLauncher.goHome();
|
||||
Wait.atMost("", new ItemSearchCondition(itemMatcher), mLauncher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition for for an item
|
||||
*/
|
||||
private class ItemSearchCondition implements Condition {
|
||||
|
||||
private final ItemOperator mOp;
|
||||
|
||||
ItemSearchCondition(ItemOperator op) {
|
||||
mOp = op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue() throws Throwable {
|
||||
return mMainThreadExecutor.submit(() -> {
|
||||
Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedContext();
|
||||
return l != null && l.getWorkspace().getFirstMatch(mOp) != null;
|
||||
}).get();
|
||||
}
|
||||
// Reload activity, so that the activity is focused
|
||||
closeCurrentActivity();
|
||||
loadLauncherSync();
|
||||
getOnceNotNull("", l -> l.getWorkspace().getFirstMatch(itemMatcher));
|
||||
}
|
||||
}
|
||||
+17
-20
@@ -22,24 +22,30 @@ import static org.junit.Assert.assertTrue;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.tapl.Widgets;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
|
||||
import com.android.launcher3.util.BaseLauncherActivityTest;
|
||||
import com.android.launcher3.util.rule.ScreenRecordRule;
|
||||
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
|
||||
import com.android.launcher3.views.OptionsPopupView;
|
||||
import com.android.launcher3.widget.picker.WidgetsFullSheet;
|
||||
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* This test run in both Out of process (Oop) and in-process (Ipc).
|
||||
* Make sure the basic interactions with the WidgetPicker works.
|
||||
*/
|
||||
@MediumTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaplWidgetPickerTest extends AbstractLauncherUiTest<Launcher> {
|
||||
public class WidgetPickerTest extends BaseLauncherActivityTest<Launcher> {
|
||||
|
||||
@Rule
|
||||
public TestRule screenRecordRule = new ScreenRecordRule();
|
||||
|
||||
private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
|
||||
return WidgetsFullSheet.getWidgetsView(launcher);
|
||||
@@ -56,30 +62,21 @@ public class TaplWidgetPickerTest extends AbstractLauncherUiTest<Launcher> {
|
||||
@ScreenRecord
|
||||
@PortraitLandscape
|
||||
public void testWidgets() {
|
||||
mLauncher.goHome();
|
||||
loadLauncherSync();
|
||||
// Test opening widgets.
|
||||
executeOnLauncher(launcher ->
|
||||
assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
|
||||
Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
|
||||
assertNotNull("openAllWidgets() returned null", widgets);
|
||||
widgets = mLauncher.getAllWidgets();
|
||||
assertNotNull("openAllWidgets() returned null",
|
||||
getFromLauncher(OptionsPopupView::openWidgets));
|
||||
WidgetsRecyclerView widgets = getFromLauncher(this::getWidgetsView);
|
||||
assertNotNull("getAllWidgets() returned null", widgets);
|
||||
executeOnLauncher(launcher ->
|
||||
assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
|
||||
executeOnLauncher(launcher -> assertTrue("Widgets is not shown", widgets.isShown()));
|
||||
executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
|
||||
0, getWidgetsScroll(launcher)));
|
||||
|
||||
// Test flinging widgets.
|
||||
widgets.flingForward();
|
||||
Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
|
||||
executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
|
||||
flingForwardY > 0));
|
||||
executeOnLauncher(AbstractFloatingView::closeAllOpenViews);
|
||||
uiDevice.waitForIdle();
|
||||
|
||||
widgets.flingBackward();
|
||||
executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
|
||||
getWidgetsScroll(launcher) < flingForwardY));
|
||||
|
||||
mLauncher.goHome();
|
||||
waitForLauncherCondition("Widgets were not closed",
|
||||
launcher -> getWidgetsView(launcher) == null);
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.ui.workspace;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
|
||||
@@ -29,11 +27,9 @@ import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
@@ -49,9 +45,6 @@ import com.android.launcher3.util.TestUtil;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Tests for theme icon support in Launcher
|
||||
*
|
||||
@@ -137,27 +130,10 @@ public class ThemeIconsTest extends BaseLauncherActivityTest<Launcher> {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// Find the app icon
|
||||
Queue<View> viewQueue = new ArrayDeque<>();
|
||||
viewQueue.add(parent);
|
||||
BubbleTextView icon = null;
|
||||
while (!viewQueue.isEmpty()) {
|
||||
View view = viewQueue.poll();
|
||||
if (view instanceof ViewGroup) {
|
||||
parent = (ViewGroup) view;
|
||||
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
|
||||
viewQueue.add(parent.getChildAt(i));
|
||||
}
|
||||
} else if (view instanceof BubbleTextView btv) {
|
||||
if (btv.getContentDescription() != null
|
||||
&& title.equals(btv.getContentDescription().toString())) {
|
||||
icon = btv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
return (BubbleTextView) searchView(parent, v ->
|
||||
v instanceof BubbleTextView btv
|
||||
&& btv.getContentDescription() != null
|
||||
&& title.equals(btv.getContentDescription().toString()));
|
||||
}
|
||||
|
||||
private BubbleTextView verifyIconTheme(String title, ViewGroup parent, boolean isThemed) {
|
||||
@@ -193,11 +169,4 @@ public class ThemeIconsTest extends BaseLauncherActivityTest<Launcher> {
|
||||
rv.getLayoutManager().scrollToPosition(pos);
|
||||
});
|
||||
}
|
||||
|
||||
private void addToWorkspace(View btv) {
|
||||
TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () ->
|
||||
btv.getAccessibilityDelegate().performAccessibilityAction(
|
||||
btv, com.android.launcher3.R.id.action_add_to_workspace, null));
|
||||
UiDevice.getInstance(getInstrumentation()).waitForIdle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ import android.view.InputDevice
|
||||
import android.view.KeyCharacterMap
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import androidx.lifecycle.Lifecycle.State.RESUMED
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ActivityScenario.ActivityAction
|
||||
@@ -30,11 +33,13 @@ import androidx.test.uiautomator.UiDevice
|
||||
import com.android.launcher3.Launcher
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.LauncherState
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST
|
||||
import com.android.launcher3.tapl.TestHelpers
|
||||
import com.android.launcher3.util.ModelTestExtensions.loadModelSync
|
||||
import com.android.launcher3.util.Wait.atMost
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.Supplier
|
||||
import org.junit.After
|
||||
|
||||
@@ -56,6 +61,8 @@ open class BaseLauncherActivityTest<LAUNCHER_TYPE : Launcher> {
|
||||
)
|
||||
.also { currentScenario = it }
|
||||
|
||||
@JvmField val uiDevice = UiDevice.getInstance(getInstrumentation())
|
||||
|
||||
@After
|
||||
fun closeCurrentActivity() {
|
||||
currentScenario?.close()
|
||||
@@ -136,18 +143,43 @@ open class BaseLauncherActivityTest<LAUNCHER_TYPE : Launcher> {
|
||||
event.recycle()
|
||||
}
|
||||
|
||||
fun startAppFast(packageName: String) {
|
||||
val intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!
|
||||
@JvmOverloads
|
||||
fun startAppFast(
|
||||
packageName: String,
|
||||
intent: Intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!,
|
||||
) {
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
targetContext().startActivity(intent)
|
||||
UiDevice.getInstance(getInstrumentation()).waitForIdle()
|
||||
uiDevice.waitForIdle()
|
||||
}
|
||||
|
||||
fun freezeAllApps() = executeOnLauncher {
|
||||
it.appsView.appsStore.enableDeferUpdates(DEFER_UPDATES_TEST)
|
||||
}
|
||||
|
||||
fun executeShellCommand(cmd: String) =
|
||||
UiDevice.getInstance(getInstrumentation()).executeShellCommand(cmd)
|
||||
fun executeShellCommand(cmd: String) = uiDevice.executeShellCommand(cmd)
|
||||
|
||||
fun addToWorkspace(view: View) {
|
||||
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {
|
||||
view.accessibilityDelegate.performAccessibilityAction(
|
||||
view,
|
||||
R.id.action_add_to_workspace,
|
||||
null,
|
||||
)
|
||||
}
|
||||
UiDevice.getInstance(getInstrumentation()).waitForIdle()
|
||||
}
|
||||
|
||||
fun ViewGroup.searchView(filter: Predicate<View>): View? {
|
||||
if (filter.test(this)) return this
|
||||
for (child in children) {
|
||||
if (filter.test(child)) return child
|
||||
if (child is ViewGroup)
|
||||
child.searchView(filter)?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Parcelable
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
|
||||
private const val DEFAULT_BROADCAST_TIMEOUT_SECS: Long = 10
|
||||
|
||||
/** Broadcast receiver which blocks until the result is received. */
|
||||
open class BlockingBroadcastReceiver(action: String) : BroadcastReceiver() {
|
||||
|
||||
val value = CompletableFuture<Intent>()
|
||||
|
||||
init {
|
||||
getInstrumentation()
|
||||
.targetContext
|
||||
.registerReceiver(this, IntentFilter(action), Context.RECEIVER_EXPORTED)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
value.complete(intent)
|
||||
}
|
||||
|
||||
@Throws(InterruptedException::class)
|
||||
fun blockingGetIntent(): Intent =
|
||||
value.get(DEFAULT_BROADCAST_TIMEOUT_SECS, SECONDS).also {
|
||||
getInstrumentation().targetContext.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
@Throws(InterruptedException::class)
|
||||
fun blockingGetExtraIntent(): Intent? =
|
||||
blockingGetIntent().getParcelableExtra<Parcelable>(Intent.EXTRA_INTENT) as Intent?
|
||||
}
|
||||
Reference in New Issue
Block a user