Merge "Create TAPL test suite for splitscreen" into tm-qpr-dev
diff --git a/OWNERS b/OWNERS
index 962d63a..2d7a014 100644
--- a/OWNERS
+++ b/OWNERS
@@ -29,6 +29,7 @@
sihua@google.com
sunnygoyal@google.com
tracyzhou@google.com
+tsuharesu@google.com
twickham@google.com
vadimt@google.com
victortulias@google.com
@@ -36,4 +37,4 @@
xuqiu@google.com
per-file FeatureFlags.java, globs = set noparent
-per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
+per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index a91507c..d581582 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -49,4 +49,6 @@
<item name="config_wallpaperMaxScale" format="float" type="dimen">
@*android:dimen/config_wallpaperMaxScale
</item>
+
+ <string name="setup_wizard_pkg" translatable="false" />
</resources>
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 9f3be69..0dde1bd 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -118,7 +118,7 @@
private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
int targetPredictor) {
// TODO: remove the running test check when b/231648228 is fixed.
- if (target != null && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (target != null && !Utilities.isRunningInTestHarness()) {
AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
.setLaunchLocation(getContainer(locationInfo))
.build();
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b00c4cb..ac2c44b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -198,15 +198,7 @@
*/
public Animator createAnimToLauncher(@NonNull LauncherState toState,
@NonNull RecentsAnimationCallbacks callbacks, long duration) {
- AnimatorSet set = new AnimatorSet();
- Animator taskbarState = mTaskbarLauncherStateController
- .createAnimToLauncher(toState, callbacks, duration);
- long halfDuration = Math.round(duration * 0.5f);
- Animator translation =
- mControllers.taskbarTranslationController.createAnimToLauncher(halfDuration);
-
- set.playTogether(taskbarState, translation);
- return set;
+ return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
}
public boolean isDraggingItem() {
@@ -280,7 +272,7 @@
* Returns {@code true} if a Taskbar education should be shown on application launch.
*/
public boolean shouldShowEduOnAppLaunch() {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return false;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index ab52adb..2864ac7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -24,7 +24,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
-import static com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS;
+import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
@@ -45,6 +45,7 @@
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
@@ -88,6 +89,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.PackageManagerHelper;
@@ -105,7 +107,6 @@
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
-import java.util.function.Consumer;
/**
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -218,8 +219,8 @@
new TaskbarScrimViewController(this, taskbarScrimView),
new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
mWindowManager,
- new RotationChangeProvider(WindowManagerGlobal.getWindowManagerService(), this,
- getMainExecutor())),
+ new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
+ getMainThreadHandler())),
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
new TaskbarStashController(this),
@@ -336,8 +337,7 @@
int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SLIPPERY
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
- if (DisplayController.isTransientTaskbar(this)
- && !IS_RUNNING_IN_TEST_HARNESS) {
+ if (DisplayController.isTransientTaskbar(this) && !isRunningInTestHarness()) {
windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
@@ -876,9 +876,14 @@
* as if the user tapped on it (preserving the split pair). Otherwise, launch it normally
* (potentially breaking a split pair).
*/
- private void launchFromTaskbarPreservingSplitIfVisible(RecentsView recents, ItemInfo info) {
+ private void launchFromTaskbarPreservingSplitIfVisible(@Nullable RecentsView recents,
+ ItemInfo info) {
+ if (recents == null) {
+ return;
+ }
+ ComponentKey componentToBeLaunched = new ComponentKey(info.getTargetComponent(), info.user);
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
- info.getTargetComponent(),
+ componentToBeLaunched,
foundTask -> {
if (foundTask != null) {
TaskView foundTaskView =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 00dfaf2..bc582e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -28,7 +28,6 @@
import com.airbnb.lottie.model.KeyPath
import com.android.launcher3.R
import com.android.launcher3.Utilities
-import com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
@@ -56,7 +55,8 @@
class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
LoggableTaskbarController {
- private val isTooltipEnabled = !IS_RUNNING_IN_TEST_HARNESS && ENABLE_TASKBAR_EDU_TOOLTIP.get()
+ private val isTooltipEnabled: Boolean
+ get() = !Utilities.isRunningInTestHarness() && ENABLE_TASKBAR_EDU_TOOLTIP.get()
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index b586487..5ac0570 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -433,6 +433,14 @@
});
animatorSet.play(stashAnimator);
}
+
+ if (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL) {
+ // Translate back to 0 at a shorter or same duration as the icon alignment animation.
+ // This ensures there is no jump after switching to hotseat, e.g. when swiping up from
+ // overview to home. Currently we do duration / 2 just to make it feel snappier.
+ animatorSet.play(mControllers.taskbarTranslationController
+ .createAnimToResetTranslation(duration / 2));
+ }
}
private boolean isInLauncher() {
@@ -460,7 +468,7 @@
updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0);
// Sync the first frame where we swap taskbar and hotseat.
- if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) {
ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
mControllers.taskbarActivityContext.getDragLayer(),
() -> {});
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index bbf861b..babafd5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -297,7 +297,7 @@
}
return supportsVisualStashing()
&& isInApp()
- && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests)
+ && (!Utilities.isRunningInTestHarness() || mEnableManualStashingDuringTests)
&& !DisplayController.isTransientTaskbar(mActivity);
}
@@ -723,7 +723,7 @@
}
}
play(as, mControllers.taskbarViewController
- .createRevealAnimToIsStashed(isStashed, isInApp()), 0, duration, EMPHASIZED);
+ .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
if (skipInterpolator != null) {
as.setInterpolator(skipInterpolator);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index a6b2a8a..062b4ce 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -135,9 +135,9 @@
}
/**
- * Returns an animation to reset the taskbar translation for animation back to launcher.
+ * Returns an animation to reset the taskbar translation to {@code 0}.
*/
- public ObjectAnimator createAnimToLauncher(long duration) {
+ public ObjectAnimator createAnimToResetTranslation(long duration) {
ObjectAnimator animator = ObjectAnimator.ofFloat(mTranslationYForSwipe, VALUE, 0);
animator.setInterpolator(Interpolators.LINEAR);
animator.setDuration(duration);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 3479255..b552e9b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -30,16 +30,15 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
-import com.android.systemui.shared.recents.model.Task;
import java.io.PrintWriter;
-import java.util.function.Consumer;
/**
* Base class for providing different taskbar UI
@@ -189,8 +188,12 @@
if (recentsView == null) {
return;
}
+
+ ComponentKey componentToBeStaged = new ComponentKey(
+ splitSelectSource.itemInfo.getTargetComponent(),
+ splitSelectSource.itemInfo.user);
recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
- splitSelectSource.intent.getComponent(),
+ componentToBeStaged,
foundTask -> {
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
@@ -206,8 +209,9 @@
*/
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
RecentsView recents = getRecentsView();
+ ComponentKey secondAppComponent = new ComponentKey(info.getTargetComponent(), info.user);
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
- info.getTargetComponent(),
+ secondAppComponent,
foundTask -> {
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index c708838..2f8cae8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,6 +17,8 @@
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
@@ -26,13 +28,15 @@
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.graphics.Rect;
-import android.util.FloatProperty;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -42,10 +46,10 @@
import androidx.core.graphics.ColorUtils;
import androidx.core.view.OneShotPreDrawListener;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.AnimatedFloat;
@@ -55,7 +59,6 @@
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -64,6 +67,7 @@
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import java.io.PrintWriter;
@@ -303,7 +307,7 @@
* @param isStashed When true, the icon crops vertically to the size of the stashed handle.
* When false, the reverse happens.
*/
- public AnimatorSet createRevealAnimToIsStashed(boolean isStashed, boolean isInApp) {
+ public AnimatorSet createRevealAnimToIsStashed(boolean isStashed) {
AnimatorSet as = new AnimatorSet();
Rect stashedBounds = new Rect();
@@ -317,6 +321,10 @@
for (int i = numChildren - 1; i >= 0; i--) {
View child = mTaskbarView.getChildAt(i);
+ if (child == mTaskbarView.getQsb()) {
+ continue;
+ }
+
// Crop the icons to/from the nav handle shape.
as.play(createRevealAnimForView(child, isStashed));
@@ -333,18 +341,34 @@
croppedTransX = newLeft - iconLeft;
}
- as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_X, isStashed
- ? new float[] {croppedTransX}
- : new float[] {croppedTransX, 0}));
-
float croppedTransY = child.getHeight() - stashedBounds.height();
- as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_Y, isStashed
- ? new float[] {croppedTransY}
- : new float[] {croppedTransY, 0}));
- as.addListener(forEndCallback(() -> {
- ICON_REVEAL_TRANSLATE_X.set(child, 0f);
- ICON_REVEAL_TRANSLATE_Y.set(child, 0f);
- }));
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+ as.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, isStashed
+ ? new float[] {croppedTransX}
+ : new float[] {croppedTransX, 0}));
+ as.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, isStashed
+ ? new float[] {croppedTransY}
+ : new float[] {croppedTransY, 0}));
+ as.addListener(forEndCallback(() ->
+ mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0)));
+ } else {
+ as.play(ObjectAnimator.ofFloat(child,
+ VIEW_TRANSLATE_X, isStashed
+ ? new float[] {croppedTransX}
+ : new float[] {croppedTransX, 0}));
+ as.play(ObjectAnimator.ofFloat(child,
+ VIEW_TRANSLATE_Y, isStashed
+ ? new float[] {croppedTransY}
+ : new float[] {croppedTransY, 0}));
+ as.addListener(forEndCallback(() -> {
+ child.setTranslationX(0);
+ child.setTranslationY(0);
+ }));
+ }
}
return as;
}
@@ -430,14 +454,14 @@
float childCenter = (child.getLeft() + child.getRight()) / 2f;
float halfQsbIconWidthDiff =
(launcherDp.hotseatQsbWidth - taskbarDp.iconSizePx) / 2f;
- setter.addFloat(child, ICON_TRANSLATE_X,
+ setter.addFloat(child, VIEW_TRANSLATE_X,
isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
hotseatIconCenter - childCenter, interpolator);
float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
- setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+ setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
if (mIsHotseatIconOnTopWhenAligned) {
setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
@@ -474,10 +498,18 @@
+ hotseatCellSize / 2f;
float childCenter = (child.getLeft() + child.getRight()) / 2f;
- setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, interpolator);
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
- setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
-
+ setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, hotseatIconCenter - childCenter, interpolator);
+ setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
+ } else {
+ setter.setFloat(child, VIEW_TRANSLATE_X,
+ hotseatIconCenter - childCenter, interpolator);
+ setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+ }
setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
}
@@ -662,107 +694,4 @@
mControllers.uiController.onIconLayoutBoundsChanged();
}
}
-
- public static final FloatProperty<View> ICON_TRANSLATE_X =
- new FloatProperty<View>("taskbarAlignmentTranslateX") {
-
- @Override
- public void setValue(View view, float v) {
- if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationXForTaskbarAlignmentAnimation(v);
- } else {
- view.setTranslationX(v);
- }
- }
-
- @Override
- public Float get(View view) {
- if (view instanceof BubbleTextView) {
- return ((BubbleTextView) view)
- .getTranslationXForTaskbarAlignmentAnimation();
- } else if (view instanceof FolderIcon) {
- return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
- }
- return view.getTranslationX();
- }
- };
-
- public static final FloatProperty<View> ICON_TRANSLATE_Y =
- new FloatProperty<View>("taskbarAlignmentTranslateY") {
-
- @Override
- public void setValue(View view, float v) {
- if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationYForTaskbarAlignmentAnimation(v);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationYForTaskbarAlignmentAnimation(v);
- } else {
- view.setTranslationY(v);
- }
- }
-
- @Override
- public Float get(View view) {
- if (view instanceof BubbleTextView) {
- return ((BubbleTextView) view)
- .getTranslationYForTaskbarAlignmentAnimation();
- } else if (view instanceof FolderIcon) {
- return ((FolderIcon) view).getTranslationYForTaskbarAlignmentAnimation();
- }
- return view.getTranslationY();
- }
- };
-
- public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_X =
- new FloatProperty<View>("taskbarRevealTranslateX") {
-
- @Override
- public void setValue(View view, float v) {
- if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationXForTaskbarRevealAnimation(v);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationXForTaskbarRevealAnimation(v);
- } else {
- view.setTranslationX(v);
- }
- }
-
- @Override
- public Float get(View view) {
- if (view instanceof BubbleTextView) {
- return ((BubbleTextView) view).getTranslationXForTaskbarRevealAnimation();
- } else if (view instanceof FolderIcon) {
- return ((FolderIcon) view).getTranslationXForTaskbarRevealAnimation();
- }
- return view.getTranslationX();
- }
- };
-
- public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_Y =
- new FloatProperty<View>("taskbarRevealTranslateY") {
-
- @Override
- public void setValue(View view, float v) {
- if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationYForTaskbarRevealAnimation(v);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationYForTaskbarRevealAnimation(v);
- } else {
- view.setTranslationY(v);
- }
- }
-
- @Override
- public Float get(View view) {
- if (view instanceof BubbleTextView) {
- return ((BubbleTextView) view).getTranslationYForTaskbarRevealAnimation();
- } else if (view instanceof FolderIcon) {
- return ((FolderIcon) view).getTranslationYForTaskbarRevealAnimation();
- }
- return view.getTranslationY();
- }
- };
-
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 951a9b6..a56300a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -68,6 +68,7 @@
import android.graphics.RectF;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.display.DisplayManager;
import android.media.permission.SafeCloseable;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -136,6 +137,7 @@
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NavigationMode;
@@ -574,10 +576,13 @@
@Override
public void startSplitSelection(SplitSelectSource splitSelectSource) {
RecentsView recentsView = getOverviewPanel();
+ ComponentKey componentToBeStaged = new ComponentKey(
+ splitSelectSource.itemInfo.getTargetComponent(),
+ splitSelectSource.itemInfo.user);
// Check if there is already an instance of this app running, if so, initiate the split
// using that.
mSplitSelectStateController.findLastActiveTaskAndRunCallback(
- splitSelectSource.intent.getComponent(),
+ componentToBeStaged,
foundTask -> {
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
@@ -878,7 +883,7 @@
getMainExecutor(),
/* backgroundExecutor= */ UI_HELPER_EXECUTOR,
/* tracingTagPrefix= */ "launcher",
- WindowManagerGlobal.getWindowManagerService()
+ getSystemService(DisplayManager.class)
);
mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
@@ -897,9 +902,10 @@
/* context= */ this,
config,
getMainExecutor(),
+ getMainThreadHandler(),
/* backgroundExecutor= */ UI_HELPER_EXECUTOR,
/* tracingTagPrefix= */ "launcher",
- WindowManagerGlobal.getWindowManagerService()
+ getSystemService(DisplayManager.class)
);
final RemoteUnfoldTransitionReceiver remoteUnfoldTransitionProgressProvider =
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 07fcf48..f16b43d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -91,7 +91,7 @@
builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
// TODO(b/246283207): Remove logging once root cause of flake detected.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
+ mRecentsView.getCurrentPage()
+ ", getScrollForPage(getCurrentPage())): "
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 29ef2b4..84b873d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -18,28 +18,53 @@
import static android.app.ActivityThread.currentApplication;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.Log;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.IntFlag;
+import com.android.launcher3.util.ScreenOnTracker;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Helper class to create various flags for system build
*/
public class FlagsFactory {
+ private static final String TAG = "FlagsFactory";
+
+ private static final FlagsFactory INSTANCE = new FlagsFactory();
+ private static final boolean FLAG_AUTO_APPLY_ENABLED = false;
+
public static final String FLAGS_PREF_NAME = "featureFlags";
public static final String NAMESPACE_LAUNCHER = "launcher";
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
+
+ private final Set<String> mKeySet = new HashSet<>();
+ private boolean mRestartRequested = false;
+
+ private FlagsFactory() {
+ if (!FLAG_AUTO_APPLY_ENABLED) {
+ return;
+ }
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_LAUNCHER, UI_HELPER_EXECUTOR, this::onPropertiesChanged);
+ }
+
/**
* Creates a new debug flag
*/
@@ -63,6 +88,7 @@
*/
public static BooleanFlag getReleaseFlag(
int bugId, String key, boolean defaultValueInCode, String description) {
+ INSTANCE.mKeySet.add(key);
boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
if (Utilities.IS_DEBUG_DEVICE) {
SharedPreferences prefs = currentApplication()
@@ -78,6 +104,15 @@
}
}
+ /**
+ * Creates a new integer flag. Integer flags are always release flags
+ */
+ public static IntFlag getIntFlag(
+ int bugId, String key, int defaultValueInCode, String description) {
+ INSTANCE.mKeySet.add(key);
+ return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
+ }
+
static List<DebugFlag> getDebugFlags() {
if (!Utilities.IS_DEBUG_DEVICE) {
return Collections.emptyList();
@@ -121,4 +156,28 @@
}
}
}
+
+ private void onPropertiesChanged(Properties properties) {
+ if (!Collections.disjoint(properties.getKeyset(), mKeySet)) {
+ // Schedule a restart
+ if (mRestartRequested) {
+ return;
+ }
+ Log.e(TAG, "Flag changed, scheduling restart");
+ mRestartRequested = true;
+ ScreenOnTracker sot = ScreenOnTracker.INSTANCE.get(currentApplication());
+ if (sot.isScreenOn()) {
+ sot.addListener(this::onScreenOnChanged);
+ } else {
+ onScreenOnChanged(false);
+ }
+ }
+ }
+
+ private void onScreenOnChanged(boolean isOn) {
+ if (mRestartRequested && !isOn) {
+ Log.e(TAG, "Restart requested, killing process");
+ System.exit(0);
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e264a7f..e9385d9 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -878,7 +878,7 @@
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
- mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
+ mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
}
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index d7ff8ab..3e565b3 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -350,6 +350,13 @@
}
/**
+ * Calculates the task size for the desktop task
+ */
+ public final void calculateDesktopTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ calculateFocusTaskSize(context, dp, outRect);
+ }
+
+ /**
* Calculates the modal taskView size for the provided device configuration
*/
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
new file mode 100644
index 0000000..35404a9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.quickstep
+
+import android.content.Context
+import android.util.Log
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.isBootAwareStartupDataEnabled
+import com.android.launcher3.util.LockedUserState
+
+/**
+ * Loads expensive objects in memory before the user is unlocked. This decreases experienced latency
+ * when starting the launcher for the first time after a reboot.
+ */
+object BootAwarePreloader {
+ private const val TAG = "BootAwarePreloader"
+
+ @JvmStatic
+ fun start(context: Context) {
+ val lp = LauncherPrefs.get(context)
+ when {
+ LockedUserState.get(context).isUserUnlocked || !isBootAwareStartupDataEnabled -> {
+ /* No-Op */
+ }
+ lp.isStartupDataMigrated -> {
+ Log.d(TAG, "preloading start up data")
+ LauncherAppState.INSTANCE.get(context)
+ }
+ else -> {
+ Log.d(TAG, "queuing start up data migration to boot aware prefs")
+ LockedUserState.get(context).runOnUserUnlocked {
+ lp.migrateStartupDataToDeviceProtectedStorage()
+ }
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 589459f..a8f3c3a 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -37,6 +37,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.tracing.OverviewComponentObserverProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -65,6 +66,7 @@
private final Intent mMyHomeIntent;
private final Intent mFallbackIntent;
private final SparseIntArray mConfigChangesMap = new SparseIntArray();
+ private final String mSetupWizardPkg;
private Consumer<Boolean> mOverviewChangeListener = b -> { };
@@ -86,6 +88,7 @@
new ComponentName(context.getPackageName(), info.activityInfo.name);
mMyHomeIntent.setComponent(myHomeComponent);
mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+ mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
mFallbackIntent = new Intent(Intent.ACTION_MAIN)
@@ -127,6 +130,12 @@
private void updateOverviewTargets() {
ComponentName defaultHome = PackageManagerWrapper.getInstance()
.getHomeActivities(new ArrayList<>());
+ if (defaultHome != null && defaultHome.getPackageName().equals(mSetupWizardPkg)) {
+ // Treat setup wizard as null default home, because there is a period between setup and
+ // launcher being default home where it is briefly null. Otherwise, it would appear as
+ // if overview targets are changing twice, giving the listener an incorrect signal.
+ defaultHome = null;
+ }
mIsHomeDisabled = mDeviceState.isHomeDisabled();
mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 9b00dcf..f30d3f1 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -89,7 +89,7 @@
* Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
*
* If split screen may be active when this is called, you might want to use
- * {@link #assignTargetsForSplitScreen(Context, RemoteAnimationTargets)}
+ * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
*/
public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
@@ -102,43 +102,45 @@
}
/**
- * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
- * apps in targets.apps to that of the _active_ split screened tasks.
- * See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
+ * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this assigns the
+ * apps in {@code targets.apps} to the {@link #mRemoteTargetHandles} with index 0 will being
+ * the left/top task, index 1 right/bottom.
*/
- public RemoteTargetHandle[] assignTargetsForSplitScreen(
- Context context, RemoteAnimationTargets targets) {
- int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
- return assignTargetsForSplitScreen(targets, splitIds);
- }
-
- /**
- * Assigns the provided splitIDs to the {@link #mRemoteTargetHandles}, with index 0 will being
- * the left/top task, index 1 right/bottom
- */
- public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
- int[] splitIds) {
- RemoteAnimationTarget topLeftTarget; // only one set if single/fullscreen task
- RemoteAnimationTarget bottomRightTarget;
+ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
if (mRemoteTargetHandles.length == 1) {
// If we're not in split screen, the splitIds count doesn't really matter since we
// should always hit this case.
mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
if (targets.apps.length > 0) {
// Unclear why/when target.apps length == 0, but it sure does happen :(
- topLeftTarget = targets.apps[0];
- mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, null);
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(targets.apps[0], null);
}
} else {
- // split screen
- topLeftTarget = targets.findTask(splitIds[0]);
- bottomRightTarget = targets.findTask(splitIds[1]);
+ RemoteAnimationTarget topLeftTarget = targets.apps[0];
+
+ // Fetch the adjacent target for split screen.
+ RemoteAnimationTarget bottomRightTarget = null;
+ for (int i = 1; i < targets.apps.length; i++) {
+ final RemoteAnimationTarget target = targets.apps[i];
+ Rect topLeftBounds = getStartBounds(topLeftTarget);
+ Rect bounds = getStartBounds(target);
+ if (topLeftBounds.left > bounds.right || topLeftBounds.top > bounds.bottom) {
+ bottomRightTarget = topLeftTarget;
+ topLeftTarget = target;
+ break;
+ } else if (topLeftBounds.right < bounds.left || topLeftBounds.bottom < bounds.top) {
+ bottomRightTarget = target;
+ break;
+ }
+ }
// remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
// vice versa
mSplitBounds = new SplitBounds(
getStartBounds(topLeftTarget),
- getStartBounds(bottomRightTarget), splitIds[0], splitIds[1]);
+ getStartBounds(bottomRightTarget),
+ topLeftTarget.taskId,
+ bottomRightTarget.taskId);
mRemoteTargetHandles[0].mTransformParams.setTargetSet(
createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 72330ef..6e47ff4 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -81,7 +81,8 @@
boolean isTablet = activity.getDeviceProfile().isTablet;
boolean isGridOnlyOverview = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
- // Add overview actions to the menu when in in-place rotate landscape mode.
+ // Add overview actions to the menu when in in-place rotate landscape mode, or in
+ // grid-only overview.
if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
// Add screenshot action to task menu.
List<SystemShortcut> screenshotShortcuts = TaskShortcutFactory.SCREENSHOT
@@ -90,8 +91,9 @@
shortcuts.addAll(screenshotShortcuts);
}
- // Add modal action only if display orientation is the same as the device orientation.
- if (orientedState.getDisplayRotation() == ROTATION_0) {
+ // Add modal action only if display orientation is the same as the device orientation,
+ // or in grid-only overview.
+ if (orientedState.getDisplayRotation() == ROTATION_0 || isGridOnlyOverview) {
List<SystemShortcut> modalShortcuts = TaskShortcutFactory.MODAL
.getShortcuts(activity, taskContainer);
if (modalShortcuts != null) {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 1a72e3f..da97df6 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -190,7 +190,7 @@
if (forDesktop) {
remoteTargetHandles = gluer.assignTargetsForDesktop(targets);
} else if (v.containsMultipleTasks()) {
- remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets, v.getTaskIds());
+ remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
} else {
remoteTargetHandles = gluer.assignTargets(targets);
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ff81e08..4680608 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -410,6 +410,7 @@
mDeviceState = new RecentsAnimationDeviceState(this, true);
mTaskbarManager = new TaskbarManager(this);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+ BootAwarePreloader.start(this);
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
@@ -1131,6 +1132,10 @@
return;
}
+ // TODO(b/258022658): Remove temporary logging.
+ Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
+ + ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
+
mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
}
diff --git a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
index ac0c17d..bd0ce34 100644
--- a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
+++ b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
@@ -15,17 +15,30 @@
*/
package com.android.quickstep.interaction;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+
import android.content.Context;
import android.graphics.Insets;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowInsets;
import android.widget.RelativeLayout;
+import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
/** Root layout that TutorialFragment uses to intercept motion events. */
public class RootSandboxLayout extends RelativeLayout {
+
+ private View mFeedbackView;
+ private View mTutorialStepView;
+ private View mSkipButton;
+ private View mDoneButton;
+
public RootSandboxLayout(Context context) {
super(context);
}
@@ -52,4 +65,51 @@
return getHeight() + insets.top + insets.bottom;
}
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ return;
+ }
+ mFeedbackView = findViewById(R.id.gesture_tutorial_fragment_feedback_view);
+ mTutorialStepView =
+ mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step);
+ mSkipButton = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_close_button);
+ mDoneButton = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_action_button);
+
+ mFeedbackView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (mSkipButton.getVisibility() != VISIBLE
+ && mDoneButton.getVisibility() != VISIBLE) {
+ return;
+ }
+ // Either the skip or the done button is ever shown at once, never both.
+ boolean showingSkipButton = mSkipButton.getVisibility() == VISIBLE;
+ boolean isRTL = Utilities.isRtl(getContext().getResources());
+ updateTutorialStepViewTranslation(
+ showingSkipButton ? mSkipButton : mDoneButton,
+ // Translate the step indicator away from whichever button is being
+ // shown. The skip button in on the left in LTR or on the right in RTL.
+ // The done button is on the right in LTR or left in RTL.
+ (showingSkipButton && !isRTL) || (!showingSkipButton && isRTL));
+ });
+ }
+
+ private void updateTutorialStepViewTranslation(
+ @NonNull View anchorView, boolean translateToRight) {
+ mTutorialStepView.setTranslationX(translateToRight
+ ? Math.min(
+ // Translate to the right if the views are overlapping on large fonts and
+ // display sizes.
+ Math.max(0, anchorView.getRight() - mTutorialStepView.getLeft()),
+ // Do not translate beyond the bounds of the container view.
+ mFeedbackView.getWidth() - mTutorialStepView.getRight())
+ : Math.max(
+ // Translate to the left if the views are overlapping on large fonts and
+ // display sizes.
+ Math.min(0, anchorView.getLeft() - mTutorialStepView.getRight()),
+ // Do not translate beyond the bounds of the container view.
+ -mTutorialStepView.getLeft()));
+ }
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 0a155cb..4690d94 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -139,7 +139,7 @@
if (IS_VERBOSE) {
Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
}
- if (!Utilities.ATLEAST_R || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (!Utilities.ATLEAST_R || Utilities.isRunningInTestHarness()) {
return;
}
SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
@@ -438,7 +438,7 @@
}
// TODO: remove this when b/231648228 is fixed.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return;
}
int cardinality = mCardinality.orElseGet(() -> getCardinality(atomInfo));
@@ -636,7 +636,7 @@
}
private static int getCardinality(LauncherAtom.ItemInfo info) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return 0;
}
switch (info.getContainerInfo().getContainerCase()) {
@@ -758,7 +758,7 @@
}
private static int getHierarchy(LauncherAtom.ItemInfo info) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return 0;
}
if (info.getContainerInfo().getContainerCase() == FOLDER) {
@@ -801,7 +801,7 @@
}
private static int getSearchAttributes(LauncherAtom.ItemInfo info) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return 0;
}
ContainerInfo containerInfo = info.getContainerInfo();
diff --git a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
index effdfdd..f6b2441 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
@@ -15,12 +15,13 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_MOVE_FROM_CENTER_ANIM;
+
import android.annotation.NonNull;
import android.view.View;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.launcher3.Reorderable;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
/**
@@ -31,12 +32,9 @@
@Override
public void apply(@NonNull View view, float x, float y) {
- if (view instanceof NavigableAppWidgetHostView) {
- ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
- } else if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationForMoveFromCenterAnimation(x, y);
+ if (view instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) view).getTranslateDelegate();
+ mtd.setTranslation(INDEX_MOVE_FROM_CENTER_ANIM, x, y);
} else {
view.setTranslationX(x);
view.setTranslationY(y);
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 69ed2f8..4bc41bc 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -129,7 +129,7 @@
* @param pointerIndex Index for the pointer being tracked in the motion event
*/
public void addPosition(MotionEvent ev, int pointerIndex) {
- long timeoutMs = Utilities.IS_RUNNING_IN_TEST_HARNESS
+ long timeoutMs = Utilities.isRunningInTestHarness()
? TEST_HARNESS_TRIGGER_TIMEOUT
: mMakePauseHarderToTrigger
? HARDER_TRIGGER_TIMEOUT
@@ -195,7 +195,7 @@
if (mIsPaused != isPaused) {
mIsPaused = isPaused;
String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason;
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d(TAG, logString);
}
ActiveGestureLog.INSTANCE.addLog(logString);
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index e928b27..cf07e6e 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -64,7 +64,7 @@
});
}
- if (!Utilities.IS_RUNNING_IN_TEST_HARNESS
+ if (!Utilities.isRunningInTestHarness()
&& !hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
boolean mFromAllApps = false;
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index d1100c7..5214f7c 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -29,7 +29,6 @@
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -58,6 +57,7 @@
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.quickstep.RecentsModel;
@@ -162,7 +162,7 @@
* Used in various task-switching or splitscreen operations when we need to check if there is a
* currently running Task of a certain type and use the most recent one.
*/
- public void findLastActiveTaskAndRunCallback(ComponentName componentName,
+ public void findLastActiveTaskAndRunCallback(ComponentKey componentKey,
Consumer<Task> callback) {
mRecentTasksModel.getTasks(taskGroups -> {
Task lastActiveTask = null;
@@ -170,12 +170,12 @@
for (int i = taskGroups.size() - 1; i >= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
Task task1 = groupTask.task1;
- if (isInstanceOfComponent(task1, componentName)) {
+ if (isInstanceOfComponent(task1, componentKey)) {
lastActiveTask = task1;
break;
}
Task task2 = groupTask.task2;
- if (isInstanceOfComponent(task2, componentName)) {
+ if (isInstanceOfComponent(task2, componentKey)) {
lastActiveTask = task2;
break;
}
@@ -189,13 +189,14 @@
* Checks if a given Task is the most recently-active Task of type componentName. Used for
* selecting already-running Tasks for splitscreen.
*/
- public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
+ public boolean isInstanceOfComponent(@Nullable Task task, ComponentKey componentKey) {
// Exclude the task that is already staged
if (task == null || task.key.id == mInitialTaskId) {
return false;
}
- return task.key.baseIntent.getComponent().equals(componentName);
+ return task.key.baseIntent.getComponent().equals(componentKey.componentName)
+ && task.key.userId == componentKey.user.getIdentifier();
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 89177b6..14898b1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -381,6 +381,7 @@
}
setOverlayEnabled(false);
onTaskListVisibilityChanged(false);
+ setVisibility(VISIBLE);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f18f268..ac59403 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -452,6 +452,7 @@
protected final Rect mLastComputedTaskSize = new Rect();
protected final Rect mLastComputedGridSize = new Rect();
protected final Rect mLastComputedGridTaskSize = new Rect();
+ protected final Rect mLastComputedDesktopTaskSize = new Rect();
private TaskView mSelectedTask = null;
// How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
@Nullable
@@ -707,6 +708,12 @@
private ObjectAnimator mActionsViewAlphaAnimator;
private float mActionsViewAlphaAnimatorFinalValue;
+ /**
+ * Keeps track of the desktop task. Optional and only present when the feature flag is enabled.
+ */
+ @Nullable
+ private DesktopTaskView mDesktopTaskView;
+
private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
new MultiWindowModeChangedListener() {
@Override
@@ -1587,6 +1594,8 @@
// update the map of instance counts
mFilterState.updateInstanceCountMap(taskGroups);
+ // Clear out desktop view if it is set
+ mDesktopTaskView = null;
DesktopTask desktopTask = null;
// Add views as children based on whether it's grouped or single task. Looping through
@@ -1645,12 +1654,13 @@
if (!taskGroups.isEmpty()) {
addView(mClearAllButton);
- if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
- TaskView taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
+ && !getSplitSelectController().isSplitSelectActive()) {
+ mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(TaskView.Type.DESKTOP);
// Always add a desktop task to the first position. Even if it is empty
- addView(taskView, 0);
+ addView(mDesktopTaskView, 0);
ArrayList<Task> tasks = desktopTask != null ? desktopTask.tasks : new ArrayList<>();
- ((DesktopTaskView) taskView).bind(tasks, mOrientationState);
+ mDesktopTaskView.bind(tasks, mOrientationState);
}
}
@@ -1710,7 +1720,7 @@
int finalTargetPage = targetPage;
runOnPageScrollsInitialized(() -> {
// TODO(b/246283207): Remove logging once root cause of flake detected.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
+ "previousCurrentPage: " + previousCurrentPage
+ ", targetPage: " + finalTargetPage
@@ -1942,6 +1952,10 @@
mLastComputedGridSize);
mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedGridTaskSize, mOrientationHandler);
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ mSizeStrategy.calculateDesktopTaskSize(mActivity, mActivity.getDeviceProfile(),
+ mLastComputedDesktopTaskSize);
+ }
mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
mTopBottomRowHeightDiff =
@@ -2032,6 +2046,11 @@
return mLastComputedGridTaskSize;
}
+ /** Gets the last computed desktop task size */
+ public Rect getLastComputedDesktopTaskSize() {
+ return mLastComputedDesktopTaskSize;
+ }
+
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
@@ -2766,10 +2785,17 @@
} else if (taskView.isDesktopTask()) {
// Desktop task was not focused. Pin it to the right of focused
desktopTaskIndex = i;
- gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+ if (taskView.getVisibility() == View.GONE) {
+ // Desktop task view is hidden, skip it from grid calculations
+ continue;
+ }
+ if (!ENABLE_GRID_ONLY_OVERVIEW.get()) {
+ // Only apply x-translation when using legacy overview grid
+ gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+ }
// Center view vertically in case it's from different orientation.
- taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
+ taskView.setGridTranslationY((mLastComputedDesktopTaskSize.height() + taskTopMargin
- taskView.getLayoutParams().height) / 2f);
} else {
if (i > focusedTaskIndex) {
@@ -4456,6 +4482,9 @@
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ updateDesktopTaskVisibility(false /* visible */);
+ }
}
/**
@@ -4473,6 +4502,15 @@
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ updateDesktopTaskVisibility(false /* visible */);
+ }
+ }
+
+ private void updateDesktopTaskVisibility(boolean visible) {
+ if (mDesktopTaskView != null) {
+ mDesktopTaskView.setVisibility(visible ? VISIBLE : GONE);
+ }
}
/**
@@ -4623,6 +4661,9 @@
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
mSplitHiddenTaskView = null;
}
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ updateDesktopTaskVisibility(true /* visible */);
+ }
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -4994,8 +5035,7 @@
mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
} else {
gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
- mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(
- getContext(), recentsAnimationTargets);
+ mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
}
mSplitBoundsConfig = gluer.getSplitBounds();
// Add release check to the targets from the RemoteTargetGluer and not the targets
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b9aaef6..ab72f2d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1619,7 +1619,12 @@
int boxWidth;
int boxHeight;
boolean isFocusedTask = isFocusedTask();
- if (isFocusedTask || isDesktopTask()) {
+ if (isDesktopTask()) {
+ Rect lastComputedDesktopTaskSize =
+ getRecentsView().getLastComputedDesktopTaskSize();
+ boxWidth = lastComputedDesktopTaskSize.width();
+ boxHeight = lastComputedDesktopTaskSize.height();
+ } else if (isFocusedTask) {
// Task will be focused and should use focused task size. Use focusTaskRatio
// that is associated with the original orientation of the focused task.
boxWidth = taskWidth;
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 5abdc93..512df8e 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -23,12 +23,14 @@
import android.content.Intent
import android.graphics.Rect
import android.os.Handler
+import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.LauncherState
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.withArgCaptor
import com.android.quickstep.RecentsModel
@@ -60,6 +62,9 @@
lateinit var splitSelectStateController: SplitSelectStateController
+ private val primaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId)
+ private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -77,6 +82,7 @@
@Test
fun activeTasks_noMatchingTasks() {
+ val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
val groupTask1 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
@@ -100,7 +106,7 @@
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
splitSelectStateController.findLastActiveTaskAndRunCallback(
- ComponentName("no", "match"),
+ nonMatchingComponent,
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -114,6 +120,8 @@
fun activeTasks_singleMatchingTask() {
val matchingPackage = "hotdog"
val matchingClass = "juice"
+ val matchingComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
@@ -149,7 +157,100 @@
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
splitSelectStateController.findLastActiveTaskAndRunCallback(
- ComponentName(matchingPackage, matchingClass),
+ matchingComponent,
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+
+ // Send our mocked tasks
+ consumer.accept(tasks)
+ }
+
+ @Test
+ fun activeTasks_skipTaskWithDifferentUser() {
+ val matchingPackage = "hotdog"
+ val matchingClass = "juice"
+ val nonPrimaryUserComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
+ val groupTask1 =
+ generateGroupTask(
+ ComponentName(matchingPackage, matchingClass),
+ ComponentName("pomegranate", "juice")
+ )
+ val groupTask2 =
+ generateGroupTask(
+ ComponentName("pumpkin", "pie"),
+ ComponentName("personal", "computer")
+ )
+ val tasks: ArrayList<GroupTask> = ArrayList()
+ tasks.add(groupTask1)
+ tasks.add(groupTask2)
+
+ // Assertions happen in the callback we get from what we pass into
+ // #findLastActiveTaskAndRunCallback
+ val taskConsumer =
+ Consumer<Task> { assertNull("No tasks should have matched", it /*task*/) }
+
+ // Capture callback from recentsModel#getTasks()
+ val consumer =
+ withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTaskAndRunCallback(
+ nonPrimaryUserComponent,
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+
+ // Send our mocked tasks
+ consumer.accept(tasks)
+ }
+
+ @Test
+ fun activeTasks_findTaskAsNonPrimaryUser() {
+ val matchingPackage = "hotdog"
+ val matchingClass = "juice"
+ val nonPrimaryUserComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
+ val groupTask1 =
+ generateGroupTask(
+ ComponentName(matchingPackage, matchingClass),
+ nonPrimaryUserHandle,
+ ComponentName("pomegranate", "juice"),
+ nonPrimaryUserHandle
+ )
+ val groupTask2 =
+ generateGroupTask(
+ ComponentName("pumpkin", "pie"),
+ ComponentName("personal", "computer")
+ )
+ val tasks: ArrayList<GroupTask> = ArrayList()
+ tasks.add(groupTask1)
+ tasks.add(groupTask2)
+
+ // Assertions happen in the callback we get from what we pass into
+ // #findLastActiveTaskAndRunCallback
+ val taskConsumer =
+ Consumer<Task> {
+ assertEquals(
+ "ComponentName package mismatched",
+ it.key.baseIntent.component.packageName,
+ matchingPackage
+ )
+ assertEquals(
+ "ComponentName class mismatched",
+ it.key.baseIntent.component.className,
+ matchingClass
+ )
+ assertEquals("userId mismatched", it.key.userId, nonPrimaryUserHandle.identifier)
+ assertEquals(it, groupTask1.task1)
+ }
+
+ // Capture callback from recentsModel#getTasks()
+ val consumer =
+ withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTaskAndRunCallback(
+ nonPrimaryUserComponent,
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -163,6 +264,8 @@
fun activeTasks_multipleMatchMostRecentTask() {
val matchingPackage = "hotdog"
val matchingClass = "juice"
+ val matchingComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
@@ -198,7 +301,7 @@
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
splitSelectStateController.findLastActiveTaskAndRunCallback(
- ComponentName(matchingPackage, matchingClass),
+ matchingComponent,
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -245,6 +348,7 @@
assertFalse(splitSelectStateController.isSplitSelectActive)
}
+ // Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
task2ComponentName: ComponentName
@@ -268,4 +372,34 @@
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1)
)
}
+
+ // Generate GroupTask with custom user handles.
+ private fun generateGroupTask(
+ task1ComponentName: ComponentName,
+ userHandle1: UserHandle,
+ task2ComponentName: ComponentName,
+ userHandle2: UserHandle
+ ): GroupTask {
+ val task1 = Task()
+ var taskInfo = ActivityManager.RunningTaskInfo()
+ // Apply custom userHandle1
+ taskInfo.userId = userHandle1.identifier
+ var intent = Intent()
+ intent.component = task1ComponentName
+ taskInfo.baseIntent = intent
+ task1.key = Task.TaskKey(taskInfo)
+ val task2 = Task()
+ taskInfo = ActivityManager.RunningTaskInfo()
+ // Apply custom userHandle2
+ taskInfo.userId = userHandle2.identifier
+ intent = Intent()
+ intent.component = task2ComponentName
+ taskInfo.baseIntent = intent
+ task2.key = Task.TaskKey(taskInfo)
+ return GroupTask(
+ task1,
+ task2,
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1)
+ )
+ }
}
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index b02e3e3..455217f 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -23,19 +23,20 @@
android:clipToPadding="false"
android:layout_below="@id/collapse_handle"
android:descendantFocusability="afterDescendants"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
launcher:pageIndicator="@+id/tabs" >
<com.android.launcher3.widget.picker.WidgetsRecyclerView
android:id="@+id/primary_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToPadding="false" />
<com.android.launcher3.widget.picker.WidgetsRecyclerView
android:id="@+id/work_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToPadding="false" />
</com.android.launcher3.widget.picker.WidgetPagedView>
@@ -47,6 +48,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/collapse_handle"
android:paddingBottom="0dp"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToOutline="true"
android:orientation="vertical">
@@ -57,7 +59,6 @@
android:gravity="center_horizontal"
android:textSize="24sp"
android:layout_marginTop="24dp"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/widget_button_text"/>
@@ -68,7 +69,6 @@
android:elevation="0.1dp"
android:background="?android:attr/colorBackground"
android:paddingBottom="8dp"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
launcher:layout_sticky="true">
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
@@ -80,7 +80,6 @@
android:layout_marginTop="8dp"
android:background="@drawable/widgets_surface_background"
android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:visibility="gone" />
<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
@@ -90,7 +89,6 @@
android:gravity="center_horizontal"
android:orientation="horizontal"
android:paddingVertical="8dp"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:background="?android:attr/colorBackground"
style="@style/TextHeadline"
launcher:layout_sticky="true">
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 366d2d2..887f00c 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -20,7 +20,7 @@
android:layout_below="@id/collapse_handle"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToPadding="false" />
<!-- SearchAndRecommendationsView without the tab layout as well -->
@@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/collapse_handle"
android:paddingBottom="16dp"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToOutline="true"
android:orientation="vertical">
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 94b8cd8..bc4a5c3 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -827,6 +827,6 @@
private boolean hasSeenReconfigurableWidgetEducationTip() {
return mLauncher.getSharedPrefs()
.getBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, false)
- || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+ || Utilities.isRunningInTestHarness();
}
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index df38c26..3eb03ed 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -34,7 +34,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -71,6 +70,7 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.views.ActivityContext;
@@ -100,21 +100,8 @@
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
- private final PointF mTranslationForReorderBounce = new PointF(0, 0);
- private final PointF mTranslationForReorderPreview = new PointF(0, 0);
-
- private float mTranslationXForTaskbarAlignmentAnimation = 0f;
- private float mTranslationYForTaskbarAlignmentAnimation = 0f;
-
- private float mTranslationXForTaskbarRevealAnimation = 0f;
- private float mTranslationYForTaskbarRevealAnimation = 0f;
-
- private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
-
private float mScaleForReorderBounce = 1f;
- private float mTranslationXForTaskbarAllAppsIcon = 0f;
-
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@Override
@@ -142,6 +129,7 @@
}
};
+ private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
private final ActivityContext mActivity;
private FastBitmapDrawable mIcon;
private boolean mCenterVertically;
@@ -960,131 +948,23 @@
mDisplay == DISPLAY_SEARCH_RESULT_SMALL;
}
- private void updateTranslation() {
- super.setTranslationX(mTranslationForReorderBounce.x
- + mTranslationForReorderPreview.x
- + mTranslationXForTaskbarAllAppsIcon
- + mTranslationForMoveFromCenterAnimation.x
- + mTranslationXForTaskbarAlignmentAnimation
- + mTranslationXForTaskbarRevealAnimation
- );
- super.setTranslationY(mTranslationForReorderBounce.y
- + mTranslationForReorderPreview.y
- + mTranslationForMoveFromCenterAnimation.y
- + mTranslationYForTaskbarAlignmentAnimation
- + mTranslationYForTaskbarRevealAnimation);
- }
-
- /**
- * Sets translationX for taskbar all apps icon
- */
- public void setTranslationXForTaskbarAllAppsIcon(float translationX) {
- mTranslationXForTaskbarAllAppsIcon = translationX;
- updateTranslation();
- }
-
- public void setReorderBounceOffset(float x, float y) {
- mTranslationForReorderBounce.set(x, y);
- updateTranslation();
- }
-
- public void getReorderBounceOffset(PointF offset) {
- offset.set(mTranslationForReorderBounce);
+ @Override
+ public MultiTranslateDelegate getTranslateDelegate() {
+ return mTranslateDelegate;
}
@Override
- public void setReorderPreviewOffset(float x, float y) {
- mTranslationForReorderPreview.set(x, y);
- updateTranslation();
- }
-
- @Override
- public void getReorderPreviewOffset(PointF offset) {
- offset.set(mTranslationForReorderPreview);
- }
-
public void setReorderBounceScale(float scale) {
mScaleForReorderBounce = scale;
super.setScaleX(scale);
super.setScaleY(scale);
}
+ @Override
public float getReorderBounceScale() {
return mScaleForReorderBounce;
}
- /**
- * Sets translation values for move from center animation
- */
- public void setTranslationForMoveFromCenterAnimation(float x, float y) {
- mTranslationForMoveFromCenterAnimation.set(x, y);
- updateTranslation();
- }
-
- /**
- * Sets translationX for taskbar to launcher alignment animation
- */
- public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
- mTranslationXForTaskbarAlignmentAnimation = translationX;
- updateTranslation();
- }
-
- /**
- * Returns translationX value for taskbar to launcher alignment animation
- */
- public float getTranslationXForTaskbarAlignmentAnimation() {
- return mTranslationXForTaskbarAlignmentAnimation;
- }
-
- /**
- * Sets translationX for taskbar to launcher alignment animation
- */
- public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
- mTranslationYForTaskbarAlignmentAnimation = translationY;
- updateTranslation();
- }
-
- /**
- * Returns translationY value for taskbar to launcher alignment animation
- */
- public float getTranslationYForTaskbarAlignmentAnimation() {
- return mTranslationYForTaskbarAlignmentAnimation;
- }
-
- /**
- * Sets translationX value for taskbar reveal animation
- */
- public void setTranslationXForTaskbarRevealAnimation(float translationX) {
- mTranslationXForTaskbarRevealAnimation = translationX;
- updateTranslation();
- }
-
- /**
- * Returns translation values for taskbar reveal animation
- */
- public float getTranslationXForTaskbarRevealAnimation() {
- return mTranslationXForTaskbarRevealAnimation;
- }
-
- /**
- * Sets translationY value for taskbar reveal animation
- */
- public void setTranslationYForTaskbarRevealAnimation(float translationY) {
- mTranslationYForTaskbarRevealAnimation = translationY;
- updateTranslation();
- }
-
- /**
- * Returns translationY values for taskbar reveal animation
- */
- public float getTranslationYForTaskbarRevealAnimation() {
- return mTranslationYForTaskbarRevealAnimation;
- }
-
- public View getView() {
- return this;
- }
-
@Override
public int getViewType() {
return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index b96e4df..731ad74 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -22,6 +22,8 @@
import static com.android.launcher3.config.FeatureFlags.SHOW_HOME_GARDENING;
import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -70,6 +72,7 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
@@ -1099,13 +1102,12 @@
lp.isLockedToGrid = false;
// End compute new x and y
- item.getReorderPreviewOffset(mTmpPointF);
- final float initPreviewOffsetX = mTmpPointF.x;
- final float initPreviewOffsetY = mTmpPointF.y;
+ MultiTranslateDelegate mtd = item.getTranslateDelegate();
+ float initPreviewOffsetX = mtd.getTranslationX(INDEX_REORDER_PREVIEW_OFFSET).getValue();
+ float initPreviewOffsetY = mtd.getTranslationY(INDEX_REORDER_PREVIEW_OFFSET).getValue();
final float finalPreviewOffsetX = newX - oldX;
final float finalPreviewOffsetY = newY - oldY;
-
// Exit early if we're not actually moving the view
if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
&& initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
@@ -1123,7 +1125,7 @@
float r = (Float) animation.getAnimatedValue();
float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
- item.setReorderPreviewOffset(x, y);
+ item.getTranslateDelegate().setTranslation(INDEX_REORDER_PREVIEW_OFFSET, x, y);
}
});
va.addListener(new AnimatorListenerAdapter() {
@@ -1134,7 +1136,8 @@
// place just yet.
if (!cancelled) {
lp.isLockedToGrid = true;
- item.setReorderPreviewOffset(0, 0);
+ item.getTranslateDelegate()
+ .setTranslation(INDEX_REORDER_PREVIEW_OFFSET, 0, 0);
child.requestLayout();
}
if (mReorderAnimators.containsKey(lp)) {
@@ -1434,7 +1437,7 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
if (c != null && !skip && (child instanceof Reorderable)) {
- ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+ ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child,
mode, lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY);
rha.animate();
}
@@ -1456,8 +1459,8 @@
// Class which represents the reorder preview animations. These animations show that an item is
// in a temporary state, and hint at where the item will return to.
- class ReorderPreviewAnimation {
- final Reorderable child;
+ class ReorderPreviewAnimation<T extends View & Reorderable> {
+ final T child;
float finalDeltaX;
float finalDeltaY;
float initDeltaX;
@@ -1477,7 +1480,7 @@
float animationProgress = 0;
ValueAnimator a;
- public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
+ ReorderPreviewAnimation(View childView, int mode, int cellX0, int cellY0,
int cellX1, int cellY1, int spanX, int spanY) {
regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
final int x0 = mTmpPoint[0];
@@ -1488,16 +1491,16 @@
final int dX = x1 - x0;
final int dY = y1 - y0;
- this.child = child;
+ this.child = (T) childView;
this.mode = mode;
finalDeltaX = 0;
finalDeltaY = 0;
- child.getReorderBounceOffset(mTmpPointF);
- initDeltaX = mTmpPointF.x;
- initDeltaY = mTmpPointF.y;
+ MultiTranslateDelegate mtd = child.getTranslateDelegate();
+ initDeltaX = mtd.getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).getValue();
+ initDeltaY = mtd.getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).getValue();
initScale = child.getReorderBounceScale();
- finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+ finalScale = mChildScale - (CHILD_DIVIDEND / child.getWidth()) * initScale;
int dir = mode == MODE_HINT ? -1 : 1;
if (dX == dY && dX == 0) {
@@ -1573,7 +1576,7 @@
float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
- child.setReorderBounceOffset(x, y);
+ child.getTranslateDelegate().setTranslation(INDEX_REORDER_BOUNCE_OFFSET, x, y);
float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
child.setReorderBounceScale(s);
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 4ac7f07..a498323 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -59,6 +59,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Partner;
import com.android.launcher3.util.Themes;
@@ -205,7 +206,9 @@
if (!newGridName.equals(gridName)) {
LauncherPrefs.get(context).put(GRID_NAME, newGridName);
}
- new DeviceGridState(this).writeToPrefs(context);
+ LockedUserState.get(context).runOnUserUnlocked(() -> {
+ new DeviceGridState(this).writeToPrefs(context);
+ });
DisplayController.INSTANCE.get(context).setPriorityListener(
(displayContext, info, flags) -> {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index de60d05..22b07ef 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1647,7 +1647,7 @@
@Override
protected void onNewIntent(Intent intent) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
}
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
@@ -3172,7 +3172,7 @@
// Setting the touch point to (-1, -1) will show the options popup in the center of
// the screen.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
}
showDefaultOptions(-1, -1);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 2b98d98..c81ad01 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -44,6 +44,7 @@
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
@@ -106,25 +107,27 @@
}
mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
- CustomWidgetManager.INSTANCE.get(mContext)
- .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
-
SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
.addUserChangeListener(mModel::forceReload);
mOnTerminateCallback.add(userChangeListener::close);
- IconObserver observer = new IconObserver();
- SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
- observer, MODEL_EXECUTOR.getHandler());
- mOnTerminateCallback.add(iconChangeTracker::close);
- MODEL_EXECUTOR.execute(observer::verifyIconChanged);
- LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
- mOnTerminateCallback.add(
- () -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
+ LockedUserState.get(context).runOnUserUnlocked(() -> {
+ CustomWidgetManager.INSTANCE.get(mContext)
+ .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
- InstallSessionTracker installSessionTracker =
- InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
- mOnTerminateCallback.add(installSessionTracker::unregister);
+ IconObserver observer = new IconObserver();
+ SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
+ observer, MODEL_EXECUTOR.getHandler());
+ mOnTerminateCallback.add(iconChangeTracker::close);
+ MODEL_EXECUTOR.execute(observer::verifyIconChanged);
+ LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
+ mOnTerminateCallback.add(
+ () -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
+
+ InstallSessionTracker installSessionTracker =
+ InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
+ mOnTerminateCallback.add(installSessionTracker::unregister);
+ });
// Register an observer to rebind the notification listener when dots are re-enabled.
SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 5680c18..e675add 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -1,8 +1,25 @@
+/*
+ * Copyright (C) 2023 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
import android.content.Context
+import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
@@ -20,10 +37,34 @@
*
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
*/
-class LauncherPrefs(private val context: Context) {
+class LauncherPrefs(private val encryptedContext: Context) {
+ private val deviceProtectedStorageContext =
+ encryptedContext.createDeviceProtectedStorageContext()
+
+ private val bootAwarePrefs
+ get() =
+ deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
+
+ private val Item.encryptedPrefs
+ get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
+
+ // This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so
+ // would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences`
+ val isStartupDataMigrated: Boolean
+ get() =
+ bootAwarePrefs.getBoolean(
+ IS_STARTUP_DATA_MIGRATED.sharedPrefKey,
+ IS_STARTUP_DATA_MIGRATED.defaultValue
+ )
+
+ private fun chooseSharedPreferences(item: Item): SharedPreferences =
+ if (isBootAwareStartupDataEnabled && item.isBootAware && isStartupDataMigrated)
+ bootAwarePrefs
+ else item.encryptedPrefs
/** Wrapper around `getInner` for a `ContextualItem` */
- fun <T> get(item: ContextualItem<T>): T = getInner(item, item.defaultValueFromContext(context))
+ fun <T> get(item: ContextualItem<T>): T =
+ getInner(item, item.defaultValueFromContext(encryptedContext))
/** Wrapper around `getInner` for an `Item` */
fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
@@ -35,7 +76,7 @@
*/
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
private fun <T> getInner(item: Item, default: T): T {
- val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
+ val sp = chooseSharedPreferences(item)
return when (item.type) {
String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
@@ -68,16 +109,8 @@
fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.apply() }
- /**
- * Stores the value provided in `SharedPreferences` according to the item configuration provided
- * It is asynchronous, so the caller can't assume that the value put is immediately available.
- */
- fun <T : Any> put(item: Item, value: T): Unit =
- context
- .getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
- .edit()
- .putValue(item, value)
- .apply()
+ /** See referenced `put` method above. */
+ fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
/**
* Synchronously stores all the values provided according to their associated Item
@@ -87,27 +120,35 @@
prepareToPutValues(itemsToValues).forEach { it.commit() }
/**
- * Update each shared preference file with the item - value pairs provided. This method is
- * optimized to avoid retrieving the same shared preference file multiple times.
+ * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
+ * the item is boot aware, this method updates both the boot aware and the encrypted files. This
+ * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
+ * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
+ * already points to encrypted storage.
*
- * @return `List<SharedPreferences.Editor>` 1 for each distinct shared preference file among the
- * items given as part of the itemsToValues parameter
+ * Returns a list of editors with all transactions added so that the caller can determine to use
+ * .apply() or .commit()
*/
private fun prepareToPutValues(
- itemsToValues: Array<out Pair<Item, Any>>
- ): List<SharedPreferences.Editor> =
- itemsToValues
- .groupBy { it.first.sharedPrefFile }
- .map { fileToItemValueList ->
- context
- .getSharedPreferences(fileToItemValueList.key, Context.MODE_PRIVATE)
- .edit()
- .apply {
- fileToItemValueList.value.forEach { itemToValue ->
- putValue(itemToValue.first, itemToValue.second)
- }
- }
+ updates: Array<out Pair<Item, Any>>
+ ): List<SharedPreferences.Editor> {
+ val updatesPerPrefFile = updates.groupBy { it.first.encryptedPrefs }.toMutableMap()
+
+ if (isBootAwareStartupDataEnabled) {
+ val bootAwareUpdates = updates.filter { it.first.isBootAware }
+ if (bootAwareUpdates.isNotEmpty()) {
+ updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
}
+ }
+
+ return updatesPerPrefFile.map { prefToItemValueList ->
+ prefToItemValueList.key.edit().apply {
+ prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
+ putValue(itemToValue.first, itemToValue.second)
+ }
+ }
+ }
+ }
/**
* Handles adding values to `SharedPreferences` regardless of type. This method is especially
@@ -117,10 +158,10 @@
@Suppress("UNCHECKED_CAST")
private fun SharedPreferences.Editor.putValue(
item: Item,
- value: Any
+ value: Any?
): SharedPreferences.Editor =
- when (value::class.java) {
- String::class.java -> putString(item.sharedPrefKey, value as String)
+ when (item.type) {
+ String::class.java -> putString(item.sharedPrefKey, value as? String)
Boolean::class.java,
java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
Int::class.java,
@@ -129,10 +170,10 @@
java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
Long::class.java,
java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
- Set::class.java -> putStringSet(item.sharedPrefKey, value as Set<String>)
+ Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
else ->
throw IllegalArgumentException(
- "item type: ${value::class} is not compatible with sharedPref methods"
+ "item type: ${item.type} is not compatible with sharedPref methods"
)
}
@@ -143,13 +184,9 @@
*/
fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
items
- .map { it.sharedPrefFile }
+ .map { chooseSharedPreferences(it) }
.distinct()
- .forEach {
- context
- .getSharedPreferences(it, Context.MODE_PRIVATE)
- .registerOnSharedPreferenceChangeListener(listener)
- }
+ .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
}
/**
@@ -159,13 +196,9 @@
fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
// If a listener is not registered to a SharedPreference, unregistering it does nothing
items
- .map { it.sharedPrefFile }
+ .map { chooseSharedPreferences(it) }
.distinct()
- .forEach {
- context
- .getSharedPreferences(it, Context.MODE_PRIVATE)
- .unregisterOnSharedPreferenceChangeListener(listener)
- }
+ .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
}
/**
@@ -174,10 +207,8 @@
*/
fun has(vararg items: Item): Boolean {
items
- .groupBy { it.sharedPrefFile }
- .forEach { (file, itemsSublist) ->
- val prefs: SharedPreferences =
- context.getSharedPreferences(file, Context.MODE_PRIVATE)
+ .groupBy { chooseSharedPreferences(it) }
+ .forEach { (prefs, itemsSublist) ->
if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
}
return true
@@ -192,62 +223,128 @@
fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
/**
- * Creates `SharedPreferences.Editor` transactions for removing all the provided [Item] values
- * from their respective `SharedPreferences` files. These returned `Editors` can then be
- * committed or applied for synchronous or async behavior.
+ * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
+ * item is boot aware, this method removes the data from both the boot aware and encrypted
+ * files.
+ *
+ * @return a list of editors with all transactions added so that the caller can determine to use
+ * .apply() or .commit()
*/
- private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> =
- items
- .groupBy { it.sharedPrefFile }
- .map { (file, items) ->
- context.getSharedPreferences(file, Context.MODE_PRIVATE).edit().also { editor ->
- items.forEach { item -> editor.remove(item.sharedPrefKey) }
- }
+ private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
+ val itemsPerFile = items.groupBy { it.encryptedPrefs }.toMutableMap()
+
+ if (isBootAwareStartupDataEnabled) {
+ val bootAwareUpdates = items.filter { it.isBootAware }
+ if (bootAwareUpdates.isNotEmpty()) {
+ itemsPerFile[bootAwarePrefs] = bootAwareUpdates
}
+ }
+
+ return itemsPerFile.map { (prefs, items) ->
+ prefs.edit().also { editor ->
+ items.forEach { item -> editor.remove(item.sharedPrefKey) }
+ }
+ }
+ }
+
+ fun migrateStartupDataToDeviceProtectedStorage() {
+ if (!isBootAwareStartupDataEnabled) return
+
+ Log.d(
+ TAG,
+ "Migrating data to unencrypted shared preferences to enable preloading " +
+ "while the user is locked the next time the device reboots."
+ )
+
+ with(bootAwarePrefs.edit()) {
+ BOOT_AWARE_ITEMS.forEach { putValue(it, get(it)) }
+ putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
+ apply()
+ }
+ }
companion object {
+ private const val TAG = "LauncherPrefs"
+ @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
+
@JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
- @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "")
- @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false)
+ @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
+ @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
@JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
- @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "")
- @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1)
+ @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true)
+ @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true)
@JvmField
val DEVICE_TYPE =
- backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
- @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "")
+ backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
+ @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true)
@JvmField
val RESTORE_DEVICE =
- backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
+ backedUpItem(
+ RestoreDbTask.RESTORED_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ true
+ )
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
- @JvmField val GRID_NAME = ConstantItem("idp_grid_name", true, null, String::class.java)
+ @JvmField
+ val GRID_NAME =
+ ConstantItem(
+ "idp_grid_name",
+ isBackedUp = true,
+ defaultValue = null,
+ isBootAware = true,
+ type = String::class.java
+ )
@JvmField
val ALLOW_ROTATION =
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
}
+ @JvmField
+ val IS_STARTUP_DATA_MIGRATED =
+ ConstantItem(
+ "is_startup_data_boot_aware",
+ isBackedUp = false,
+ defaultValue = false,
+ isBootAware = true
+ )
@VisibleForTesting
@JvmStatic
- fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
- ConstantItem(sharedPrefKey, true, defaultValue)
+ fun <T> backedUpItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ isBootAware: Boolean = false
+ ): ConstantItem<T> =
+ ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware)
@JvmStatic
fun <T> backedUpItem(
sharedPrefKey: String,
type: Class<out T>,
+ isBootAware: Boolean = false,
defaultValueFromContext: (c: Context) -> T
- ): ContextualItem<T> = ContextualItem(sharedPrefKey, true, defaultValueFromContext, type)
+ ): ContextualItem<T> =
+ ContextualItem(
+ sharedPrefKey,
+ isBackedUp = true,
+ defaultValueFromContext,
+ isBootAware,
+ type
+ )
@VisibleForTesting
@JvmStatic
- fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
- ConstantItem(sharedPrefKey, false, defaultValue)
+ fun <T> nonRestorableItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ isBootAware: Boolean = false
+ ): ConstantItem<T> =
+ ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, isBootAware)
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
@JvmStatic
@@ -255,7 +352,7 @@
// Use application context for shared preferences, so we use single cached instance
return context.applicationContext.getSharedPreferences(
SHARED_PREFERENCES_KEY,
- Context.MODE_PRIVATE
+ MODE_PRIVATE
)
}
@@ -265,16 +362,23 @@
// Use application context for shared preferences, so we use a single cached instance
return context.applicationContext.getSharedPreferences(
DEVICE_PREFERENCES_KEY,
- Context.MODE_PRIVATE
+ MODE_PRIVATE
)
}
}
}
+// This is hard-coded to false for now until it is time to release this optimization. It is only
+// a var because the unit tests are setting this to true so they can run.
+@VisibleForTesting var isBootAwareStartupDataEnabled: Boolean = false
+
+private val BOOT_AWARE_ITEMS: MutableSet<ConstantItem<*>> = mutableSetOf()
+
abstract class Item {
abstract val sharedPrefKey: String
abstract val isBackedUp: Boolean
abstract val type: Class<*>
+ abstract val isBootAware: Boolean
val sharedPrefFile: String
get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
@@ -285,14 +389,22 @@
override val sharedPrefKey: String,
override val isBackedUp: Boolean,
val defaultValue: T,
+ override val isBootAware: Boolean,
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
override val type: Class<out T> = defaultValue!!::class.java
-) : Item()
+) : Item() {
+ init {
+ if (isBootAware && isBootAwareStartupDataEnabled) {
+ BOOT_AWARE_ITEMS.add(this)
+ }
+ }
+}
data class ContextualItem<T>(
override val sharedPrefKey: String,
override val isBackedUp: Boolean,
private val defaultSupplier: (c: Context) -> T,
+ override val isBootAware: Boolean,
override val type: Class<out T>
) : Item() {
private var default: T? = null
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index 0d59848..6a518a7 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -70,7 +70,7 @@
boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView,
int[] direction, boolean commit) {
// Add seam to x position
- if (cellX > mCountX / 2) {
+ if (cellX >= mCountX / 2) {
cellX++;
}
int finalCellX = cellX;
@@ -109,7 +109,7 @@
lp.canReorder = false;
mCountX++;
mShortcutsAndWidgets.addViewInLayout(mSeam, lp);
- mOccupied = createGridOccupancy();
+ mOccupied = createGridOccupancyWithSeam(mOccupied);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
}
@@ -139,14 +139,19 @@
return solution;
}
- GridOccupancy createGridOccupancy() {
- GridOccupancy grid = new GridOccupancy(mCountX, mCountY);
- for (int i = 0; i < mShortcutsAndWidgets.getChildCount(); i++) {
- View view = mShortcutsAndWidgets.getChildAt(i);
- CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
- int seamOffset = lp.getCellX() >= mCountX / 2 && lp.canReorder ? 1 : 0;
- grid.markCells(lp.getCellX() + seamOffset, lp.getCellY(), lp.cellHSpan, lp.cellVSpan,
- true);
+
+
+ GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
+ GridOccupancy grid = new GridOccupancy(getCountX(), getCountY());
+ for (int x = 0; x < getCountX(); x++) {
+ for (int y = 0; y < getCountY(); y++) {
+ int offset = x >= getCountX() / 2 ? 1 : 0;
+ if (x == getCountX() / 2) {
+ grid.cells[x][y] = true;
+ } else {
+ grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
+ }
+ }
}
return grid;
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c3d8a53..c7431ed 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -773,7 +773,7 @@
if (mScroller.isFinished() && pageScrollChanged) {
// TODO(b/246283207): Remove logging once root cause of flake detected.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS && !(this instanceof Workspace)) {
+ if (Utilities.isRunningInTestHarness() && !(this instanceof Workspace)) {
Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
+ "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
+ getNextPage() + ", getScrollForPage(getNextPage()): "
@@ -1713,7 +1713,7 @@
return false;
}
- if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
duration *= Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, 1);
}
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
index 047fb01..5afd95d 100644
--- a/src/com/android/launcher3/Reorderable.java
+++ b/src/com/android/launcher3/Reorderable.java
@@ -16,33 +16,19 @@
package com.android.launcher3;
-import android.graphics.PointF;
-import android.view.View;
+import com.android.launcher3.util.MultiTranslateDelegate;
public interface Reorderable {
/**
- * Set the offset related to reorder hint and bounce animations
+ * Returns the delegate to control translation
*/
- void setReorderBounceOffset(float x, float y);
-
- void getReorderBounceOffset(PointF offset);
-
- /**
- * Set the offset related to previewing the new reordered position
- */
- void setReorderPreviewOffset(float x, float y);
-
- void getReorderPreviewOffset(PointF offset);
+ MultiTranslateDelegate getTranslateDelegate();
/**
* Set the scale related to reorder hint and "bounce" animations
*/
void setReorderBounceScale(float scale);
- float getReorderBounceScale();
- /**
- * Get the com.android.view related to this object
- */
- View getView();
+ float getReorderBounceScale();
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 55b745b..b00199f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.CellLayout.FOLDER;
import static com.android.launcher3.CellLayout.HOTSEAT;
import static com.android.launcher3.CellLayout.WORKSPACE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING;
import android.app.WallpaperManager;
import android.content.Context;
@@ -208,7 +209,8 @@
float scaleY = appWidgetScale.y;
nahv.setScaleToFit(Math.min(scaleX, scaleY));
- nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+ nahv.getTranslateDelegate().setTranslation(INDEX_WIDGET_CENTERING,
+ -(lp.width - (lp.width * scaleX)) / 2.0f,
-(lp.height - (lp.height * scaleY)) / 2.0f);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7d01f7b..59327dc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -150,11 +150,14 @@
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
}
- public static boolean IS_RUNNING_IN_TEST_HARNESS =
- ActivityManager.isRunningInTestHarness();
+ private static boolean sIsRunningInTestHarness = ActivityManager.isRunningInTestHarness();
+
+ public static boolean isRunningInTestHarness() {
+ return sIsRunningInTestHarness;
+ }
public static void enableRunningInTestHarnessForTests() {
- IS_RUNNING_IN_TEST_HARNESS = true;
+ sIsRunningInTestHarness = true;
}
public static boolean isPropertyEnabled(String propertyName) {
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 0188a47..df22425 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -127,7 +127,7 @@
|| onboardingPrefs.getBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN)
|| AbstractFloatingView.getTopOpenView(launcher) != null
|| launcher.getSystemService(UserManager.class).isDemoUser()
- || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ || Utilities.isRunningInTestHarness()) {
return;
}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 770e931..24cc0ac 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -127,7 +127,7 @@
*/
private static AccessibilityManager getAccessibilityManagerForTest(Context context) {
// If not running in a test harness, don't participate in test exchanges.
- if (!Utilities.IS_RUNNING_IN_TEST_HARNESS) return null;
+ if (!Utilities.isRunningInTestHarness()) return null;
final AccessibilityManager accessibilityManager = getManager(context);
if (!accessibilityManager.isEnabled()) return null;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index af24334..98b61d1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -21,9 +21,14 @@
import android.content.Context;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Utilities;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+
/**
* Defines a set of flags used to control various launcher behaviors.
*
@@ -33,6 +38,11 @@
public static final String FLAGS_PREF_NAME = "featureFlags";
+ @VisibleForTesting
+ public static Predicate<BooleanFlag> sBooleanReader = f -> f.mCurrentValue;
+ @VisibleForTesting
+ public static ToIntFunction<IntFlag> sIntReader = f -> f.mCurrentValue;
+
private FeatureFlags() { }
public static boolean showFlagTogglerUi(Context context) {
@@ -247,10 +257,6 @@
270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", false,
"Enable option to launch search results using the new view container transitions");
- public static final BooleanFlag TWO_PREDICTED_ROWS_ALL_APPS_SEARCH = getReleaseFlag(270394225,
- "TWO_PREDICTED_ROWS_ALL_APPS_SEARCH", false,
- "Use 2 rows of app predictions in All Apps search zero-state");
-
public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", true,
"Enable option to show keyboard when going to all-apps");
@@ -354,10 +360,6 @@
"Enable the ability to generate monochromatic icons, if it is not provided by the app"
);
- public static final BooleanFlag ENABLE_DREAM_TRANSITION = getDebugFlag(270396364,
- "ENABLE_DREAM_TRANSITION", true,
- "Enable the launcher transition when the device enters a dream");
-
public static final BooleanFlag ENABLE_TASKBAR_EDU_TOOLTIP = getDebugFlag(270396268,
"ENABLE_TASKBAR_EDU_TOOLTIP", true,
"Enable the tooltip version of the Taskbar education flow.");
@@ -384,10 +386,6 @@
"ENABLE_KEYBOARD_QUICK_SWITCH", false,
"Enables keyboard quick switching");
- public static final BooleanFlag ENABLE_SMARTSPACE_DEFAULT_DATE_REMOVED = getDebugFlag(269761613,
- "ENABLE_SMARTSPACE_DEFAULT_DATE_REMOVED", false,
- "Enables remove smartspace default date");
-
public static class BooleanFlag {
private final boolean mCurrentValue;
@@ -397,7 +395,23 @@
}
public boolean get() {
- return mCurrentValue;
+ return sBooleanReader.test(this);
+ }
+ }
+
+ /**
+ * Class representing an integer flag
+ */
+ public static class IntFlag {
+
+ private final int mCurrentValue;
+
+ public IntFlag(int currentValue) {
+ mCurrentValue = currentValue;
+ }
+
+ public int get() {
+ return sIntReader.applyAsInt(this);
}
}
}
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 08e50dd..0e76bbb 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -43,7 +43,7 @@
private long getEnterSpringLoadHoverTime() {
// Some TAPL tests are flaky on Cuttlefish with a low waiting time
- return Utilities.IS_RUNNING_IN_TEST_HARNESS
+ return Utilities.isRunningInTestHarness()
? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
: ENTER_SPRING_LOAD_HOVER_TIME;
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ee1a060..86f4beb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -28,7 +28,6 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -77,6 +76,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
@@ -93,6 +93,7 @@
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
DraggableView, Reorderable {
+ private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
public FolderInfo mInfo;
@@ -133,14 +134,6 @@
private Rect mTouchArea = new Rect();
- private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
- private float mTranslationXForTaskbarAlignmentAnimation = 0f;
- private float mTranslationYForTaskbarAlignmentAnimation = 0f;
- private float mTranslationXForTaskbarRevealAnimation = 0f;
- private float mTranslationYForTaskbarRevealAnimation = 0f;
-
- private final PointF mTranslationForReorderBounce = new PointF(0, 0);
- private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
@@ -770,120 +763,23 @@
mPreviewItemManager.onFolderClose(currentPage);
}
- private void updateTranslation() {
- super.setTranslationX(mTranslationForReorderBounce.x
- + mTranslationForReorderPreview.x
- + mTranslationForMoveFromCenterAnimation.x
- + mTranslationXForTaskbarAlignmentAnimation
- + mTranslationXForTaskbarRevealAnimation);
- super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
- + mTranslationForMoveFromCenterAnimation.y
- + mTranslationYForTaskbarAlignmentAnimation
- + mTranslationYForTaskbarRevealAnimation);
- }
-
- public void setReorderBounceOffset(float x, float y) {
- mTranslationForReorderBounce.set(x, y);
- updateTranslation();
- }
-
- public void getReorderBounceOffset(PointF offset) {
- offset.set(mTranslationForReorderBounce);
- }
-
- /**
- * Sets translationX value for taskbar to launcher alignment animation
- */
- public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
- mTranslationXForTaskbarAlignmentAnimation = translationX;
- updateTranslation();
- }
-
- /**
- * Returns translation values for taskbar to launcher alignment animation
- */
- public float getTranslationXForTaskbarAlignmentAnimation() {
- return mTranslationXForTaskbarAlignmentAnimation;
- }
-
- /**
- * Sets translationY value for taskbar to launcher alignment animation
- */
- public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
- mTranslationYForTaskbarAlignmentAnimation = translationY;
- updateTranslation();
- }
-
- /**
- * Returns translation values for taskbar to launcher alignment animation
- */
- public float getTranslationYForTaskbarAlignmentAnimation() {
- return mTranslationYForTaskbarAlignmentAnimation;
- }
-
- /**
- * Sets translationX value for taskbar reveal animation
- */
- public void setTranslationXForTaskbarRevealAnimation(float translationX) {
- mTranslationXForTaskbarRevealAnimation = translationX;
- updateTranslation();
- }
-
- /**
- * Returns translation values for taskbar reveal animation
- */
- public float getTranslationXForTaskbarRevealAnimation() {
- return mTranslationXForTaskbarRevealAnimation;
- }
-
- /**
- * Sets translationY value for taskbar reveal animation
- */
- public void setTranslationYForTaskbarRevealAnimation(float translationY) {
- mTranslationYForTaskbarRevealAnimation = translationY;
- updateTranslation();
- }
-
- /**
- * Returns translationY values for taskbar reveal animation
- */
- public float getTranslationYForTaskbarRevealAnimation() {
- return mTranslationYForTaskbarRevealAnimation;
- }
-
- /**
- * Sets translation values for move from center animation
- */
- public void setTranslationForMoveFromCenterAnimation(float x, float y) {
- mTranslationForMoveFromCenterAnimation.set(x, y);
- updateTranslation();
+ @Override
+ public MultiTranslateDelegate getTranslateDelegate() {
+ return mTranslateDelegate;
}
@Override
- public void setReorderPreviewOffset(float x, float y) {
- mTranslationForReorderPreview.set(x, y);
- updateTranslation();
- }
-
- @Override
- public void getReorderPreviewOffset(PointF offset) {
- offset.set(mTranslationForReorderPreview);
- }
-
public void setReorderBounceScale(float scale) {
mScaleForReorderBounce = scale;
super.setScaleX(scale);
super.setScaleY(scale);
}
+ @Override
public float getReorderBounceScale() {
return mScaleForReorderBounce;
}
- public View getView() {
- return this;
- }
-
@Override
public int getViewType() {
return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 8efd12a..5a50569 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -92,7 +92,7 @@
private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1;
private static final int ALPHA_DURATION_MILLIS = 3000;
- private static final int OVERLAY_ALPHA_RANGE = 127;
+ private static final int OVERLAY_ALPHA_RANGE = 191;
private static final long WAVE_MOTION_DELAY_FACTOR_MILLIS = 100;
private static final WeakHashMap<Integer, PorterDuffColorFilter> COLOR_FILTER_MAP =
new WeakHashMap<>();
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 5444d92..17b472a 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -61,7 +61,7 @@
@Override
public Bundle call(String method, String arg, Bundle extras) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
handler.init(getContext());
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index c151606..f95548d 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -39,13 +39,13 @@
}
public static void recordEvent(String sequence, String event) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
recordEventSlow(sequence, event);
}
}
public static void recordEvent(String sequence, String message, Object parameter) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
recordEventSlow(sequence, message + ": " + parameter);
}
}
@@ -58,14 +58,14 @@
}
public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
recordEventSlow(sequence, message + ": " + event);
registerEventNotFromTest(event);
}
}
public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
+ if (Utilities.isRunningInTestHarness() && event.getAction() != MotionEvent.ACTION_MOVE) {
recordEventSlow(sequence, message + ": " + event);
registerEventNotFromTest(event);
}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 9ed6700..3d455d8 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -139,11 +139,11 @@
// TODO(b/258604917): When running in test harness, use !sTransientTaskbarStatusForTests
// once tests are updated to expect new persistent behavior such as not allowing long press
// to stash.
- if (!Utilities.IS_RUNNING_IN_TEST_HARNESS && FORCE_PERSISTENT_TASKBAR.get()) {
+ if (!Utilities.isRunningInTestHarness() && FORCE_PERSISTENT_TASKBAR.get()) {
return false;
}
return getInfo().navigationMode == NavigationMode.NO_BUTTON
- && (Utilities.IS_RUNNING_IN_TEST_HARNESS
+ && (Utilities.isRunningInTestHarness()
? sTransientTaskbarStatusForTests
: ENABLE_TRANSIENT_TASKBAR.get());
}
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 7b49583..f5e13d2 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -50,7 +50,9 @@
}
companion object {
- @VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+ @VisibleForTesting
+ @JvmField
+ val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
@JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
}
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
new file mode 100644
index 0000000..0b5bc8d
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 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 static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+
+import android.view.View;
+
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
+
+/**
+ * A utility class to split translation components for various workspace items
+ */
+public class MultiTranslateDelegate {
+
+ // offset related to reorder hint and bounce animations
+ public static final int INDEX_REORDER_BOUNCE_OFFSET = 0;
+ // offset related to previewing the new reordered position
+ public static final int INDEX_REORDER_PREVIEW_OFFSET = 1;
+ public static final int INDEX_MOVE_FROM_CENTER_ANIM = 2;
+
+ // Specific for icons and folders
+ public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3;
+ public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
+
+ // Specific for widgets
+ public static final int INDEX_WIDGET_CENTERING = 3;
+
+ public static final int COUNT = 5;
+
+ private final MultiPropertyFactory<View> mTranslationX;
+ private final MultiPropertyFactory<View> mTranslationY;
+
+ public MultiTranslateDelegate(View target) {
+ this(target, COUNT, COUNT);
+ }
+
+ public MultiTranslateDelegate(View target, int countX, int countY) {
+ mTranslationX = new MultiPropertyFactory<>(target, VIEW_TRANSLATE_X, countX, Float::sum);
+ mTranslationY = new MultiPropertyFactory<>(target, VIEW_TRANSLATE_Y, countY, Float::sum);
+ }
+
+ /**
+ * Helper method to set both translations, x and y at a given index
+ */
+ public void setTranslation(int index, float x, float y) {
+ getTranslationX(index).setValue(x);
+ getTranslationY(index).setValue(y);
+ }
+
+ /**
+ * Returns the translation x for the provided index
+ */
+ public MultiProperty getTranslationX(int index) {
+ return mTranslationX.get(index);
+ }
+
+ /**
+ * Returns the translation y for the provided index
+ */
+ public MultiProperty getTranslationY(int index) {
+ return mTranslationY.get(index);
+ }
+}
diff --git a/src/com/android/launcher3/views/IconButtonView.java b/src/com/android/launcher3/views/IconButtonView.java
index 9969eeb..71f6756 100644
--- a/src/com/android/launcher3/views/IconButtonView.java
+++ b/src/com/android/launcher3/views/IconButtonView.java
@@ -37,6 +37,7 @@
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.MultiTranslateDelegate;
/**
* Button in Taskbar that shows a tinted background and foreground.
@@ -45,6 +46,12 @@
private static final int[] ATTRS = {android.R.attr.icon};
+ private static final int INDEX_TASKBAR_ALL_APPS_ICON = MultiTranslateDelegate.COUNT;
+ private static final int MY_COUNT = MultiTranslateDelegate.COUNT + 1;
+
+ private final MultiTranslateDelegate mTranslateDelegate =
+ new MultiTranslateDelegate(this, MY_COUNT, MultiTranslateDelegate.COUNT);
+
public IconButtonView(Context context) {
this(context, null);
}
@@ -88,6 +95,18 @@
}
}
+ @Override
+ public MultiTranslateDelegate getTranslateDelegate() {
+ return mTranslateDelegate;
+ }
+
+ /**
+ * Sets translationX for taskbar all apps icon
+ */
+ public void setTranslationXForTaskbarAllAppsIcon(float translationX) {
+ getTranslateDelegate().getTranslationX(INDEX_TASKBAR_ALL_APPS_ICON).setValue(translationX);
+ }
+
private static class IconDrawable extends FastBitmapDrawable {
private final Drawable mFg;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 2ac1e94..68ece03 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -341,7 +341,7 @@
/** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
protected boolean hasSeenEducationTip() {
return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
- || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+ || Utilities.isRunningInTestHarness();
}
@Override
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 241c937..3389fb1 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -19,7 +19,6 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;
@@ -29,6 +28,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
@@ -39,20 +39,13 @@
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
implements DraggableView, Reorderable {
+ private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
+
/**
* The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
*/
private float mScaleToFit = 1f;
- /**
- * The translation values to center the widget within its cellspans.
- */
- private final PointF mTranslationForCentering = new PointF(0, 0);
-
- private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
-
- private final PointF mTranslationForReorderBounce = new PointF(0, 0);
- private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
private final Rect mTempRect = new Rect();
@@ -163,57 +156,23 @@
setSelected(childIsFocused);
}
- public View getView() {
- return this;
- }
-
- private void updateTranslation() {
- super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
- + mTranslationForCentering.x + mTranslationForMoveFromCenterAnimation.x);
- super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
- + mTranslationForCentering.y + mTranslationForMoveFromCenterAnimation.y);
- }
-
- public void setTranslationForCentering(float x, float y) {
- mTranslationForCentering.set(x, y);
- updateTranslation();
- }
-
- public void setTranslationForMoveFromCenterAnimation(float x, float y) {
- mTranslationForMoveFromCenterAnimation.set(x, y);
- updateTranslation();
- }
-
- public void setReorderBounceOffset(float x, float y) {
- mTranslationForReorderBounce.set(x, y);
- updateTranslation();
- }
-
- public void getReorderBounceOffset(PointF offset) {
- offset.set(mTranslationForReorderBounce);
- }
-
- @Override
- public void setReorderPreviewOffset(float x, float y) {
- mTranslationForReorderPreview.set(x, y);
- updateTranslation();
- }
-
- @Override
- public void getReorderPreviewOffset(PointF offset) {
- offset.set(mTranslationForReorderPreview);
- }
-
private void updateScale() {
super.setScaleX(mScaleToFit * mScaleForReorderBounce);
super.setScaleY(mScaleToFit * mScaleForReorderBounce);
}
+ @Override
+ public MultiTranslateDelegate getTranslateDelegate() {
+ return mTranslateDelegate;
+ }
+
+ @Override
public void setReorderBounceScale(float scale) {
mScaleForReorderBounce = scale;
updateScale();
}
+ @Override
public float getReorderBounceScale() {
return mScaleForReorderBounce;
}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index ce47d70..80bc1a7 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
import static com.android.launcher3.Utilities.ATLEAST_S;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING;
import android.content.Context;
import android.graphics.Bitmap;
@@ -314,7 +315,7 @@
setScaleToFit(1.0f);
}
// When the drag start, translations need to be set to zero to center the view
- setTranslationForCentering(0f, 0f);
+ getTranslateDelegate().setTranslation(INDEX_WIDGET_CENTERING, 0f, 0f);
}
}
@@ -464,7 +465,8 @@
} else {
mAppWidgetHostViewPreview.setScaleToFit(mAppWidgetHostViewScale);
}
- mAppWidgetHostViewPreview.setTranslationForCentering(
+ mAppWidgetHostViewPreview.getTranslateDelegate().setTranslation(
+ INDEX_WIDGET_CENTERING,
-(params.width - (params.width * mPreviewContainerScale)) / 2.0f,
-(params.height - (params.height * mPreviewContainerScale)) / 2.0f);
mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index f8068aa..f4d6749 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -201,6 +201,7 @@
private TextView mHeaderTitle;
private FrameLayout mRightPane;
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
+ private DeviceProfile mDeviceProfile;
private final boolean mIsTwoPane;
private int mOrientation;
@@ -210,8 +211,10 @@
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- DeviceProfile dp = Launcher.getLauncher(context).getDeviceProfile();
- mIsTwoPane = dp.isTablet && dp.isLandscape && LARGE_SCREEN_WIDGET_PICKER.get();
+ mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ mIsTwoPane = mDeviceProfile.isTablet
+ && mDeviceProfile.isLandscape
+ && LARGE_SCREEN_WIDGET_PICKER.get();
mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
@@ -874,7 +877,9 @@
}
// Checks the orientation of the screen
- if (LARGE_SCREEN_WIDGET_PICKER.get() && mOrientation != newConfig.orientation) {
+ if (LARGE_SCREEN_WIDGET_PICKER.get()
+ && mOrientation != newConfig.orientation
+ && mDeviceProfile.isTablet) {
mOrientation = newConfig.orientation;
handleClose(false);
show(Launcher.getLauncher(getContext()), false);
@@ -934,7 +939,7 @@
protected boolean hasSeenEducationDialog() {
return mActivityContext.getSharedPrefs()
.getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false)
- || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+ || Utilities.isRunningInTestHarness();
}
private void setUpEducationViewsIfNeeded() {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 599969b..4463adc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -17,6 +17,7 @@
package com.android.launcher3.uioverrides.flags;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.IntFlag;
import java.io.PrintWriter;
@@ -43,6 +44,14 @@
}
/**
+ * Creates a new integer flag. Integer flags are always release flags
+ */
+ public static IntFlag getIntFlag(
+ int bugId, String key, int defaultValueInCode, String description) {
+ return new IntFlag(defaultValueInCode);
+ }
+
+ /**
* Dumps the current flags state to the print writer
*/
public static void dump(PrintWriter pw) { }
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
index e8372b1..41ef3de 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -1,9 +1,27 @@
+/*
+ * Copyright (C) 2023 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
+import android.content.Context
+import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.LauncherPrefs.Companion.BOOT_AWARE_PREFS_KEY
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -13,19 +31,22 @@
private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
-private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, Boolean::class.java)
+private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, false, Boolean::class.java)
private const val TEST_DEFAULT_VALUE = "default"
private const val TEST_PREF_KEY = "test_pref_key"
+private const val WAIT_TIME_IN_SECONDS = 3L
+
@SmallTest
@RunWith(AndroidJUnit4::class)
class LauncherPrefsTest {
- private val launcherPrefs by lazy {
- LauncherPrefs.get(InstrumentationRegistry.getInstrumentation().targetContext).apply {
- remove(TEST_BOOLEAN_ITEM, TEST_STRING_ITEM, TEST_INT_ITEM)
- }
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext }
+ private val launcherPrefs by lazy { LauncherPrefs.get(context) }
+
+ init {
+ isBootAwareStartupDataEnabled = true
}
@Test
@@ -52,7 +73,7 @@
addListener(listener, TEST_STRING_ITEM)
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"))
- assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
+ assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue()
remove(TEST_STRING_ITEM)
}
}
@@ -68,7 +89,7 @@
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "hello."))
// latch will be still be 1 (and await will return false) if the listener was not called
- assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse()
+ assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isFalse()
remove(TEST_STRING_ITEM)
}
}
@@ -85,7 +106,7 @@
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"),
TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue)
)
- assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
+ assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue()
removeListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
latch = CountDownLatch(1)
@@ -96,7 +117,7 @@
)
remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
- assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse()
+ assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isFalse()
}
}
@@ -166,4 +187,133 @@
val backedUpItem = LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE)
assertThat(backedUpItem.sharedPrefFile).isEqualTo(LauncherFiles.SHARED_PREFERENCES_KEY)
}
+
+ @Test
+ fun put_bootAwareItem_updatesDeviceProtectedStorage() {
+ val bootAwareItem =
+ LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+
+ val bootAwarePrefs: SharedPreferences =
+ context
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
+ bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
+
+ launcherPrefs.putSync(bootAwareItem.to(bootAwareItem.defaultValue))
+ assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
+
+ launcherPrefs.removeSync(bootAwareItem)
+ }
+
+ @Test
+ fun put_bootAwareItem_updatesEncryptedStorage() {
+ val bootAwareItem =
+ LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+
+ val encryptedPrefs: SharedPreferences =
+ context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
+ encryptedPrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
+
+ launcherPrefs.putSync(bootAwareItem.to(TEST_STRING_ITEM.defaultValue))
+ assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
+
+ launcherPrefs.removeSync(bootAwareItem)
+ }
+
+ @Test
+ fun remove_bootAwareItem_removesFromDeviceProtectedStorage() {
+ val bootAwareItem =
+ LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+
+ val bootAwarePrefs: SharedPreferences =
+ context
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
+
+ bootAwarePrefs
+ .edit()
+ .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
+ .commit()
+
+ launcherPrefs.removeSync(bootAwareItem)
+ assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse()
+ }
+
+ @Test
+ fun remove_bootAwareItem_removesFromEncryptedStorage() {
+ val bootAwareItem =
+ LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+
+ val encryptedPrefs: SharedPreferences =
+ context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
+
+ encryptedPrefs
+ .edit()
+ .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
+ .commit()
+
+ launcherPrefs.removeSync(bootAwareItem)
+ assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isFalse()
+ }
+
+ @Test
+ fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() {
+ val bootAwareItem =
+ LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+ launcherPrefs.removeSync(bootAwareItem)
+
+ val bootAwarePrefs: SharedPreferences =
+ context
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
+
+ if (bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)) {
+ bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
+ }
+
+ val encryptedPrefs: SharedPreferences =
+ context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
+
+ encryptedPrefs
+ .edit()
+ .putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
+ .commit()
+
+ launcherPrefs.migrateStartupDataToDeviceProtectedStorage()
+ assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
+
+ launcherPrefs.removeSync(bootAwareItem)
+ }
+
+ @Test
+ fun migrate_onlyEncryptedItemsToDeviceProtectedStorage_doesNotHappen() {
+ val onlyEncryptedItem =
+ LauncherPrefs.backedUpItem(
+ TEST_PREF_KEY + "_",
+ TEST_DEFAULT_VALUE + "_",
+ isBootAware = false
+ )
+
+ val bootAwarePrefs: SharedPreferences =
+ context
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
+
+ if (bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)) {
+ bootAwarePrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit()
+ }
+
+ val encryptedPrefs: SharedPreferences =
+ context.getSharedPreferences(onlyEncryptedItem.sharedPrefFile, Context.MODE_PRIVATE)
+
+ encryptedPrefs
+ .edit()
+ .putString(onlyEncryptedItem.sharedPrefKey, onlyEncryptedItem.defaultValue)
+ .commit()
+
+ launcherPrefs.migrateStartupDataToDeviceProtectedStorage()
+ assertThat(bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)).isFalse()
+
+ encryptedPrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit()
+ }
}
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index fa4c519..545b645 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -510,7 +510,7 @@
UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
- SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
+ SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
index d7c6c4f..433fd31 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -27,12 +27,18 @@
import androidx.test.uiautomator.UiDevice;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.IntFlag;
+
import org.junit.Assert;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
public class TestUtil {
public static final String DUMMY_PACKAGE = "com.example.android.aardwolf";
@@ -68,6 +74,36 @@
}
}
+ /**
+ * Utility class to override a boolean flag during test. Note that the returned SafeCloseable
+ * must be closed to restore the original state
+ */
+ public static SafeCloseable overrideFlag(BooleanFlag flag, boolean value) {
+ Predicate<BooleanFlag> originalProxy = FeatureFlags.sBooleanReader;
+ Predicate<BooleanFlag> testProxy = f -> f == flag ? value : originalProxy.test(f);
+ FeatureFlags.sBooleanReader = testProxy;
+ return () -> {
+ if (FeatureFlags.sBooleanReader == testProxy) {
+ FeatureFlags.sBooleanReader = originalProxy;
+ }
+ };
+ }
+
+ /**
+ * Utility class to override a int flag during test. Note that the returned SafeCloseable
+ * must be closed to restore the original state
+ */
+ public static SafeCloseable overrideFlag(IntFlag flag, int value) {
+ ToIntFunction<IntFlag> originalProxy = FeatureFlags.sIntReader;
+ ToIntFunction<IntFlag> testProxy = f -> f == flag ? value : originalProxy.applyAsInt(f);
+ FeatureFlags.sIntReader = testProxy;
+ return () -> {
+ if (FeatureFlags.sIntReader == testProxy) {
+ FeatureFlags.sIntReader = originalProxy;
+ }
+ };
+ }
+
public static void uninstallDummyApp() throws IOException {
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"pm uninstall " + DUMMY_PACKAGE);