Merge "Fix two dragging related bugs in Launcher home" into sc-v2-dev
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d67b23b..31c0f5f 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -30,6 +30,7 @@
determines how many thumbnails will be fetched in the background. -->
<integer name="recentsThumbnailCacheSize">3</integer>
<integer name="recentsIconCacheSize">12</integer>
+ <integer name="recentsScrollHapticMinGapMillis">20</integer>
<!-- Assistant Gesture -->
<integer name="assistant_gesture_min_time_threshold">200</integer>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 43f015c..978bd47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -77,6 +77,7 @@
@Override
public void onTransitionFinished() {
mMoveFromCenterAnimator.onTransitionFinished();
+ mMoveFromCenterAnimator.clearRegisteredViews();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 283743d..ef6f53e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,7 +24,7 @@
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.ObjectAnimator;
@@ -38,11 +38,11 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 40c3e02..ff3c517 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,7 @@
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -67,14 +67,15 @@
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -398,6 +399,14 @@
nonOverviewAnim.setFloatValues(startProgress, endProgress);
mNonOverviewAnim.dispatchOnStart();
}
+ if (targetState == QUICK_SWITCH) {
+ // Navigating to quick switch, add scroll feedback since the first time is not
+ // considered a scroll by the RecentsView.
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE,
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
+ RecentsView.SCROLL_VIBRATION_FALLBACK);
+ }
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0603ba5..010f463 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -22,6 +22,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -41,6 +42,7 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -55,6 +57,12 @@
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
+ public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
+ public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
+ public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
protected final T mActivity;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
@@ -334,10 +342,10 @@
fling = false;
}
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
- boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
@@ -357,6 +365,10 @@
mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
mEndDisplacement, animationDuration);
+ if (goingUp && goingToEnd) {
+ VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+ TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
+ }
}
private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 830737d..1e841da 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -46,6 +45,7 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -92,7 +92,6 @@
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
@@ -112,6 +111,7 @@
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -945,6 +945,7 @@
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify swipe-to-home (recents animation) is finished
SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1805,8 +1806,13 @@
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
+ boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds().length > 0;
nextTask.launchTask(success -> {
resultCallback.accept(success);
+ if (isOldTaskSplit) {
+ SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(taskId);
+ }
if (success) {
if (hasTaskPreviouslyAppeared) {
onRestartPreviouslyAppearedTask();
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 8fb851c..b232464 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -32,6 +32,7 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -157,6 +158,7 @@
}
if (cmd.type == TYPE_HOME) {
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
return true;
}
} else {
@@ -175,6 +177,7 @@
return launchTask(recents, getNextTask(recents), cmd);
case TYPE_HOME:
recents.startHome();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
return true;
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 7d2d413..aea2d4c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -533,10 +533,17 @@
}
}
- public void exitSplitScreen() {
+ /**
+ * To be called whenever the user exits out of split screen apps (either by launching another
+ * app or by swiping home)
+ * @param topTaskId The taskId of the new app that was launched. System will then move this task
+ * to the front of what the user sees while removing all other split stages.
+ * If swiping to home (or there is no task to put at the top), can pass in -1.
+ */
+ public void exitSplitScreen(int topTaskId) {
if (mSplitScreen != null) {
try {
- mSplitScreen.exitSplitScreen();
+ mSplitScreen.exitSplitScreen(topTaskId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call exitSplitScreen");
}
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 7465db3..b2b2f59 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -43,7 +43,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.VibratorWrapper;
/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
public class EdgeBackGesturePanel extends View {
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index fc1dd6f..851cccf 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,7 +16,6 @@
package com.android.quickstep.interaction;
import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -26,6 +25,7 @@
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -47,11 +47,11 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
/** Utility class to handle Home and Assistant gestures. */
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
index 0f4ed01..fa4cddc 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -44,8 +44,6 @@
if (frozen) {
mPersistentGroupedIds = getRunningSplitTaskIds();
} else {
- // TODO(b/198310766) Need to also explicitly exit split screen if
- // we're not currently viewing split screened apps
mPersistentGroupedIds = EMPTY_ARRAY;
}
}
@@ -53,8 +51,11 @@
/**
* Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
- * whenever task list unfreezes.
- * When not null, this indicates that we need to load a GroupedTaskView as the most recent
+ * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
+ * home - in that case the task list does not unfreeze immediately after the gesture, so it's
+ * done via {@link #notifySwipingToHome()}.
+ *
+ * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
* page, so user can quickswitch back to a grouped task.
*/
private int[] mPersistentGroupedIds;
@@ -140,6 +141,18 @@
}
}
+ /** Notifies SystemUi to remove any split screen state */
+ public void notifySwipingToHome() {
+ boolean hasSplitTasks = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getPersistentSplitIds().length > 0;
+ if (!hasSplitTasks) {
+ return;
+ }
+
+ SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(-1);
+ mPersistentGroupedIds = EMPTY_ARRAY;
+ }
+
private void resetTaskId(StagedSplitTaskPosition taskPosition) {
taskPosition.taskId = -1;
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index a089e73..849a7bc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -296,9 +296,8 @@
}
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
- mCurrentFullscreenParams.setProgress(
- fullScreenProgress, recentsViewScale.value, /*taskViewScale=*/1f, mTaskRect.width(),
- mDp, mPositionHelper);
+ mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
+ /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
// Apply thumbnail matrix
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
similarity index 65%
rename from src/com/android/launcher3/util/VibratorWrapper.java
rename to quickstep/src/com/android/quickstep/util/VibratorWrapper.java
index b0defd4..211bd08 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.quickstep.util;
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
@@ -21,15 +21,20 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.media.AudioAttributes;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
*/
@@ -39,8 +44,15 @@
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
+ public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
+ public static final VibrationEffect EFFECT_TEXTURE_TICK =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
/**
* Haptic when entering overview.
@@ -78,7 +90,27 @@
/** Vibrates with the given effect if haptic feedback is available and enabled. */
public void vibrate(VibrationEffect vibrationEffect) {
if (mHasVibrator && mIsHapticFeedbackEnabled) {
- UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+ UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS));
+ }
+ }
+
+ /**
+ * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
+ * vibrates if haptic feedback is available and enabled.
+ */
+ @SuppressLint("NewApi")
+ public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
+ if (mHasVibrator && mIsHapticFeedbackEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (Utilities.ATLEAST_R && primitiveId >= 0
+ && mVibrator.areAllPrimitivesSupported(primitiveId)) {
+ mVibrator.vibrate(VibrationEffect.startComposition()
+ .addPrimitive(primitiveId, primitiveScale)
+ .compose(), VIBRATION_ATTRS);
+ } else {
+ mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 24d6044..dd470e8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,6 +35,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -78,7 +79,9 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.VibrationEffect;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -159,6 +162,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -243,6 +247,12 @@
}
};
+ public static final int SCROLL_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+ public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
+ public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
/**
* Can be used to tint the color of the RecentsView to simulate a scrim that can views
* excluded from. Really should be a proper scrim.
@@ -396,6 +406,7 @@
protected final ACTIVITY_TYPE mActivity;
private final float mFastFlingVelocity;
+ private final int mScrollHapticMinGapMillis;
private final RecentsModel mModel;
private final int mGridSideMargin;
private final ClearAllButton mClearAllButton;
@@ -442,6 +453,7 @@
private ObjectAnimator mTintingAnimator;
private int mOverScrollShift = 0;
+ private long mScrollLastHapticTimestamp;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -628,6 +640,8 @@
final int rotation = mActivity.getDisplay().getRotation();
mOrientationState.setRecentsRotation(rotation);
+ mScrollHapticMinGapMillis = getResources()
+ .getInteger(R.integer.recentsScrollHapticMinGapMillis);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mModel = RecentsModel.INSTANCE.get(context);
@@ -1228,6 +1242,25 @@
}
@Override
+ protected void onEdgeAbsorbingScroll() {
+ vibrateForScroll();
+ }
+
+ @Override
+ protected void onScrollOverPageChanged() {
+ vibrateForScroll();
+ }
+
+ private void vibrateForScroll() {
+ long now = SystemClock.uptimeMillis();
+ if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
+ mScrollLastHapticTimestamp = now;
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE,
+ SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK);
+ }
+ }
+
+ @Override
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
// Enables swiping to the left or right only if the task overlay is not modal.
if (!isModal()) {
@@ -2001,9 +2034,6 @@
/**
* Called only when a swipe-up gesture from an app has completed. Only called after
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
- *
- * TODO(b/198310766) Need to also explicitly exit split screen if
- * the swipe up was to home
*/
public void onSwipeUpAnimationSuccess() {
animateUpTaskIconScale();
@@ -2592,10 +2622,13 @@
runActionOnRemoteHandles(remoteTargetHandle -> {
TransformParams params = remoteTargetHandle.getTransformParams();
anim.setFloat(params, TransformParams.TARGET_ALPHA, 0,
- clampToProgress(ACCEL, 0, 0.5f));
+ clampToProgress(FINAL_FRAME, 0, 0.5f));
});
}
- anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
+ boolean isTaskInBottomGridRow = showAsGrid() && !mTopRowIdSet.contains(
+ taskView.getTaskViewId()) && taskView.getTaskViewId() != mFocusedTaskViewId;
+ anim.setFloat(taskView, VIEW_ALPHA, 0,
+ clampToProgress(isTaskInBottomGridRow ? ACCEL : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 08b614a..7c558c2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -99,6 +99,7 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.util.TransformParams;
@@ -693,8 +694,13 @@
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);
+ boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getPersistentSplitIds().length > 0;
if (ActivityManagerWrapper.getInstance()
.startActivityFromRecents(mTask.key, opts.options)) {
+ if (isOldTaskSplit) {
+ SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id);
+ }
RecentsView recentsView = getRecentsView();
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
recentsView.onTaskLaunchedInLiveTileMode();
@@ -1071,7 +1077,6 @@
private void setNonGridScale(float nonGridScale) {
mNonGridScale = nonGridScale;
- updateCornerRadius();
applyScale();
}
@@ -1102,6 +1107,7 @@
scale *= mDismissScale;
setScaleX(scale);
setScaleY(scale);
+ updateSnapshotRadius();
}
/**
@@ -1417,29 +1423,25 @@
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
- updateCornerRadius();
+ updateSnapshotRadius();
- mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
mOutlineProvider.updateParams(
mCurrentFullscreenParams,
mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
invalidateOutline();
}
- private void updateCornerRadius() {
+ private void updateSnapshotRadius() {
updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
+ mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
}
void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
if (getRecentsView() == null) {
return;
}
- mCurrentFullscreenParams.setProgress(
- mFullscreenProgress,
- getRecentsView().getScaleX(),
- mNonGridScale,
- getWidth(), mActivity.getDeviceProfile(),
- previewPositionHelper);
+ mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
+ getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
}
/**
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
deleted file mode 100644
index 6473d00..0000000
--- a/robolectric_tests/Android.bp
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2021 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.
-
-//
-// Launcher Robolectric test target.
-//
-// "robolectric_android-all-stub", not needed, we write our own stubs
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "packages_apps_Launcher3_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["packages_apps_Launcher3_license"],
-}
-
-filegroup {
- name: "launcher3-robolectric-resources",
- path: "resources",
- srcs: ["resources/*"],
-}
-
-filegroup {
- name: "launcher3-robolectric-src",
- srcs: ["src/**/*.java"],
-}
-
-android_robolectric_test {
- name: "LauncherRoboTests",
- srcs: [
- ":launcher3-robolectric-src",
- ],
- java_resources: [":launcher3-robolectric-resources"],
- static_libs: [
- "truth-prebuilt",
- "androidx.test.espresso.contrib",
- "androidx.test.espresso.core",
- "androidx.test.espresso.intents",
- "androidx.test.ext.junit",
- "androidx.test.runner",
- "androidx.test.rules",
- "mockito-robolectric-prebuilt",
- "SystemUISharedLib",
- ],
- robolectric_prebuilt_version: "4.5.1",
- instrumentation_for: "Launcher3",
-
- test_options: {
- timeout: 36000,
- },
-}
diff --git a/robolectric_tests/res/values/overlayable_icons_test.xml b/robolectric_tests/res/values/overlayable_icons_test.xml
deleted file mode 100644
index 5144e52..0000000
--- a/robolectric_tests/res/values/overlayable_icons_test.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
- Copyright (C) 2019 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.
--->
-<resources>
- <!-- overlayable_icons references all of the drawables in this package
- that are being overlayed by resource overlays. If you remove/rename
- any of these resources, you must also change the resource overlay icons.-->
- <array name="overlayable_icons">
- <item>@drawable/ic_corp</item>
- <item>@drawable/ic_drag_handle</item>
- <item>@drawable/ic_hourglass_top</item>
- <item>@drawable/ic_info_no_shadow</item>
- <item>@drawable/ic_install_no_shadow</item>
- <item>@drawable/ic_palette</item>
- <item>@drawable/ic_pin</item>
- <item>@drawable/ic_remove_no_shadow</item>
- <item>@drawable/ic_setting</item>
- <item>@drawable/ic_smartspace_preferences</item>
- <item>@drawable/ic_split_screen</item>
- <item>@drawable/ic_uninstall_no_shadow</item>
- <item>@drawable/ic_warning</item>
- <item>@drawable/ic_widget</item>
- </array>
-</resources>
diff --git a/robolectric_tests/resources/robolectric.properties b/robolectric_tests/resources/robolectric.properties
deleted file mode 100644
index abb6968..0000000
--- a/robolectric_tests/resources/robolectric.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-sdk=30
-
-shadows= \
- com.android.launcher3.shadows.LShadowAppPredictionManager \
- com.android.launcher3.shadows.LShadowAppWidgetManager \
- com.android.launcher3.shadows.LShadowBackupManager \
- com.android.launcher3.shadows.LShadowDisplay \
- com.android.launcher3.shadows.LShadowLauncherApps \
- com.android.launcher3.shadows.ShadowDeviceFlag \
- com.android.launcher3.shadows.ShadowLooperExecutor \
- com.android.launcher3.shadows.ShadowMainThreadInitializedObject \
- com.android.launcher3.shadows.ShadowOverrides \
- com.android.launcher3.shadows.ShadowSurfaceTransactionApplier \
-
-application=com.android.launcher3.util.LauncherTestApplication
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
deleted file mode 100644
index ae051f7..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import static org.mockito.Mockito.mock;
-
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-/**
- * Shadow for {@link AppPredictionManager} which create mock predictors
- */
-@Implements(value = AppPredictionManager.class)
-public class LShadowAppPredictionManager {
-
- @Implementation
- public AppPredictor createAppPredictionSession(AppPredictionContext predictionContext) {
- return mock(AppPredictor.class);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
deleted file mode 100644
index 696ffd0..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 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.shadows;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.os.Process;
-import android.os.UserHandle;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowAppWidgetManager;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ShadowAppWidgetManager} with missing shadow methods
- */
-@Implements(value = AppWidgetManager.class)
-public class LShadowAppWidgetManager extends ShadowAppWidgetManager {
-
- @Override
- protected List<AppWidgetProviderInfo> getInstalledProviders() {
- return getInstalledProvidersForProfile(null);
- }
-
- @Implementation
- public List<AppWidgetProviderInfo> getInstalledProvidersForProfile(UserHandle profile) {
- UserHandle user = profile == null ? Process.myUserHandle() : profile;
- return super.getInstalledProviders().stream().filter(
- info -> user.equals(info.getProfile())).collect(Collectors.toList());
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
deleted file mode 100644
index eae0101..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import android.app.backup.BackupManager;
-import android.os.UserHandle;
-import android.util.LongSparseArray;
-
-import androidx.annotation.Nullable;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowBackupManager;
-
-/**
- * Extension of {@link ShadowBackupManager} with missing shadow methods
- */
-@Implements(value = BackupManager.class)
-public class LShadowBackupManager extends ShadowBackupManager {
-
- private LongSparseArray<UserHandle> mProfileMapping = new LongSparseArray<>();
-
- public void addProfile(long userSerial, UserHandle userHandle) {
- mProfileMapping.put(userSerial, userHandle);
- }
-
- @Implementation
- @Nullable
- public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
- return mProfileMapping.get(ancestralSerialNumber);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
deleted file mode 100644
index 3813fa1..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Display;
-
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadows.ShadowDisplay;
-
-/**
- * Extension of {@link ShadowDisplay} with missing shadow methods
- */
-@Implements(value = Display.class)
-public class LShadowDisplay extends ShadowDisplay {
-
- private final Rect mInsets = new Rect();
-
- @RealObject Display realObject;
-
- /**
- * Sets the insets for the display
- */
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- }
-
- @Override
- protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
- directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
- outSmallestSize.x -= mInsets.left + mInsets.right;
- outLargestSize.x -= mInsets.left + mInsets.right;
-
- outSmallestSize.y -= mInsets.top + mInsets.bottom;
- outLargestSize.y -= mInsets.top + mInsets.bottom;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
deleted file mode 100644
index 6a6f0fb..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2019 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.shadows;
-
-import static org.robolectric.util.ReflectionHelpers.ClassParameter;
-import static org.robolectric.util.ReflectionHelpers.callConstructor;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-import android.util.ArraySet;
-
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowLauncherApps;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ShadowLauncherApps} with missing shadow methods
- */
-@Implements(value = LauncherApps.class)
-public class LShadowLauncherApps extends ShadowLauncherApps {
-
- public final ArraySet<PackageUserKey> disabledApps = new ArraySet<>();
- public final ArraySet<ComponentKey> disabledActivities = new ArraySet<>();
-
- @Implementation
- @Override
- protected List<ShortcutInfo> getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) {
- try {
- return super.getShortcuts(query, user);
- } catch (UnsupportedOperationException e) {
- return Collections.emptyList();
- }
- }
-
- @Implementation
- protected boolean isPackageEnabled(String packageName, UserHandle user) {
- return !disabledApps.contains(new PackageUserKey(packageName, user));
- }
-
- @Implementation
- protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
- return !disabledActivities.contains(new ComponentKey(component, user));
- }
-
- @Implementation
- protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
- ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
- .resolveActivity(intent, 0);
- return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user);
- }
-
- public LauncherActivityInfo getLauncherActivityInfo(
- ActivityInfo activityInfo, UserHandle user) {
- return callConstructor(LauncherActivityInfo.class,
- ClassParameter.from(Context.class, RuntimeEnvironment.application),
- ClassParameter.from(ActivityInfo.class, activityInfo),
- ClassParameter.from(UserHandle.class, user));
- }
-
- @Implementation
- public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
- throws PackageManager.NameNotFoundException {
- return RuntimeEnvironment.application.getPackageManager()
- .getApplicationInfo(packageName, flags);
- }
-
- @Implementation
- public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setPackage(packageName);
- return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
- .stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
- .collect(Collectors.toList());
- }
-
- @Implementation
- public boolean hasShortcutHostPermission() {
- return true;
- }
-
- @Implementation
- public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
- return RuntimeEnvironment.application.getPackageManager().getPackageInstaller()
- .getAllSessions();
- }
-
- @Implementation
- public void registerPackageInstallerSessionCallback(
- Executor executor, PackageInstaller.SessionCallback callback) {
- }
-
- @Override
- protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
- UserHandle user) {
- Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
- return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
- .stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
- .collect(Collectors.toList());
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
deleted file mode 100644
index b58e4b7..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 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.shadows;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.DeviceFlag;
-import com.android.launcher3.util.LooperExecutor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadow.api.Shadow;
-
-/**
- * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
- */
-@Implements(value = DeviceFlag.class, isInAndroidSdk = false)
-public class ShadowDeviceFlag {
-
- @RealObject private DeviceFlag mRealObject;
- @Nullable private Boolean mValue;
-
- /**
- * Mock change listener as it uses internal system classes not available to robolectric
- */
- @Implementation
- protected void addChangeListener(Context context, Runnable r) { }
-
- @Implementation
- protected static boolean getDeviceValue(String key, boolean defaultValue) {
- return defaultValue;
- }
-
- @Implementation
- public boolean get() {
- if (mValue != null) {
- return mValue;
- }
- return Shadow.directlyOn(mRealObject, DeviceFlag.class, "get");
- }
-
- public void setValue(boolean value) {
- mValue = new Boolean(value);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
deleted file mode 100644
index 57eda7e..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 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.shadows;
-
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-import static org.robolectric.util.ReflectionHelpers.setField;
-
-import android.os.Handler;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.util.LooperExecutor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
- */
-@Implements(value = LooperExecutor.class, isInAndroidSdk = false)
-public class ShadowLooperExecutor {
-
- @RealObject private LooperExecutor mRealExecutor;
-
- private Handler mOverriddenHandler;
-
- @Implementation
- protected Handler getHandler() {
- if (mOverriddenHandler != null) {
- return mOverriddenHandler;
- }
- Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
- Thread thread = handler.getLooper().getThread();
- if (!thread.isAlive()) {
- // Robolectric destroys all loopers at the end of every test. Since Launcher maintains
- // some static threads, they need to be reinitialized in case they were destroyed.
- setField(mRealExecutor, "mHandler",
- new Handler(createAndStartNewLooper(thread.getName())));
- }
- return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
- }
-
- public void setHandler(@Nullable Handler handler) {
- mOverriddenHandler = handler;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
deleted file mode 100644
index 6e2ccf8..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2019 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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
-import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Set;
-import java.util.WeakHashMap;
-
-/**
- * Shadow for {@link MainThreadInitializedObject} to provide reset functionality for static sObjects
- */
-@Implements(value = MainThreadInitializedObject.class, isInAndroidSdk = false)
-public class ShadowMainThreadInitializedObject {
-
- // Keep reference to all created MainThreadInitializedObject so they can be cleared after test
- private static Set<MainThreadInitializedObject> sObjects =
- Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
-
- @RealObject private MainThreadInitializedObject mRealObject;
-
- @Implementation
- protected void __constructor__(ObjectProvider provider) {
- invokeConstructor(MainThreadInitializedObject.class, mRealObject,
- from(ObjectProvider.class, provider));
- sObjects.add(mRealObject);
- }
-
- /**
- * Resets all the initialized sObjects to be null
- */
- public static void resetInitializedObjects() {
- for (MainThreadInitializedObject object : new ArrayList<>(sObjects)) {
- object.initializeForTesting(null);
- }
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
deleted file mode 100644
index 131f691..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import android.content.Context;
-
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Shadow for {@link Overrides} to provide custom overrides for test
- */
-@Implements(value = Overrides.class, isInAndroidSdk = false)
-public class ShadowOverrides {
-
- private static Map<Class, ObjectProvider> sProviderMap = new HashMap<>();
-
- @Implementation
- public static <T extends ResourceBasedOverride> T getObject(
- Class<T> clazz, Context context, int resId) {
- ObjectProvider<T> provider = sProviderMap.get(clazz);
- if (provider != null) {
- return provider.get(context);
- }
- return Shadow.directlyOn(Overrides.class, "getObject",
- ClassParameter.from(Class.class, clazz),
- ClassParameter.from(Context.class, context),
- ClassParameter.from(int.class, resId));
- }
-
- public static <T> void setProvider(Class<T> clazz, ObjectProvider<T> provider) {
- sProviderMap.put(clazz, provider);
- }
-
- public static void clearProvider() {
- sProviderMap.clear();
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
deleted file mode 100644
index a9f2f27..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
-import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
-
-import android.view.View;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow for SurfaceTransactionApplier to override default functionality
- */
-@Implements(className = "com.android.quickstep.util.SurfaceTransactionApplier",
- isInAndroidSdk = false)
-public class ShadowSurfaceTransactionApplier {
-
- @RealObject
- private Object mRealObject;
-
- @Implementation
- protected void __constructor__(View view) {
- invokeConstructor(mRealObject.getClass(), mRealObject, from(View.class, null));
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java b/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
deleted file mode 100644
index efac150..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2020 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 org.mockito.Mockito.mock;
-
-import android.app.Application;
-
-import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
-import com.android.launcher3.shadows.ShadowOverrides;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-
-import org.robolectric.TestLifecycleApplication;
-import org.robolectric.shadows.ShadowLog;
-
-import java.lang.reflect.Method;
-
-public class LauncherTestApplication extends Application implements TestLifecycleApplication {
-
- @Override
- public void beforeTest(Method method) {
- ShadowLog.stream = System.out;
-
- // Disable plugins
- PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
- }
-
- @Override
- public void prepareTest(Object test) { }
-
- @Override
- public void afterTest(Method method) {
- ShadowLog.stream = null;
- ShadowMainThreadInitializedObject.resetInitializedObjects();
- ShadowOverrides.clearProvider();
- }
-}
diff --git a/robolectric_tests/unstaged/SettingsCacheTest.java b/robolectric_tests/unstaged/SettingsCacheTest.java
deleted file mode 100644
index fbf4c63..0000000
--- a/robolectric_tests/unstaged/SettingsCacheTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2021 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 org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.net.Uri;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class SettingsCacheTest {
-
- public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1");
- public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");;
-
- private SettingsCache.OnChangeListener mChangeListener;
- private SettingsCache mSettingsCache;
-
- @Before
- public void setup() {
- mChangeListener = mock(SettingsCache.OnChangeListener.class);
- Context targetContext = RuntimeEnvironment.application;
- mSettingsCache = SettingsCache.INSTANCE.get(targetContext);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener);
- }
-
- @Test
- public void listenerCalledOnChange() {
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- verify(mChangeListener, times(1)).onSettingsChanged(true);
- }
-
- @Test
- public void getValueRespectsDefaultValue() {
- // Case of key not found
- boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertFalse(val);
- }
-
- @Test
- public void getValueHitsCache() {
- mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
- boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertTrue(val);
- }
-
- @Test
- public void getValueUpdatedCache() {
- // First ensure there's nothing in cache
- boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertFalse(val);
-
- mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
- val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertTrue(val);
- }
-
- @Test
- public void multipleListenersSingleKey() {
- SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener);
-
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- verify(mChangeListener, times(1)).onSettingsChanged(true);
- verify(secondListener, times(1)).onSettingsChanged(true);
- }
-
- @Test
- public void singleListenerMultipleKeys() {
- SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener);
-
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
- verify(mChangeListener, times(1)).onSettingsChanged(true);
- verify(secondListener, times(1)).onSettingsChanged(true);
- }
-
- @Test
- public void sameListenerMultipleKeys() {
- SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener);
-
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
- verify(mChangeListener, times(2)).onSettingsChanged(true);
- verify(secondListener, times(0)).onSettingsChanged(true);
- }
-}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 978886d..108091c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -108,6 +108,8 @@
// relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
// page.
protected int mCurrentPageScrollDiff;
+ // The current page the PagedView is scrolling over on it's way to the destination page.
+ protected int mCurrentScrollOverPage;
@ViewDebug.ExportedProperty(category = "launcher")
protected int mNextPage = INVALID_PAGE;
@@ -180,6 +182,7 @@
mScroller = new OverScroller(context, SCROLL);
mCurrentPage = 0;
+ mCurrentScrollOverPage = 0;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
@@ -437,6 +440,7 @@
}
int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
mCurrentPage = validateNewPage(currentPage);
+ mCurrentScrollOverPage = mCurrentPage;
updateCurrentPageScroll();
notifyPageSwitchListener(prevPage);
invalidate();
@@ -557,9 +561,11 @@
if (newPos < mMinScroll && oldPos >= mMinScroll) {
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
+ onEdgeAbsorbingScroll();
} else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
+ onEdgeAbsorbingScroll();
}
}
@@ -577,6 +583,7 @@
sendScrollAccessibilityEvent();
int prevPage = mCurrentPage;
mCurrentPage = validateNewPage(mNextPage);
+ mCurrentScrollOverPage = mCurrentPage;
mNextPage = INVALID_PAGE;
notifyPageSwitchListener(prevPage);
@@ -766,7 +773,8 @@
childStart += primaryDimension + getChildGap();
// This makes sure that the space is added after the page, not after each panel
- if (i % panelCount == panelCount - 1) {
+ int lastPanel = mIsRtl ? 0 : panelCount - 1;
+ if (i % panelCount == lastPanel) {
childStart += mPageSpacing;
}
}
@@ -837,6 +845,7 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
mCurrentPage = validateNewPage(mCurrentPage);
+ mCurrentScrollOverPage = mCurrentPage;
dispatchPageCountChanged();
}
@@ -1408,6 +1417,20 @@
protected void onNotSnappingToPageInFreeScroll() { }
+ /**
+ * Called when the view edges absorb part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onEdgeAbsorbingScroll() {
+ }
+
+ /**
+ * Called when the current page closest to the center of the screen changes as part of the
+ * scroll. Subclasses can override this to provide custom behavior during scroll.
+ */
+ protected void onScrollOverPageChanged() {
+ }
+
protected boolean shouldFlingForVelocity(int velocity) {
float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
return Math.abs(velocity) > threshold;
@@ -1692,6 +1715,15 @@
}
@Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ int newDestinationPage = getDestinationPage();
+ if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) {
+ mCurrentScrollOverPage = newDestinationPage;
+ onScrollOverPageChanged();
+ }
+ }
+
+ @Override
public CharSequence getAccessibilityClassName() {
// Some accessibility services have special logic for ScrollView. Since we provide same
// accessibility info as ScrollView, inform the service to handle use the same way.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8a43ca9..9a3da53 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -338,15 +338,17 @@
int paddingBottom = grid.cellLayoutBottomPaddingPx;
int panelCount = getPanelCount();
+ int rightPanelModulus = mIsRtl ? 0 : panelCount - 1;
+ int leftPanelModulus = mIsRtl ? panelCount - 1 : 0;
int numberOfScreens = mScreenOrder.size();
for (int i = 0; i < numberOfScreens; i++) {
int paddingLeft = paddingLeftRight;
int paddingRight = paddingLeftRight;
if (panelCount > 1) {
- if (i % panelCount == 0) { // left side panel
+ if (i % panelCount == leftPanelModulus) {
paddingLeft = paddingLeftRight;
paddingRight = 0;
- } else if (i % panelCount == panelCount - 1) { // right side panel
+ } else if (i % panelCount == rightPanelModulus) {
paddingLeft = 0;
paddingRight = paddingLeftRight;
} else { // middle panel
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 761053d..1076e88 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -28,6 +28,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.util.IntSet;
import java.util.Locale;
import java.util.Objects;
@@ -44,6 +45,7 @@
public static final int TYPE_PHONE = 0;
public static final int TYPE_MULTI_DISPLAY = 1;
public static final int TYPE_TABLET = 2;
+ public static final IntSet COMPATIBLE_TYPES = IntSet.wrap(TYPE_PHONE, TYPE_MULTI_DISPLAY);
private final String mGridSizeString;
private final int mNumHotseat;
@@ -109,7 +111,9 @@
if (o == null || getClass() != o.getClass()) return false;
DeviceGridState that = (DeviceGridState) o;
return mNumHotseat == that.mNumHotseat
- && mDeviceType == that.mDeviceType
+ && (mDeviceType == that.mDeviceType
+ || (COMPATIBLE_TYPES.contains(mDeviceType)
+ && COMPATIBLE_TYPES.contains(that.mDeviceType)))
&& Objects.equals(mGridSizeString, that.mGridSizeString);
}
}
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 4d63218..b06b8a1 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -20,6 +20,7 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -29,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
@@ -44,6 +46,7 @@
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -57,12 +60,15 @@
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.FlagTogglerPrefUi;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.OnboardingPrefs;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -104,6 +110,7 @@
initFlags();
loadPluginPrefs();
maybeAddSandboxCategory();
+ addOnboardingPrefsCatergory();
if (getActivity() != null) {
getActivity().setTitle("Developer Options");
@@ -153,6 +160,15 @@
}
});
+ if (getArguments() != null) {
+ String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY);
+ // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
+ // key. This is a slight variation where we instead filter by the human-readable titles.
+ if (filter != null) {
+ filterBox.setText(filter);
+ }
+ }
+
View listView = getListView();
final int bottomPadding = listView.getPaddingBottom();
listView.setOnApplyWindowInsetsListener((v, insets) -> {
@@ -355,6 +371,28 @@
sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
}
+ private void addOnboardingPrefsCatergory() {
+ PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
+ onboardingCategory.setSummary("Reset these if you want to see the education again.");
+ for (Map.Entry<String, String[]> titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) {
+ String title = titleAndKeys.getKey();
+ String[] keys = titleAndKeys.getValue();
+ Preference onboardingPref = new Preference(getContext());
+ onboardingPref.setTitle(title);
+ onboardingPref.setSummary("Tap to reset");
+ onboardingPref.setOnPreferenceClickListener(preference -> {
+ SharedPreferences.Editor sharedPrefsEdit = Utilities.getPrefs(getContext()).edit();
+ for (String key : keys) {
+ sharedPrefsEdit.remove(key);
+ }
+ sharedPrefsEdit.apply();
+ Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
+ return true;
+ });
+ onboardingCategory.addPreference(onboardingPref);
+ }
+ }
+
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
.replace("com.android.launcher3.action.PLUGIN_", "");
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index cf1467a..5ba0d30 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -39,6 +39,14 @@
public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
+ // When adding a new key, add it here as well, to be able to reset it from Developer Options.
+ public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
+ "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
+ "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
+ HOTSEAT_LONGPRESS_TIP_SEEN },
+ "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
+ "Taskbar Education", new String[] { TASKBAR_EDU_SEEN }
+ );
/**
* Events that either have happened or have not (booleans).
diff --git a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java b/tests/src/com/android/launcher3/settings/SettingsActivityTest.java
similarity index 96%
rename from robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java
rename to tests/src/com/android/launcher3/settings/SettingsActivityTest.java
index 3271812..1c205f0 100644
--- a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java
+++ b/tests/src/com/android/launcher3/settings/SettingsActivityTest.java
@@ -55,6 +55,7 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,6 +76,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_aboutTap_launchesActivity() {
ActivityScenario.launch(SettingsActivity.class);
onView(withId(R.id.recycler_view)).perform(
@@ -88,6 +90,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_developerOptionsTap_launchesActivityWithFragment() {
PluginPrefs.setHasPlugins(mApplicationContext);
ActivityScenario.launch(SettingsActivity.class);
@@ -100,6 +103,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_aboutScreenIntent() {
Bundle fragmentArgs = new Bundle();
fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen");
@@ -114,6 +118,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_developerOptionsFragmentIntent() {
Intent intent = new Intent(mApplicationContext, SettingsActivity.class)
.putExtra(EXTRA_FRAGMENT, DeveloperOptionsFragment.class.getName());
@@ -125,6 +130,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_intentWithUnknownFragment() {
String fragmentClass = PreferenceFragmentCompat.class.getName();
Intent intent = new Intent(mApplicationContext, SettingsActivity.class)
@@ -139,6 +145,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_backButtonFinishesActivity() {
Bundle fragmentArgs = new Bundle();
fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen");