Merge "Detects nav bar gestures to progress through Home tutorial." into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index eac0bfa..fe57e9b 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -13,12 +13,20 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.LauncherRecentsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:accessibilityPaneTitle="@string/accessibility_recent_apps"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:theme="@style/HomeScreenElementTheme"
- android:visibility="invisible" />
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.quickstep.views.LauncherRecentsView
+ android:id="@+id/overview_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:theme="@style/HomeScreenElementTheme"
+ android:visibility="invisible" />
+
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
+</merge>
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 363840a..9266b06 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -20,6 +20,7 @@
<dimen name="chip_hint_start_padding">10dp</dimen>
<dimen name="chip_hint_end_padding">12dp</dimen>
<dimen name="chip_hint_horizontal_margin">20dp</dimen>
+ <dimen name="chip_hint_vertical_offset">16dp</dimen>
<dimen name="chip_hint_elevation">2dp</dimen>
<dimen name="chip_icon_size">16dp</dimen>
<dimen name="chip_text_height">26dp</dimen>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 0019ecb..79b4002 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -37,7 +38,6 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.util.FloatProperty;
import android.view.View;
import androidx.annotation.NonNull;
@@ -49,7 +49,6 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -198,16 +197,15 @@
case INDEX_RECENTS_FADE_ANIM:
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
- case INDEX_RECENTS_TRANSLATE_X_ANIM:
- PagedOrientationHandler orientationHandler =
- ((RecentsView)mLauncher.getOverviewPanel()).getPagedViewOrientedState()
- .getOrientationHandler();
- FloatProperty<View> translate = orientationHandler.getPrimaryViewTranslate();
- return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), translate)
+ case INDEX_RECENTS_TRANSLATE_X_ANIM: {
+ RecentsView rv = mLauncher.getOverviewPanel();
+ return new SpringAnimationBuilder(mLauncher)
+ .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
- .build(mLauncher);
+ .build(rv, ADJACENT_PAGE_OFFSET);
+ }
case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 8af26c6..7520688 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -19,11 +19,13 @@
import static com.android.launcher3.graphics.IconShape.getShape;
import android.content.Context;
+import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.DashPathEffect;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.Rect;
+import android.os.Process;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -39,6 +41,7 @@
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
@@ -51,13 +54,18 @@
public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
LauncherAccessibilityDelegate.AccessibilityActionHandler {
+ private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.11f;
boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Path mRingPath = new Path();
private boolean mIsPinned = false;
- private int mNormalizedIconRadius;
+ private final int mNormalizedIconRadius;
+ private final BlurMaskFilter mShadowFilter;
+ private int mPlateColor;
+
public PredictedAppIcon(Context context) {
this(context, null, 0);
@@ -73,13 +81,18 @@
mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
setOnClickListener(ItemClickHandler.INSTANCE);
setOnFocusChangeListener(Launcher.getLauncher(context).getFocusHandler());
+ int shadowSize = context.getResources().getDimensionPixelSize(
+ R.dimen.blur_size_thin_outline);
+ mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
}
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
if (!mIsPinned) {
- drawEffect(canvas);
+ boolean isBadged = getTag() instanceof WorkspaceItemInfo
+ && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+ drawEffect(canvas, isBadged);
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
@@ -102,7 +115,7 @@
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
super.applyFromWorkspaceItem(info);
int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
- mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
+ mPlateColor = ColorUtils.setAlphaComponent(color, 200);
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -174,9 +187,25 @@
return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
}
- private void drawEffect(Canvas canvas) {
- getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(),
- mNormalizedIconRadius, mIconRingPaint);
+ private void drawEffect(Canvas canvas, boolean isBadged) {
+ mRingPath.reset();
+ getShape().addToPath(mRingPath, getOutlineOffsetX(), getOutlineOffsetY(),
+ mNormalizedIconRadius);
+ if (isBadged) {
+ float outlineSize = mNormalizedIconRadius * RING_EFFECT_RATIO * 2;
+ float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
+ float badgeInset = mNormalizedIconRadius * 2 - badgeSize;
+ getShape().addToPath(mRingPath, getOutlineOffsetX() + badgeInset,
+ getOutlineOffsetY() + badgeInset, badgeSize / 2);
+
+ }
+ mIconRingPaint.setColor(RING_SHADOW_COLOR);
+ mIconRingPaint.setMaskFilter(mShadowFilter);
+ canvas.drawPath(mRingPath, mIconRingPaint);
+ mIconRingPaint.setColor(mPlateColor);
+ mIconRingPaint.setMaskFilter(null);
+ canvas.drawPath(mRingPath, mIconRingPaint);
}
/**
@@ -205,10 +234,8 @@
mOffsetX = icon.getOutlineOffsetX();
mOffsetY = icon.getOutlineOffsetY();
mIconRadius = icon.mNormalizedIconRadius;
- mOutlinePaint.setStyle(Paint.Style.STROKE);
- mOutlinePaint.setStrokeWidth(5);
- mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0));
- mOutlinePaint.setColor(Color.argb(100, 245, 245, 245));
+ mOutlinePaint.setStyle(Paint.Style.FILL);
+ mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 3d6e519..2f55fda 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,19 +15,14 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.FloatProperty;
-import android.view.View;
-import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
@@ -37,6 +32,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -60,7 +56,7 @@
mRecentsView.updateEmptyMessage();
mRecentsView.resetTaskVisuals();
}
- setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state, LINEAR);
+ setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
}
@@ -78,22 +74,17 @@
AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
- setAlphas(builder, toState,
- config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+ setAlphas(builder, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), LINEAR);
}
- private void setAlphas(PropertySetter propertySetter, LauncherState state,
- Interpolator actionInterpolator) {
+ private void setAlphas(PropertySetter propertySetter, LauncherState state) {
float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
buttonAlpha, LINEAR);
-
- View actionsView = mLauncher.getActionsView();
- if (actionsView != null) {
- propertySetter.setFloat(actionsView, VIEW_ALPHA, buttonAlpha, actionInterpolator);
- }
+ propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
+ MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index a87d6d1..e57e841 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -61,13 +61,16 @@
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {getOverviewScale(launcher), NO_OFFSET};
+ }
+
+ private float getOverviewScale(Launcher launcher) {
// Initialize the recents view scale to what it would be when starting swipe up
RecentsView recentsView = launcher.getOverviewPanel();
int taskCount = recentsView.getTaskViewCount();
- if (taskCount == 0) {
- return super.getOverviewScaleAndTranslation(launcher);
- }
+ if (taskCount == 0) return 1;
+
TaskView dummyTask;
if (recentsView.getCurrentPage() >= recentsView.getTaskViewStartIndex()) {
if (recentsView.getCurrentPage() <= taskCount - 1) {
@@ -78,8 +81,8 @@
} else {
dummyTask = recentsView.getTaskViewAt(0);
}
- return recentsView.getTempAppWindowAnimationHelper().updateForFullscreenOverview(dummyTask)
- .getScaleAndTranslation();
+ return recentsView.getTempAppWindowAnimationHelper()
+ .updateForFullscreenOverview(dummyTask).getSrcToTargetScale();
}
@Override
@@ -106,7 +109,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 1f;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index 1288e7b..b27f16a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -24,24 +24,18 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.states.StateAnimationConfig;
public class OverviewPeekState extends OverviewState {
+ private static final float OVERVIEW_OFFSET = 0.7f;
+
public OverviewPeekState(int id) {
super(id);
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
- result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
- - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
- if (Utilities.isRtl(launcher.getResources())) {
- result.translationX = -result.translationX;
- }
- return result;
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {NO_SCALE, OVERVIEW_OFFSET};
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index bcfb11c..e44f59f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -24,7 +24,6 @@
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
@@ -123,8 +122,8 @@
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1f, 0f, 0f);
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {NO_SCALE, NO_OFFSET};
}
@Override
@@ -144,7 +143,6 @@
@Override
public void onStateTransitionEnd(Launcher launcher) {
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
DiscoveryBounce.showForOverviewIfNeeded(launcher);
RecentsView recentsView = launcher.getOverviewPanel();
AccessibilityManagerCompat.sendCustomAccessibilityEvent(
@@ -205,7 +203,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 1f;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 77118d5..2b456ec 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -23,6 +22,7 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.ValueAnimator;
@@ -131,12 +131,8 @@
final PendingAnimation builder = new PendingAnimation(accuracy);
if (mStartState == OVERVIEW) {
RecentsView recentsView = mLauncher.getOverviewPanel();
- float pullbackDist = mPullbackDistance;
- if (!recentsView.isRtl()) {
- pullbackDist = -pullbackDist;
- }
-
- builder.setFloat(recentsView, VIEW_TRANSLATE_X, pullbackDist, PULLBACK_INTERPOLATOR);
+ builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
+ -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
builder.addOnFrameCallback(
() -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 71aa2e8..381ecf1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -166,8 +166,8 @@
float velocityDp = dpiFromPx(velocity);
boolean isFling = Math.abs(velocityDp) > 1;
LauncherStateManager stateManager = mLauncher.getStateManager();
- if (isFling) {
- // When flinging, go back to home instead of overview.
+ boolean goToHomeInsteadOfOverview = isFling;
+ if (goToHomeInsteadOfOverview) {
if (velocity > 0) {
stateManager.goToState(NORMAL, true,
() -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
@@ -187,20 +187,21 @@
() -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
anim.start();
}
- } else {
- if (mReachedOverview) {
- float distanceDp = dpiFromPx(Math.max(
- Math.abs(mRecentsView.getTranslationX()),
- Math.abs(mRecentsView.getTranslationY())));
- long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
- distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
- mRecentsView.animate()
- .translationX(0)
- .translationY(0)
- .setInterpolator(ACCEL_DEACCEL)
- .setDuration(duration)
- .withEndAction(this::maybeSwipeInteractionToOverviewComplete);
- }
+ }
+ if (mReachedOverview) {
+ float distanceDp = dpiFromPx(Math.max(
+ Math.abs(mRecentsView.getTranslationX()),
+ Math.abs(mRecentsView.getTranslationY())));
+ long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+ distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+ mRecentsView.animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(ACCEL_DEACCEL)
+ .setDuration(duration)
+ .withEndAction(goToHomeInsteadOfOverview
+ ? null
+ : this::maybeSwipeInteractionToOverviewComplete);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index c92a872..f4f8bc9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -40,17 +40,15 @@
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.view.MotionEvent;
-import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseQuickstepLauncher;
@@ -59,6 +57,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.states.StateAnimationConfig;
@@ -237,58 +236,32 @@
private void setupOverviewAnimators() {
final LauncherState fromState = QUICK_SWITCH;
final LauncherState toState = OVERVIEW;
- LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
- .getOverviewScaleAndTranslation(mLauncher);
- LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
- .getOverviewScaleAndTranslation(mLauncher);
- // Update RecentView's translationX to have it start offscreen.
- float startScale = Utilities.mapRange(
- SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
- fromScaleAndTranslation.scale,
- toScaleAndTranslation.scale);
- fromScaleAndTranslation.translationX = mRecentsView.getOffscreenTranslationX(startScale);
// Set RecentView's initial properties.
- mRecentsView.setScaleX(fromScaleAndTranslation.scale);
- mRecentsView.setScaleY(fromScaleAndTranslation.scale);
- mRecentsView.setTranslationX(fromScaleAndTranslation.translationX);
- mRecentsView.setTranslationY(fromScaleAndTranslation.translationY);
+ SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
+ ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
+ float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
// - RecentsView translationX
// - OverviewScrim
- AnimatorSet xOverviewAnim = new AnimatorSet();
- xOverviewAnim.play(ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
- toScaleAndTranslation.translationX));
- xOverviewAnim.play(ObjectAnimator.ofFloat(
- mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
- toState.getOverviewScrimAlpha(mLauncher)));
- long xAccuracy = (long) (mXRange * 2);
- xOverviewAnim.setDuration(xAccuracy);
- mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
+ PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
+ xAnim.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], LINEAR);
+ xAnim.setFloat(mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+ toState.getOverviewScrimAlpha(mLauncher), LINEAR);
+ mXOverviewAnim = xAnim.createPlaybackController();
mXOverviewAnim.dispatchOnStart();
// As we drag up, animate the following properties:
- // - RecentsView translationY
// - RecentsView scale
// - RecentsView fullscreenProgress
- AnimatorSet yAnimation = new AnimatorSet();
- Animator translateYAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_Y,
- toScaleAndTranslation.translationY);
- Animator scaleAnim = ObjectAnimator.ofFloat(mRecentsView, SCALE_PROPERTY,
- toScaleAndTranslation.scale);
- Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(mRecentsView, FULLSCREEN_PROGRESS,
- fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
- scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
- fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
- yAnimation.play(translateYAnim);
- yAnimation.play(scaleAnim);
- yAnimation.play(fullscreenProgressAnim);
- long yAccuracy = (long) (mYRange * 2);
- yAnimation.setDuration(yAccuracy);
- mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
+ PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
+ yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR);
+ yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
+ toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR);
+ mYOverviewAnim = yAnim.createPlaybackController();
mYOverviewAnim.dispatchOnStart();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 1b3610a..f6f892b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -24,6 +24,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.MotionEvent;
+import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
@@ -209,9 +210,11 @@
mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
- mTempCords[1] = mTaskBeingDragged.getHeight();
- dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
- mEndDisplacement = dl.getHeight() - mTempCords[1];
+ // Since the thumbnail is what is filling the screen, based the end displacement on it.
+ View thumbnailView = mTaskBeingDragged.getThumbnail();
+ mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
+ dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
+ mEndDisplacement = secondaryLayerDimension - mTempCords[1];
}
mEndDisplacement *= verticalFactor;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index fadde37..d22e5af 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -31,7 +31,6 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
-import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
@@ -46,7 +45,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.FloatingIconView;
@@ -133,13 +131,19 @@
mDeviceState = deviceState;
mGestureState = gestureState;
mActivityInterface = gestureState.getActivityInterface();
- mActivityInitListener =
- mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
mInputConsumer = inputConsumer;
mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ }
+
+ /**
+ * To be called at the end of constructor of subclasses. This calls various methods which can
+ * depend on proper class initialization.
+ */
+ protected void initAfterSubclassConstructor() {
initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
- .getDeviceProfile(mContext));
+ .getDeviceProfile(mContext));
}
protected void performHapticFeedback() {
@@ -244,6 +248,10 @@
return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
}
+ protected void updateSource(Rect stackBounds, RemoteAnimationTargetCompat runningTarget) {
+ mAppWindowAnimationHelper.updateSource(stackBounds, runningTarget);
+ }
+
@Override
public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
RecentsAnimationTargets targets) {
@@ -267,7 +275,7 @@
dp.updateInsets(targets.homeContentInsets);
dp.updateIsSeascape(mContext);
if (runningTaskTarget != null) {
- mAppWindowAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+ updateSource(overviewStackBounds, runningTaskTarget);
}
mAppWindowAnimationHelper.prepareAnimation(dp, false /* isOpening */);
@@ -317,6 +325,7 @@
mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
dp, mContext, TEMP_RECT);
+
if (!dp.isMultiWindowMode) {
// When updating the target rect, also update the home bounds since the location on
// screen of the launcher window may be stale (position is not updated until first
@@ -325,13 +334,14 @@
mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
}
int displayRotation = 0;
- if (mOrientedState != null) {
+ if (mOrientedState != null && mOrientedState.isMultipleOrientationSupportedByDevice()) {
// TODO(b/150300347): The first recents animation after launcher is started with the
// foreground app not in landscape will look funky until that bug is fixed
displayRotation = mOrientedState.getDisplayRotation();
RectF tempRectF = new RectF(TEMP_RECT);
- mOrientedState.mapRectFromNormalOrientation(tempRectF, dp.widthPx, dp.heightPx);
+ mOrientedState.mapRectFromRotation(displayRotation,
+ tempRectF, dp.widthPx, dp.heightPx);
tempRectF.roundOut(TEMP_RECT);
}
mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
@@ -395,11 +405,15 @@
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
- public void initWhenReady() {
+ /**
+ * Registers a callback to run when the activity is ready.
+ * @param intent The intent that will be used to start the activity if it doesn't exist already.
+ */
+ public void initWhenReady(Intent intent) {
// Preload the plan
RecentsModel.INSTANCE.get(mContext).getTasks(null);
- mActivityInitListener.register();
+ mActivityInitListener.register(intent);
}
/**
@@ -407,11 +421,12 @@
*/
protected void applyTransformUnchecked() {
float shift = mCurrentShift.value;
- float offset = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
+ float offset = mRecentsView == null ? 0 : mRecentsView.getScrollOffsetScaled();
float taskSize = getOrientationHandler()
.getPrimarySize(mAppWindowAnimationHelper.getTargetRect());
float offsetScale = getTaskCurveScaleForOffset(offset, taskSize);
- mTransformParams.setProgress(shift)
+ mTransformParams
+ .setProgress(shift)
.setOffset(offset)
.setOffsetScale(offsetScale)
.setTargetSet(mRecentsAnimationTargets)
@@ -457,6 +472,7 @@
FloatingIconView fiv = (FloatingIconView) floatingView;
anim.addAnimatorListener(fiv);
fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
+ fiv.setFastFinishRunnable(anim::end);
}
AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 46b026e..1b2979b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -129,6 +129,7 @@
mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
+ initAfterSubclassConstructor();
initStateCallbacks();
}
@@ -188,10 +189,10 @@
}
@Override
- public void initWhenReady() {
+ public void initWhenReady(Intent intent) {
if (mInQuickSwitchMode) {
// Only init if we are in quickswitch mode
- super.initWhenReady();
+ super.initWhenReady(intent);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
index 33fe5a9..fd17551 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -42,9 +42,10 @@
public class ImageActionsApi {
private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi";
- private final Context mContext;
- private final Supplier<Bitmap> mBitmapSupplier;
- private final SystemUiProxy mSystemUiProxy;
+
+ protected final Context mContext;
+ protected final Supplier<Bitmap> mBitmapSupplier;
+ protected final SystemUiProxy mSystemUiProxy;
public ImageActionsApi(Context context, Supplier<Bitmap> bitmapSupplier) {
mContext = context;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 455ae76..b4764dc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,8 +15,6 @@
*/
package com.android.quickstep;
-import static android.view.View.TRANSLATION_Y;
-
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_FADE_ANIM;
import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_TRANSLATE_X_ANIM;
@@ -25,15 +23,14 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -57,7 +54,6 @@
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -81,7 +77,6 @@
*/
public final class LauncherActivityInterface implements BaseActivityInterface<Launcher> {
- private Runnable mAdjustInterpolatorsRunnable;
private Pair<Float, Float> mSwipeUpPullbackStartAndMaxProgress =
BaseActivityInterface.super.getSwipeUpPullbackStartAndMaxProgress();
@@ -243,14 +238,6 @@
}
@Override
- public void adjustActivityControllerInterpolators() {
- if (mAdjustInterpolatorsRunnable != null) {
- mAdjustInterpolatorsRunnable.run();
- mAdjustInterpolatorsRunnable = null;
- }
- }
-
- @Override
public void onTransitionCancelled() {
launcher.getStateManager().goToState(startState, false /* animate */);
}
@@ -272,42 +259,24 @@
.createStateElementAnimation(
INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
- int runningTaskIndex = recentsView.getRunningTaskIndex();
- if (runningTaskIndex == recentsView.getTaskViewStartIndex()) {
- // If we are on the first task (we haven't quick switched), translate recents in
- // from the side. Calculate the start translation based on current scale/scroll.
- float currScale = recentsView.getScaleX();
- float scrollOffsetX = recentsView.getScrollOffset();
- float offscreenX = recentsView.getOffscreenTranslationX(currScale);
-
- float fromTranslation = attached ? offscreenX - scrollOffsetX : 0;
- float toTranslation = attached ? 0 : offscreenX - scrollOffsetX;
- launcher.getStateManager()
- .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
-
- PagedOrientationHandler pagedOrientationHandler =
- recentsView.getPagedViewOrientedState().getOrientationHandler();
- if (!recentsView.isShown() && animate) {
- pagedOrientationHandler
- .getPrimaryViewTranslate().set(recentsView, fromTranslation);
- } else {
- fromTranslation =
- pagedOrientationHandler.getPrimaryViewTranslate().get(recentsView);
- }
-
- if (!animate) {
- pagedOrientationHandler
- .getPrimaryViewTranslate().set(recentsView, toTranslation);
- } else {
- launcher.getStateManager().createStateElementAnimation(
- INDEX_RECENTS_TRANSLATE_X_ANIM,
- fromTranslation, toTranslation).start();
- }
-
- fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+ float fromTranslation = attached ? 1 : 0;
+ float toTranslation = attached ? 0 : 1;
+ launcher.getStateManager()
+ .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+ if (!recentsView.isShown() && animate) {
+ ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
} else {
- fadeAnim.setInterpolator(ACCEL_DEACCEL);
+ fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
}
+ if (!animate) {
+ ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
+ } else {
+ launcher.getStateManager().createStateElementAnimation(
+ INDEX_RECENTS_TRANSLATE_X_ANIM,
+ fromTranslation, toTranslation).start();
+ }
+
+ fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
}
};
@@ -365,51 +334,20 @@
return;
}
- LauncherState.ScaleAndTranslation fromScaleAndTranslation
- = fromState.getOverviewScaleAndTranslation(launcher);
- LauncherState.ScaleAndTranslation endScaleAndTranslation
- = endState.getOverviewScaleAndTranslation(launcher);
- float fromTranslationY = fromScaleAndTranslation.translationY;
- float endTranslationY = endScaleAndTranslation.translationY;
float fromFullscreenProgress = fromState.getOverviewFullscreenProgress();
float endFullscreenProgress = endState.getOverviewFullscreenProgress();
- Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
- fromScaleAndTranslation.scale, endScaleAndTranslation.scale);
- Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
- fromTranslationY, endTranslationY);
+ float fromScale = fromState.getOverviewScaleAndOffset(launcher)[0];
+ float endScale = endState.getOverviewScaleAndOffset(launcher)[0];
+
+ Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, endScale);
Animator applyFullscreenProgress = ObjectAnimator.ofFloat(recentsView,
RecentsView.FULLSCREEN_PROGRESS, fromFullscreenProgress, endFullscreenProgress);
- anim.playTogether(scale, translateY, applyFullscreenProgress);
-
- mAdjustInterpolatorsRunnable = () -> {
- // Adjust the translateY interpolator to account for the running task's top inset.
- // When progress <= 1, this is handled by each task view as they set their fullscreen
- // progress. However, once we go to progress > 1, fullscreen progress stays at 0, so
- // recents as a whole needs to translate further to keep up with the app window.
- TaskView runningTaskView = recentsView.getRunningTaskView();
- if (runningTaskView == null) {
- runningTaskView = recentsView.getCurrentPageTaskView();
- if (runningTaskView == null) {
- // There are no task views in LockTask mode when Overview is enabled.
- return;
- }
- }
- TimeInterpolator oldInterpolator = translateY.getInterpolator();
- Rect fallbackInsets = launcher.getDeviceProfile().getInsets();
- float extraTranslationY = runningTaskView.getThumbnail().getInsets(fallbackInsets).top;
- float normalizedTranslationY = extraTranslationY / (fromTranslationY - endTranslationY);
- translateY.setInterpolator(t -> {
- float newT = oldInterpolator.getInterpolation(t);
- return newT <= 1f ? newT : newT + normalizedTranslationY * (newT - 1);
- });
- };
+ anim.playTogether(scale, applyFullscreenProgress);
// Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
- float pullbackStartProgress = (0.75f - fromScaleAndTranslation.scale)
- / (endScaleAndTranslation.scale - fromScaleAndTranslation.scale);
- float pullbackMaxProgress = (0.5f - fromScaleAndTranslation.scale)
- / (endScaleAndTranslation.scale - fromScaleAndTranslation.scale);
+ float pullbackStartProgress = (0.75f - fromScale) / (endScale - fromScale);
+ float pullbackMaxProgress = (0.5f - fromScale) / (endScale - fromScale);
mSwipeUpPullbackStartAndMaxProgress = new Pair<>(
pullbackStartProgress, pullbackMaxProgress);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 31a2814..52b40a9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -17,6 +17,8 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -39,16 +41,17 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.SystemClock;
-import android.util.Log;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewTreeObserver.OnDrawListener;
@@ -67,7 +70,6 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -78,9 +80,11 @@
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -177,6 +181,9 @@
private AnimatorPlaybackController mLauncherTransitionController;
private boolean mHasLauncherTransitionControllerStarted;
+ private final TaskViewSimulator mTaskViewSimulator;
+ private AnimatorPlaybackController mWindowTransitionController;
+
private AnimationFactory mAnimationFactory = (t) -> { };
private boolean mWasLauncherAlreadyVisible;
@@ -201,6 +208,10 @@
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
+ mTaskViewSimulator = new TaskViewSimulator(
+ context, LayoutUtils::calculateLauncherTaskSize, true);
+
+ initAfterSubclassConstructor();
initStateCallbacks();
}
@@ -473,23 +484,11 @@
} else if (mContinuingLastGesture
&& mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
recentsAttachedToAppWindow = true;
- animate = false;
} else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
// The window is going away so make sure recents is always visible in this case.
recentsAttachedToAppWindow = true;
- animate = false;
} else {
recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
- if (animate) {
- // Only animate if an adjacent task view is visible on screen.
- TaskView adjacentTask1 = mRecentsView.getNextTaskView();
- TaskView adjacentTask2 = mRecentsView.getPreviousTaskView();
- float prevTranslationX = mRecentsView.getTranslationX();
- mRecentsView.setTranslationX(0);
- animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
- || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
- mRecentsView.setTranslationX(prevTranslationX);
- }
}
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
}
@@ -523,6 +522,34 @@
mAnimationFactory.createActivityInterface(mTransitionDragLength);
}
+ @Override
+ protected void updateSource(Rect stackBounds, RemoteAnimationTargetCompat runningTarget) {
+ super.updateSource(stackBounds, runningTarget);
+ mTaskViewSimulator.setPreview(runningTarget, mRecentsAnimationTargets);
+ }
+
+ @Override
+ protected void initTransitionEndpoints(DeviceProfile dp) {
+ super.initTransitionEndpoints(dp);
+ mTaskViewSimulator.setDp(dp, false /* isOpening */);
+ mTaskViewSimulator.setLayoutRotation(
+ mDeviceState.getCurrentActiveRotation(),
+ mDeviceState.getDisplayRotation());
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.setDuration(mTransitionDragLength * 2);
+ anim.setInterpolator(t -> t * mDragLengthFactor);
+ anim.play(ObjectAnimator.ofFloat(mTaskViewSimulator.recentsViewScale,
+ AnimatedFloat.VALUE,
+ mTaskViewSimulator.getFullScreenScale(), 1));
+ anim.play(ObjectAnimator.ofFloat(mTaskViewSimulator.fullScreenProgress,
+ AnimatedFloat.VALUE,
+ BACKGROUND_APP.getOverviewFullscreenProgress(),
+ OVERVIEW.getOverviewFullscreenProgress()));
+ mWindowTransitionController =
+ AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2);
+ }
+
/**
* We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
* (it has its own animation) or if we're already animating the current controller.
@@ -542,7 +569,6 @@
private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
mLauncherTransitionController = anim;
mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
- mAnimationFactory.adjustActivityControllerInterpolators();
mLauncherTransitionController.dispatchOnStart();
updateLauncherTransitionProgress();
}
@@ -555,7 +581,9 @@
@Override
public void updateFinalShift() {
if (mRecentsAnimationTargets != null) {
- applyTransformUnchecked();
+ // Base class expects applyTransformUnchecked to be called here.
+ // TODO: Remove this dependency for swipe-up animation.
+ // applyTransformUnchecked();
updateSysUiFlags(mCurrentShift.value);
}
@@ -575,6 +603,16 @@
}
}
+ if (mWindowTransitionController != null) {
+ float progress = mCurrentShift.value / mDragLengthFactor;
+ mWindowTransitionController.setPlayFraction(progress);
+ mTransformParams
+ .setTargetSet(mRecentsAnimationTargets)
+ .setLauncherOnTop(true);
+
+ mTaskViewSimulator.setScroll(mRecentsView == null ? 0 : mRecentsView.getScrollOffset());
+ mTaskViewSimulator.apply(mTransformParams);
+ }
updateLauncherTransitionProgress();
}
@@ -1020,7 +1058,6 @@
mLauncherTransitionController.dispatchSetInterpolator(t -> end);
} else {
mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
- mAnimationFactory.adjustActivityControllerInterpolators();
}
mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
@@ -1040,7 +1077,8 @@
}
private void continueComputingRecentsScrollIfNecessary() {
- if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) {
+ if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
+ && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
computeRecentsScrollIfInvisible();
mRecentsView.post(this::continueComputingRecentsScrollIfNecessary);
}
@@ -1296,6 +1334,7 @@
private void setTargetAlphaProvider(TargetAlphaProvider provider) {
mAppWindowAnimationHelper.setTaskAlphaCallback(provider);
+ mTaskViewSimulator.setTaskAlphaCallback(provider);
updateFinalShift();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 42d944f..52a2558 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -71,6 +71,7 @@
mRecentsRootView = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
mRecentsRootView.recreateControllers();
+ mFallbackRecentsView.init(findViewById(R.id.overview_actions_view));
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index fbf29af..147f933 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,7 +16,6 @@
package com.android.quickstep;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import android.content.Context;
@@ -84,47 +83,47 @@
/**
* Overlay on each task handling Overview Action Buttons.
*/
- public static class TaskOverlay {
+ public static class TaskOverlay<T extends OverviewActionsView> {
private final Context mApplicationContext;
- private OverviewActionsView mActionsView;
- private final TaskThumbnailView mThumbnailView;
+ protected final TaskThumbnailView mThumbnailView;
+ private T mActionsView;
protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
mThumbnailView = taskThumbnailView;
}
+ protected T getActionsView() {
+ if (mActionsView == null) {
+ mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+ R.id.overview_actions_view);
+ }
+ return mActionsView;
+ }
+
/**
* Called when the current task is interactive for the user
*/
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
ImageActionsApi imageApi = new ImageActionsApi(
mApplicationContext, mThumbnailView::getThumbnail);
+ getActionsView().setCallbacks(new OverlayUICallbacks() {
+ @Override
+ public void onShare() {
+ imageApi.startShareActivity();
+ }
- if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
- && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) {
- mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
- R.id.overview_actions_view);
- }
- if (mActionsView != null) {
- mActionsView.setListener(new OverviewActionsView.Listener() {
- @Override
- public void onShare() {
- imageApi.startShareActivity();
- }
-
- @Override
- public void onScreenshot() {
- imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
- getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
- }
- });
- }
-
+ @Override
+ public void onScreenshot() {
+ imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+ getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
+ }
+ });
}
+
/**
* Called when the overlay is no longer used.
*/
@@ -161,4 +160,16 @@
return Insets.of(0, 0, 0, 0);
}
}
+
+ /**
+ * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
+ * controller.
+ */
+ public interface OverlayUICallbacks {
+ /** User has indicated they want to share the current task. */
+ void onShare();
+
+ /** User has indicated they want to screenshot the current task. */
+ void onScreenshot();
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 0fb51f3..28c2b97 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
+import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -90,7 +91,6 @@
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.tracing.ProtoTraceable;
import java.io.FileDescriptor;
@@ -499,7 +499,10 @@
}
}
- ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+ if (mUncheckedConsumer != InputConsumer.NO_OP) {
+ ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+ }
+
boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL)
&& mConsumer != null
&& !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture();
@@ -595,7 +598,7 @@
}
private void handleOrientationSetup(InputConsumer baseInputConsumer) {
- if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+ if (!isFixedRotationTransformEnabled(this)) {
return;
}
mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
@@ -739,12 +742,14 @@
final BaseActivityInterface<BaseDraggingActivity> activityInterface =
mOverviewComponentObserver.getActivityInterface();
+ final Intent overviewIntent = new Intent(
+ mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
if (activityInterface.getCreatedActivity() == null) {
// Make sure that UI states will be initialized.
activityInterface.createActivityInitListener((wasVisible) -> {
AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
return false;
- }).register();
+ }).register(overviewIntent);
} else if (fromInit) {
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
@@ -752,8 +757,7 @@
return;
}
- mTaskAnimationManager.preloadRecentsAnimation(
- mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
+ mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
}
@Override
@@ -802,6 +806,7 @@
if (mGestureState != null) {
mGestureState.dump(pw);
}
+ SysUINavigationMode.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
: mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
@@ -832,7 +837,7 @@
private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+ return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
@@ -858,11 +863,6 @@
}
}
- public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
- UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
- .startRecentsActivity(intent, null, listener, null, null));
- }
-
@Override
public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) {
mOverscrollPlugin = overscrollPlugin;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index dc0c194..3cf9b2c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -26,10 +26,10 @@
import android.view.View;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.Utilities;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -57,7 +57,6 @@
private boolean mInOverviewState = true;
private float mZoomScale = 1f;
- private float mZoomTranslationY = 0f;
private RunningTaskInfo mRunningTaskInfo;
@@ -66,7 +65,12 @@
}
public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(context, attrs, defStyleAttr, false);
+ }
+
+ @Override
+ public void init(OverviewActionsView actionsView) {
+ super.init(actionsView);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
@@ -139,14 +143,11 @@
if (getTaskViewCount() == 0) {
mZoomScale = 1f;
- mZoomTranslationY = 0f;
} else {
TaskView dummyTask = getTaskViewAt(0);
- ScaleAndTranslation sat = getTempAppWindowAnimationHelper()
+ mZoomScale = getTempAppWindowAnimationHelper()
.updateForFullscreenOverview(dummyTask)
- .getScaleAndTranslation();
- mZoomScale = sat.scale;
- mZoomTranslationY = sat.translationY;
+ .getSrcToTargetScale();
}
setZoomProgress(mZoomInProgress);
@@ -155,7 +156,6 @@
public void setZoomProgress(float progress) {
mZoomInProgress = progress;
SCALE_PROPERTY.set(this, Utilities.mapRange(mZoomInProgress, 1, mZoomScale));
- TRANSLATION_Y.set(this, Utilities.mapRange(mZoomInProgress, 0, mZoomTranslationY));
FULLSCREEN_PROGRESS.set(this, mZoomInProgress);
}
@@ -195,9 +195,4 @@
}
super.applyLoadPlan(tasks);
}
-
- @Override
- protected boolean supportsVerticalLandscape() {
- return false;
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index fe9ef2b..1f6c506 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -333,7 +333,8 @@
mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
- mInteractionHandler.initWhenReady();
+ Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
+ mInteractionHandler.initWhenReady(intent);
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
@@ -341,7 +342,6 @@
mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
notifyGestureStarted();
} else {
- Intent intent = mInteractionHandler.getLaunchIntent();
intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
mInteractionHandler);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
index 0a21413..c49b8f2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
@@ -36,17 +36,14 @@
import com.android.launcher3.R;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
-import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
* Input consumer for handling events to pass to an {@code OverscrollPlugin}.
- *
- * @param <T> Draggable activity subclass used by RecentsView
*/
-public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends DelegateInputConsumer {
+public class OverscrollInputConsumer extends DelegateInputConsumer {
private static final String TAG = "OverscrollInputConsumer";
@@ -61,12 +58,12 @@
private final float mSquaredSlop;
- private final Context mContext;
private final GestureState mGestureState;
@Nullable
private final OverscrollPlugin mPlugin;
private final GestureDetector mGestureDetector;
+ @Nullable
private RecentsView mRecentsView;
public OverscrollInputConsumer(Context context, GestureState gestureState,
@@ -77,7 +74,6 @@
.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
mFlingThresholdPx = context.getResources()
.getDimension(R.dimen.gestures_overscroll_fling_threshold);
- mContext = context;
mGestureState = gestureState;
mPlugin = plugin;
@@ -85,9 +81,6 @@
mSquaredSlop = slop * slop;
mGestureDetector = new GestureDetector(context, new FlingGestureListener());
-
- gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
- .register();
}
@Override
@@ -95,12 +88,6 @@
return TYPE_OVERSCROLL | mDelegate.getType();
}
- private boolean onActivityInit(Boolean alreadyOnHome) {
- mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel();
-
- return true;
- }
-
@Override
public void onMotionEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
@@ -191,10 +178,17 @@
}
private boolean isOverscrolled() {
+ if (mRecentsView == null) {
+ BaseDraggingActivity activity = mGestureState.getActivityInterface()
+ .getCreatedActivity();
+ if (activity != null) {
+ mRecentsView = activity.getOverviewPanel();
+ }
+ }
+
// Make sure there isn't an app to quick switch to on our right
int maxIndex = 0;
- if ((mRecentsView instanceof LauncherRecentsView)
- && ((LauncherRecentsView) mRecentsView).hasRecentsExtraCard()) {
+ if (mRecentsView != null && mRecentsView.hasRecentsExtraCard()) {
maxIndex = 1;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 75a5976..5abbd86 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -30,13 +30,11 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
-import android.view.Surface;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
@@ -60,7 +58,7 @@
public class AppWindowAnimationHelper {
// The bounds of the source app in device coordinates
- private final Rect mSourceStackBounds = new Rect();
+ private final RectF mSourceStackBounds = new RectF();
// The insets of the source app
private final Rect mSourceInsets = new Rect();
// The source app bounds with the source insets applied, in the device coordinates
@@ -159,14 +157,10 @@
mSourceRect.set(scaledTargetRect);
}
- private float getSrcToTargetScale() {
- if (mOrientedState == null ||
- (mOrientedState.getDisplayRotation() == Surface.ROTATION_0
- || mOrientedState.getDisplayRotation() == Surface.ROTATION_180)) {
- return mSourceRect.width() / mTargetRect.width();
- } else {
- return mSourceRect.height() / mTargetRect.height();
- }
+ public float getSrcToTargetScale() {
+ return LayoutUtils.getTaskScale(mOrientedState,
+ mSourceRect.width(), mSourceRect.height(),
+ mTargetRect.width(), mTargetRect.height());
}
public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
@@ -214,7 +208,6 @@
float alpha;
float cornerRadius = 0f;
float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
- int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
if (app.mode == params.mTargetSet.targetMode) {
alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
@@ -251,13 +244,11 @@
alpha = mBaseAlphaCallback.getAlpha(app, progress);
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) {
crop = null;
- layer = Integer.MAX_VALUE;
}
}
builder.withAlpha(alpha)
.withMatrix(mTmpMatrix)
.withWindowCrop(crop)
- .withLayer(layer)
// Since radius is in Surface space, but we draw the rounded corners in screen
// space, we have to undo the scale
.withCornerRadius(cornerRadius / scale);
@@ -273,12 +264,14 @@
mTmpRectF.set(mTargetRect);
Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale);
mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF));
- if (mOrientedState == null || mOrientedState.areMultipleLayoutOrientationsDisabled()) {
+ if (mOrientedState == null
+ || !mOrientedState.isMultipleOrientationSupportedByDevice()) {
mCurrentRect.offset(params.mOffset, 0);
} else {
int displayRotation = mOrientedState.getDisplayRotation();
+ int launcherRotation = mOrientedState.getLauncherRotation();
mOrientedState.getOrientationHandler().offsetTaskRect(mCurrentRect,
- params.mOffset, displayRotation);
+ params.mOffset, displayRotation, launcherRotation);
}
}
@@ -377,15 +370,6 @@
return this;
}
- /**
- * @return The source rect's scale and translation relative to the target rect.
- */
- public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
- float scale = getSrcToTargetScale();
- float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
- return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
- }
-
private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(activity);
if (proxy.isActive()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index dde7605..8a6c4a1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -207,9 +207,23 @@
mRectScaleAnim.skipToEnd();
}
}
+ mRectXAnimEnded = true;
+ mRectYAnimEnded = true;
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ }
+
+ private boolean isEnded() {
+ return mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded;
}
private void onUpdate() {
+ if (isEnded()) {
+ // Prevent further updates from being called. This can happen between callbacks for
+ // ending the x/y/scale animations.
+ return;
+ }
+
if (!mOnUpdateListeners.isEmpty()) {
float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
mTargetRect.width());
@@ -229,7 +243,7 @@
}
private void maybeOnEnd() {
- if (mAnimsStarted && mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+ if (mAnimsStarted && isEnded()) {
mAnimsStarted = false;
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationEnd(null);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index bde6f9a..13c20f1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -28,6 +28,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;
@@ -185,13 +186,13 @@
ResourceProvider rp = DynamicResource.provider(v.getContext());
float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
- ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y)
+ ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
.setStiffness(stiffness)
.setDampingRatio(damping)
.setMinimumVisibleChange(1f)
.setEndValue(0)
.setStartVelocity(mVelocity)
- .build(v.getContext());
+ .build(v, VIEW_TRANSLATE_Y);
springTransY.setStartDelay(startDelay);
mAnimators.play(springTransY);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
new file mode 100644
index 0000000..0bc021b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -0,0 +1,298 @@
+/*
+ * 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.quickstep.util;
+
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.states.RotationHelper.deltaRotation;
+import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
+import static com.android.quickstep.util.AppWindowAnimationHelper.applySurfaceParams;
+import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled;
+import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.RecentsAnimationTargets;
+import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
+import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
+import com.android.quickstep.views.RecentsView.ScrollState;
+import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
+import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskView.FullscreenDrawParams;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+/**
+ * A utility class which emulates the layout behavior of TaskView and RecentsView
+ */
+public class TaskViewSimulator {
+
+ private final Rect mTmpCropRect = new Rect();
+ private final RectF mTempRectF = new RectF();
+
+ private final RecentsOrientedState mOrientationState;
+ private final Context mContext;
+ private final TaskSizeProvider mSizeProvider;
+
+ private final Rect mTaskRect = new Rect();
+ private final PointF mPivot = new PointF();
+ private DeviceProfile mDp;
+
+ private final Matrix mMatrix = new Matrix();
+ private RemoteAnimationTargetCompat mRunningTarget;
+ private RecentsAnimationTargets mAllTargets;
+
+ // Whether to boost the opening animation target layers, or the closing
+ private int mBoostModeTargetLayers = -1;
+ private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
+
+ // Thumbnail view properties
+ private final Rect mThumbnailPosition = new Rect();
+ private final ThumbnailData mThumbnailData = new ThumbnailData();
+ private final PreviewPositionHelper mPositionHelper;
+ private final Matrix mInversePositionMatrix = new Matrix();
+
+ // TaskView properties
+ private final FullscreenDrawParams mCurrentFullscreenParams;
+ private float mCurveScale = 1;
+
+ // RecentsView properties
+ public final AnimatedFloat recentsViewScale = new AnimatedFloat(() -> { });
+ public final AnimatedFloat fullScreenProgress = new AnimatedFloat(() -> { });
+ private final ScrollState mScrollState = new ScrollState();
+ private final int mPageSpacing;
+
+ // Cached calculations
+ private boolean mLayoutValid = false;
+ private boolean mScrollValid = false;
+
+ public TaskViewSimulator(Context context, TaskSizeProvider sizeProvider,
+ boolean rotationSupportedByActivity) {
+ mContext = context;
+ mSizeProvider = sizeProvider;
+ mPositionHelper = new PreviewPositionHelper(context);
+
+ mOrientationState = new RecentsOrientedState(context, rotationSupportedByActivity,
+ i -> { });
+ // We do not need to attach listeners as the simulator is created just for the gesture
+ // duration, and any settings are unlikely to change during this
+ mOrientationState.initWithoutListeners();
+
+ mCurrentFullscreenParams = new FullscreenDrawParams(context);
+ mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ }
+
+ /**
+ * Sets the device profile for the current state
+ */
+ public void setDp(DeviceProfile dp, boolean isOpening) {
+ mDp = dp;
+ mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
+ mLayoutValid = false;
+ }
+
+ /**
+ * @see com.android.quickstep.views.RecentsView#setLayoutRotation(int, int)
+ */
+ public void setLayoutRotation(int touchRotation, int displayRotation) {
+ int launcherRotation;
+ if (!mOrientationState.isMultipleOrientationSupportedByDevice()
+ || mOrientationState.isHomeRotationAllowed()) {
+ launcherRotation = displayRotation;
+ } else {
+ launcherRotation = ROTATION_0;
+ }
+
+ mOrientationState.update(touchRotation, displayRotation, launcherRotation);
+ mLayoutValid = false;
+ }
+
+ /**
+ * @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
+ */
+ public float getFullScreenScale() {
+ if (mDp == null) {
+ return 1;
+ }
+ mSizeProvider.calculateTaskSize(mContext, mDp, mTaskRect);
+ return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot);
+ }
+
+ /**
+ * Sets the targets which the simulator will control
+ */
+ public void setPreview(
+ RemoteAnimationTargetCompat runningTarget, RecentsAnimationTargets allTargets) {
+ mRunningTarget = runningTarget;
+ mAllTargets = allTargets;
+
+ mThumbnailData.insets.set(mRunningTarget.contentInsets);
+ // TODO: What is this?
+ mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
+
+ mThumbnailPosition.set(runningTarget.screenSpaceBounds);
+ // TODO: Should sourceContainerBounds already have this offset?
+ mThumbnailPosition.offsetTo(mRunningTarget.position.x, mRunningTarget.position.y);
+
+ mLayoutValid = false;
+ }
+
+ /**
+ * Updates the scroll for RecentsView
+ */
+ public void setScroll(int scroll) {
+ if (mScrollState.scroll != scroll) {
+ mScrollState.scroll = scroll;
+ mScrollValid = false;
+ }
+ }
+
+ /**
+ * Sets an alternate function which can be used to control the alpha
+ */
+ public void setTaskAlphaCallback(TargetAlphaProvider callback) {
+ mTaskAlphaCallback = callback;
+ }
+
+ /**
+ * Applies the target to the previously set parameters
+ */
+ public void apply(TransformParams params) {
+ if (mDp == null || mRunningTarget == null) {
+ return;
+ }
+ if (!mLayoutValid) {
+ mLayoutValid = true;
+
+ getFullScreenScale();
+ mThumbnailData.rotation = isFixedRotationTransformEnabled(mContext)
+ ? mOrientationState.getDisplayRotation() : mPositionHelper.getCurrentRotation();
+
+ mPositionHelper.updateThumbnailMatrix(mThumbnailPosition, mThumbnailData,
+ mDp.isMultiWindowMode, mTaskRect.width(), mTaskRect.height());
+
+ mPositionHelper.getMatrix().invert(mInversePositionMatrix);
+
+ PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
+ mScrollState.halfPageSize =
+ poh.getPrimaryValue(mTaskRect.width(), mTaskRect.height()) / 2;
+ mScrollState.halfScreenSize = poh.getPrimaryValue(mDp.widthPx, mDp.heightPx) / 2;
+ mScrollValid = false;
+ }
+
+ if (!mScrollValid) {
+ mScrollValid = true;
+ int start = mOrientationState.getOrientationHandler()
+ .getPrimaryValue(mTaskRect.left, mTaskRect.top);
+ mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
+ mScrollState.updateInterpolation(start, mPageSpacing);
+ mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
+ }
+
+ float progress = Utilities.boundToRange(fullScreenProgress.value, 0, 1);
+ mCurrentFullscreenParams.setProgress(
+ progress, recentsViewScale.value, mTaskRect.width(), mDp, mPositionHelper);
+
+ // Apply thumbnail matrix
+ RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
+ float scale = mCurrentFullscreenParams.mScale;
+ float taskWidth = mTaskRect.width();
+ float taskHeight = mTaskRect.height();
+
+ mMatrix.set(mPositionHelper.getMatrix());
+ mMatrix.postScale(scale, scale);
+ mMatrix.postTranslate(insets.left, insets.top);
+
+ // Apply TaskView matrix: scale, translate, scroll
+ mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
+ mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+ mOrientationState.getOrientationHandler().set(
+ mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
+
+ // Apply recensView matrix
+ mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
+ postDisplayRotation(deltaRotation(
+ mOrientationState.getLauncherRotation(), mOrientationState.getDisplayRotation()),
+ mDp.widthPx, mDp.heightPx, mMatrix);
+
+ // Crop rect is the inverse of thumbnail matrix
+ mTempRectF.set(-insets.left, -insets.top,
+ taskWidth + insets.right, taskHeight + insets.bottom);
+ mInversePositionMatrix.mapRect(mTempRectF);
+ mTempRectF.roundOut(mTmpCropRect);
+
+ SurfaceParams[] surfaceParams = new SurfaceParams[mAllTargets.unfilteredApps.length];
+ for (int i = 0; i < mAllTargets.unfilteredApps.length; i++) {
+ RemoteAnimationTargetCompat app = mAllTargets.unfilteredApps[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash)
+ .withLayer(RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers));
+
+ if (app.mode == mAllTargets.targetMode) {
+ float alpha = mTaskAlphaCallback.getAlpha(app, params.getTargetAlpha());
+ if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ // Fade out Assistant overlay.
+ if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
+ && app.isNotInRecents) {
+ alpha = Interpolators.ACCEL_2.getInterpolation(fullScreenProgress.value);
+ }
+
+ builder.withAlpha(alpha)
+ .withMatrix(mMatrix)
+ .withWindowCrop(mTmpCropRect)
+ .withCornerRadius(mCurrentFullscreenParams.mCurrentDrawnCornerRadius);
+ } else if (params.getTargetSet().hasRecents) {
+ // If home has a different target then recents, reverse anim the home target.
+ builder.withAlpha(fullScreenProgress.value * params.getTargetAlpha());
+ }
+ } else {
+ builder.withAlpha(1);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.isLauncherOnTop()) {
+ builder.withLayer(Integer.MAX_VALUE);
+ }
+ }
+ surfaceParams[i] = builder.build();
+ }
+
+ applySurfaceParams(params.getSyncTransactionApplier(), surfaceParams);
+ }
+
+ /**
+ * Interface for calculating taskSize
+ */
+ public interface TaskSizeProvider {
+
+ /**
+ * Sets the outRect to the expected taskSize
+ */
+ void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect);
+ }
+
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 98eb29a..0b6d340 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -45,9 +45,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.ScrimView;
@@ -88,10 +86,6 @@
}
};
- private RotationHelper.ForcedRotationChangedListener mForcedRotationChangedListener =
- isForcedRotation -> LauncherRecentsView.this
- .disableMultipleLayoutRotations(!isForcedRotation);
-
public LauncherRecentsView(Context context) {
this(context, null);
}
@@ -101,12 +95,17 @@
}
public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setContentAlpha(0);
+ super(context, attrs, defStyleAttr, true);
mActivity.getStateManager().addStateListener(this);
}
@Override
+ public void init(OverviewActionsView actionsView) {
+ super.init(actionsView);
+ setContentAlpha(0);
+ }
+
+ @Override
public void startHome() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
switchToScreenshot(null,
@@ -183,27 +182,6 @@
LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
}
- /**
- * @return The translationX to apply to this view so that the first task is just offscreen.
- */
- public float getOffscreenTranslationX(float recentsScale) {
- LauncherState.ScaleAndTranslation overviewScaleAndTranslation =
- NORMAL.getOverviewScaleAndTranslation(mActivity);
- float offscreen = mOrientationHandler.getTranslationValue(overviewScaleAndTranslation);
- // Offset since scale pushes tasks outwards.
- getTaskSize(sTempRect);
- int taskSize = mOrientationHandler.getPrimarySize(sTempRect);
- offscreen += taskSize * (recentsScale - 1) / 2;
- if (mRunningTaskTileHidden) {
- // The first task is hidden, so offset by its width.
- offscreen -= (taskSize + getPageSpacing()) * recentsScale;
- }
- if (isRtl()) {
- offscreen = -offscreen;
- }
- return offscreen;
- }
-
@Override
protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -286,12 +264,6 @@
}
@Override
- protected boolean supportsVerticalLandscape() {
- return FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()
- && !mOrientationState.areMultipleLayoutOrientationsDisabled();
- }
-
- @Override
public void reset() {
super.reset();
@@ -344,7 +316,6 @@
super.onAttachedToWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(
mRecentsExtraCardPluginListener, RecentsExtraCard.class);
- mActivity.getRotationHelper().addForcedRotationCallback(mForcedRotationChangedListener);
}
@Override
@@ -352,7 +323,6 @@
super.onDetachedFromWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(
mRecentsExtraCardPluginListener);
- mActivity.getRotationHelper().removeForcedRotationCallback(mForcedRotationChangedListener);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index 6a37e2b..d160686 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,34 +16,61 @@
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
import android.content.Context;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.widget.FrameLayout;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* View for showing action buttons in Overview
*/
-public class OverviewActionsView extends FrameLayout {
+public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
+ implements OnClickListener {
- private final View mScreenshotButton;
- private final View mShareButton;
+ @IntDef(flag = true, value = {
+ HIDDEN_UNSUPPORTED_NAVIGATION,
+ HIDDEN_DISABLED_FEATURE,
+ HIDDEN_NON_ZERO_ROTATION,
+ HIDDEN_NO_TASKS,
+ HIDDEN_GESTURE_RUNNING,
+ HIDDEN_NO_RECENTS,
+ HIDDEN_FULLESCREEN_PROGRESS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActionsHiddenFlags { }
- /**
- * Listener for taps on the various actions.
- */
- public interface Listener {
- /** User has initiated the share actions. */
- void onShare();
+ public static final int HIDDEN_UNSUPPORTED_NAVIGATION = 1 << 0;
+ public static final int HIDDEN_DISABLED_FEATURE = 1 << 1;
+ public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 2;
+ public static final int HIDDEN_NO_TASKS = 1 << 3;
+ public static final int HIDDEN_GESTURE_RUNNING = 1 << 4;
+ public static final int HIDDEN_NO_RECENTS = 1 << 5;
+ public static final int HIDDEN_FULLESCREEN_PROGRESS = 1 << 6;
- /** User has initiated the screenshot action. */
- void onScreenshot();
- }
+ private static final int INDEX_CONTENT_ALPHA = 0;
+ private static final int INDEX_VISIBILITY_ALPHA = 1;
+ private static final int INDEX_HIDDEN_FLAGS_ALPHA = 2;
+
+ private final MultiValueAlpha mMultiValueAlpha;
+
+ @ActionsHiddenFlags
+ private int mHiddenFlags;
+
+ protected T mCallbacks;
public OverviewActionsView(Context context) {
this(context, null);
@@ -54,26 +81,62 @@
}
public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ super(context, attrs, defStyleAttr, 0);
+ mMultiValueAlpha = new MultiValueAlpha(this, 3);
}
- public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true);
- mShareButton = findViewById(R.id.action_share);
- mScreenshotButton = findViewById(R.id.action_screenshot);
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ findViewById(R.id.action_share).setOnClickListener(this);
+ findViewById(R.id.action_screenshot).setOnClickListener(this);
}
/**
* Set listener for callbacks on action button taps.
*
- * @param listener for callbacks, or {@code null} to clear the listener.
+ * @param callbacks for callbacks, or {@code null} to clear the listener.
*/
- public void setListener(@Nullable OverviewActionsView.Listener listener) {
- mShareButton.setOnClickListener(
- listener == null ? null : view -> listener.onShare());
- mScreenshotButton.setOnClickListener(
- listener == null ? null : view -> listener.onScreenshot());
+ public void setCallbacks(T callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mCallbacks == null) {
+ return;
+ }
+ int id = view.getId();
+ if (id == R.id.action_share) {
+ mCallbacks.onShare();
+ } else if (id == R.id.action_screenshot) {
+ mCallbacks.onScreenshot();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateHiddenFlags(HIDDEN_DISABLED_FEATURE, !ENABLE_OVERVIEW_ACTIONS.get());
+ updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
+ }
+
+ public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
+ if (enable) {
+ mHiddenFlags |= visibilityFlags;
+ } else {
+ mHiddenFlags &= ~visibilityFlags;
+ }
+ boolean isHidden = mHiddenFlags != 0;
+ mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
+ setVisibility(isHidden ? INVISIBLE : VISIBLE);
+ }
+
+ public AlphaProperty getContentAlpha() {
+ return mMultiValueAlpha.getProperty(INDEX_CONTENT_ALPHA);
+ }
+
+ public AlphaProperty getVisibilityAlpha() {
+ return mMultiValueAlpha.getProperty(INDEX_VISIBILITY_ALPHA);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index a18f7ba..3b6fd13 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -28,7 +28,6 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_DISMISS_SWIPE_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_LAUNCH_SWIPE_DOWN;
@@ -39,6 +38,11 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_FULLESCREEN_PROGRESS;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -50,8 +54,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
@@ -66,12 +72,10 @@
import android.util.FloatProperty;
import android.util.Property;
import android.util.SparseBooleanArray;
-import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.OrientationEventListener;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -85,9 +89,7 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
-import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -100,7 +102,6 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -115,19 +116,18 @@
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
-import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ConfigurationCompat;
import com.android.systemui.shared.system.LauncherEventUtil;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -172,10 +172,23 @@
}
};
- protected final RecentsOrientedState mOrientationState = new RecentsOrientedState();
+ public static final FloatProperty<RecentsView> ADJACENT_PAGE_OFFSET =
+ new FloatProperty<RecentsView>("adjacentPageOffset") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ if (recentsView.mAdjacentPageOffset != v) {
+ recentsView.mAdjacentPageOffset = v;
+ recentsView.updateAdjacentPageOffset();
+ }
+ }
- private OrientationEventListener mOrientationListener;
- private int mPreviousRotation;
+ @Override
+ public Float get(RecentsView recentsView) {
+ return recentsView.mAdjacentPageOffset;
+ }
+ };
+
+ protected final RecentsOrientedState mOrientationState;
protected RecentsAnimationController mRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected AppWindowAnimationHelper mAppWindowAnimationHelper;
@@ -185,6 +198,7 @@
protected boolean mEnableDrawingLiveTile = false;
protected final Rect mTempRect = new Rect();
protected final RectF mTempRectF = new RectF();
+ private final PointF mTempPointF = new PointF();
private static final int DISMISS_TASK_DURATION = 300;
private static final int ADDITION_TASK_DURATION = 200;
@@ -195,7 +209,6 @@
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final int mTaskTopMargin;
- private final int mTaskBottomMargin;
private final ClearAllButton mClearAllButton;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
@@ -214,6 +227,8 @@
private boolean mOverlayEnabled;
protected boolean mFreezeViewVisibility;
+ private float mAdjacentPageOffset = 0;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -328,8 +343,7 @@
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
- private View mActionsView;
- private boolean mGestureRunning = false;
+ private OverviewActionsView mActionsView;
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
@@ -339,14 +353,17 @@
}
};
- public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
+ boolean rotationSupportedByActivity) {
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
setEnableFreeScroll(true);
+ mOrientationState = new RecentsOrientedState(
+ context, rotationSupportedByActivity, this::animateRecentsRotationInPlace);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
- mActivity = (T) BaseActivity.fromContext(context);
+ mActivity = BaseActivity.fromContext(context);
mModel = RecentsModel.INSTANCE.get(context);
mIdp = InvariantDeviceProfile.INSTANCE.get(context);
mTempAppWindowAnimationHelper =
@@ -362,7 +379,6 @@
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(context);
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -378,26 +394,10 @@
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
- disableMultipleLayoutRotations(!supportsVerticalLandscape());
+ mOrientationHandler = mOrientationState.getOrientationHandler();
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
-
- mOrientationListener = new OrientationEventListener(getContext()) {
- @Override
- public void onOrientationChanged(int i) {
- int rotation = RecentsOrientedState.getRotationForUserDegreesRotated(i);
- if (mPreviousRotation != rotation) {
- animateRecentsRotationInPlace(rotation);
- if (rotation == 0) {
- showActionsView();
- } else {
- hideActionsView();
- }
- mPreviousRotation = rotation;
- }
- }
- };
}
public OverScroller getScroller() {
@@ -468,6 +468,10 @@
reset();
}
+ public void init(OverviewActionsView actionsView) {
+ mActionsView = actionsView;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -481,7 +485,7 @@
mIPinnedStackAnimationListener.setActivity(mActivity);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
mIPinnedStackAnimationListener);
- setActionsView();
+ mOrientationState.init();
}
@Override
@@ -496,6 +500,7 @@
mIdp.removeOnChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
mIPinnedStackAnimationListener.setActivity(null);
+ mOrientationState.destroy();
}
@Override
@@ -507,6 +512,7 @@
TaskView taskView = (TaskView) child;
mHasVisibleTaskData.delete(taskView.getTask().key.id);
mTaskViewPool.recycle(taskView);
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
updateTaskStartIndex(child);
}
@@ -519,6 +525,7 @@
// child direction back to match system settings.
child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
updateTaskStartIndex(child);
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
}
private void updateTaskStartIndex(View affectingView) {
@@ -549,17 +556,9 @@
}
public void setOverviewStateEnabled(boolean enabled) {
- if (supportsVerticalLandscape()
- && !TestProtocol.sDisableSensorRotation // Ignore hardware dependency for tests
- && mOrientationListener.canDetectOrientation()) {
- if (enabled) {
- mOrientationListener.enable();
- } else {
- mOrientationListener.disable();
- }
- }
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
+ mOrientationState.setRotationWatcherEnabled(enabled);
if (!enabled) {
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
@@ -585,6 +584,15 @@
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ int windowConfigurationRotation = ConfigurationCompat
+ .getWindowConfigurationRotation(getResources().getConfiguration());
+ setLayoutInternal(mOrientationState.getTouchRotation(),
+ mOrientationState.getDisplayRotation(), windowConfigurationRotation);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
final int x = (int) ev.getX();
@@ -668,7 +676,6 @@
if (getTaskViewCount() != requiredTaskCount) {
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
- hideActionsView();
}
for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
addView(mTaskViewPool.getView());
@@ -678,7 +685,6 @@
}
if (requiredTaskCount > 0) {
addView(mClearAllButton);
- showActionsView();
}
}
@@ -718,7 +724,6 @@
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
}
- hideActionsView();
}
public int getTaskViewCount() {
@@ -778,9 +783,7 @@
for (int i = 0; i < taskCount; i++) {
getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
}
- if (mActionsView != null) {
- mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE);
- }
+ mActionsView.updateHiddenFlags(HIDDEN_FULLESCREEN_PROGRESS, fullscreenProgress > 0);
}
private void updateTaskStackListenerState() {
@@ -803,7 +806,6 @@
mTaskHeight = mTempRect.height();
mTempRect.top -= mTaskTopMargin;
- mTempRect.bottom += mTaskBottomMargin;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
@@ -980,7 +982,7 @@
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
setRunningTaskIconScaledDown(true);
- mGestureRunning = true;
+ mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, true);
}
/**
@@ -998,10 +1000,6 @@
}
private void animateRecentsRotationInPlace(int newRotation) {
- if (!supportsVerticalLandscape()) {
- return;
- }
-
AnimatorSet pa = setRecentsChangedOrientation(true);
pa.addListener(AnimationSuccessListener.forRunnable(() -> {
setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
@@ -1026,7 +1024,6 @@
return as;
}
- abstract protected boolean supportsVerticalLandscape();
private void rotateAllChildTasks() {
for (int i = 0; i < getTaskViewCount(); i++) {
@@ -1046,7 +1043,7 @@
}
setRunningTaskHidden(false);
animateUpRunningTaskIconScale();
- mGestureRunning = false;
+ mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, false);
}
/**
@@ -1057,13 +1054,12 @@
*/
public void showCurrentTask(int runningTaskId) {
if (getTaskView(runningTaskId) == null) {
- boolean wasEmpty = getTaskViewCount() == 0;
+ boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView = mTaskViewPool.getView();
addView(taskView, mTaskViewStartIndex);
if (wasEmpty) {
addView(mClearAllButton);
- showActionsView();
}
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
@@ -1387,7 +1383,6 @@
if (getTaskViewCount() == 0) {
removeViewInLayout(mClearAllButton);
- hideActionsView();
startHome();
} else {
snapToPageImmediately(pageToSnapTo);
@@ -1530,14 +1525,12 @@
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
+ mActionsView.getContentAlpha().setValue(mContentAlpha);
+
if (alpha > 0) {
setVisibility(VISIBLE);
- if (!mGestureRunning) {
- showActionsView();
- }
} else if (!mFreezeViewVisibility) {
setVisibility(GONE);
- hideActionsView();
}
}
@@ -1548,34 +1541,38 @@
public void setFreezeViewVisibility(boolean freezeViewVisibility) {
if (mFreezeViewVisibility != freezeViewVisibility) {
mFreezeViewVisibility = freezeViewVisibility;
-
if (!mFreezeViewVisibility) {
setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
- if (mContentAlpha > 0) {
- showActionsView();
- } else {
- hideActionsView();
- }
}
}
}
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (mActionsView != null) {
+ mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
+ }
+ }
+
public void setLayoutRotation(int touchRotation, int displayRotation) {
- if (mOrientationState.update(touchRotation, displayRotation)) {
+ int launcherRotation = mOrientationState.getLauncherRotation();
+ setLayoutInternal(touchRotation, displayRotation, launcherRotation);
+ }
+
+ private void setLayoutInternal(int touchRotation, int displayRotation, int launcherRotation) {
+ if (mOrientationState.update(touchRotation, displayRotation, launcherRotation)) {
mOrientationHandler = mOrientationState.getOrientationHandler();
mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
+ mActivity.getDragLayer().recreateControllers();
+ mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
+ touchRotation != 0 || launcherRotation != 0);
requestLayout();
}
}
- public void disableMultipleLayoutRotations(boolean disable) {
- mOrientationState.disableMultipleOrientations(disable);
- mOrientationHandler = mOrientationState.getOrientationHandler();
- requestLayout();
- }
-
public RecentsOrientedState getPagedViewOrientedState() {
return mOrientationState;
}
@@ -1590,11 +1587,6 @@
}
@Nullable
- public TaskView getPreviousTaskView() {
- return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() - 1);
- }
-
- @Nullable
public TaskView getCurrentPageTaskView() {
return getTaskViewAtByAbsoluteIndex(getCurrentPage());
}
@@ -1653,11 +1645,36 @@
updateEmptyStateUi(changed);
- // Set the pivot points to match the task preview center
- setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin)
- + (getHeight() - mInsets.bottom - getPaddingBottom() - mTaskBottomMargin)) / 2);
- setPivotX(((mInsets.left + getPaddingLeft())
- + (getWidth() - mInsets.right - getPaddingRight())) / 2);
+ // Update the pivots such that when the task is scaled, it fills the full page
+ getTaskSize(mTempRect);
+ getPagedViewOrientedState().getFullScreenScaleAndPivot(
+ mTempRect, mActivity.getDeviceProfile(), mTempPointF);
+ setPivotX(mTempPointF.x);
+ setPivotY(mTempPointF.y);
+ updateAdjacentPageOffset();
+ }
+
+ private void updateAdjacentPageOffset() {
+ float offset = mAdjacentPageOffset * getWidth();
+ if (mIsRtl) {
+ offset = -offset;
+ }
+ int count = getChildCount();
+
+ TaskView runningTask = mRunningTaskId == -1 ? null : getTaskView(mRunningTaskId);
+ int midPoint = runningTask == null ? -1 : indexOfChild(runningTask);
+
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setTranslationX(i == midPoint ? 0 : (i < midPoint ? -offset : offset));
+ }
+ updateCurveProperties();
+ }
+
+ /**
+ * TODO: Do not assume motion across X axis for adjacent page
+ */
+ public float getPageOffsetScale() {
+ return Math.max(getWidth(), 1);
}
private void updateDeadZoneRects() {
@@ -1739,14 +1756,10 @@
int centerTaskIndex = getCurrentPage();
boolean launchingCenterTask = taskIndex == centerTaskIndex;
- LauncherState.ScaleAndTranslation toScaleAndTranslation = appWindowAnimationHelper
- .getScaleAndTranslation();
- float toScale = toScaleAndTranslation.scale;
- float toTranslationY = toScaleAndTranslation.translationY;
+ float toScale = appWindowAnimationHelper.getSrcToTargetScale();
if (launchingCenterTask) {
RecentsView recentsView = tv.getRecentsView();
anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
- anim.play(ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, toTranslationY));
anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
} else {
// We are launching an adjacent task, so parallax the center and other adjacent task.
@@ -2006,23 +2019,29 @@
return mClearAllButton;
}
+
/**
* @return How many pixels the running task is offset on the x-axis due to the current scrollX.
*/
- public float getScrollOffset() {
+ public int getScrollOffset() {
if (getRunningTaskIndex() == -1) {
return 0;
}
- int startScroll = getScrollForPage(getRunningTaskIndex());
- int offsetX = startScroll - mOrientationHandler.getPrimaryScroll(this);
- offsetX *= mOrientationHandler.getPrimaryScale(this);
- return offsetX;
+ return getScrollForPage(getRunningTaskIndex()) - mOrientationHandler.getPrimaryScroll(this);
+ }
+
+ /**
+ * @return How many pixels the running task is offset on the x-axis due to the current scrollX
+ * and parent scale.
+ */
+ public float getScrollOffsetScaled() {
+ return getScrollOffset() * mOrientationHandler.getPrimaryScale(this);
}
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
float degreesRotated;
if (navbarRotation == 0) {
- degreesRotated = mOrientationState.getTouchRotationDegrees();
+ degreesRotated = mOrientationHandler.getDegreesRotated();
} else {
degreesRotated = -navbarRotation;
}
@@ -2035,7 +2054,8 @@
// PagedOrientationHandler
return e -> {
if (navbarRotation != 0
- && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
+ && mOrientationState.isMultipleOrientationSupportedByDevice()
+ && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) {
mOrientationState.flipVertical(e);
super.onTouchEvent(e);
mOrientationState.flipVertical(e);
@@ -2122,36 +2142,4 @@
mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
}
-
- private void showActionsView() {
- if (mActionsView != null && getTaskViewCount() > 0) {
- mActionsView.setVisibility(VISIBLE);
- }
- }
-
- private void hideActionsView() {
- if (mActionsView != null) {
- mActionsView.setVisibility(GONE);
- }
- }
-
- private void setActionsView() {
- if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
- && SysUINavigationMode.removeShelfFromOverview(mActivity)) {
- mActionsView = ((ViewGroup) getParent()).findViewById(R.id.overview_actions_view);
- if (mActionsView != null) {
- InsettableFrameLayout.LayoutParams layoutParams =
- new InsettableFrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
- getResources().getDimensionPixelSize(
- R.dimen.overview_actions_height));
- layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
- int margin = getResources().getDimensionPixelSize(
- R.dimen.overview_actions_horizontal_margin);
- layoutParams.setMarginStart(margin);
- layoutParams.setMarginEnd(margin);
- mActionsView.setLayoutParams(layoutParams);
- showActionsView();
- }
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 80022b4..9b47520 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -40,6 +40,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
@@ -150,9 +151,26 @@
return (type & TYPE_TASK_MENU) != 0;
}
- public void setPosition(float x, float y) {
- setX(x);
- setY(y + mThumbnailTopMargin);
+ public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
+ float adjustedY = y + mThumbnailTopMargin;
+ // Changing pivot to make computations easier
+ // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
+ // which would render the X and Y position set here incorrect
+ setPivotX(0);
+ setPivotY(0);
+ setRotation(pagedOrientationHandler.getDegreesRotated());
+ setX(pagedOrientationHandler.getTaskMenuX(x, mTaskView.getThumbnail()));
+ setY(pagedOrientationHandler.getTaskMenuY(adjustedY, mTaskView.getThumbnail()));
+ }
+
+ public void onRotationChanged() {
+ if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
+ mOpenCloseAnimator.end();
+ }
+ if (mIsOpen) {
+ mOptionLayout.removeAllViews();
+ populateAndLayoutMenu();
+ }
}
public static TaskMenuView showForTask(TaskView taskView) {
@@ -168,12 +186,16 @@
}
mActivity.getDragLayer().addView(this);
mTaskView = taskView;
- addMenuOptions(mTaskView);
- orientAroundTaskView(mTaskView);
+ populateAndLayoutMenu();
post(this::animateOpen);
return true;
}
+ private void populateAndLayoutMenu() {
+ addMenuOptions(mTaskView);
+ orientAroundTaskView(mTaskView);
+ }
+
private void addMenuOptions(TaskView taskView) {
Drawable icon = taskView.getTask().icon.getConstantState().newDrawable();
mTaskIcon.setDrawable(icon);
@@ -200,21 +222,26 @@
R.layout.task_view_menu_option, this, false);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
+ LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
+ mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
menuOptionView.setOnClickListener(menuOption);
mOptionLayout.addView(menuOptionView);
}
private void orientAroundTaskView(TaskView taskView) {
+ PagedOrientationHandler orientationHandler = taskView.getPagedOrientationHandler();
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
Rect insets = mActivity.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
- params.width = taskView.getMeasuredWidth();
+ params.width = orientationHandler.getTaskMenuWidth(taskView.getThumbnail());
params.gravity = Gravity.START;
setLayoutParams(params);
setScaleX(taskView.getScaleX());
setScaleY(taskView.getScaleY());
- setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top);
+ mOptionLayout.setOrientation(orientationHandler.getTaskMenuLayoutOrientation());
+ setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
+ taskView.getPagedOrientationHandler());
}
private void animateOpen() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 178ff32..a05e0fa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -36,11 +36,9 @@
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.FloatProperty;
-import android.util.Log;
import android.util.Property;
import android.view.Surface;
import android.view.View;
-import android.view.ViewGroup;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.R;
@@ -50,7 +48,7 @@
import com.android.launcher3.util.Themes;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
-import com.android.quickstep.util.TaskCornerRadius;
+import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.plugins.OverviewScreenshotActions;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.model.Task;
@@ -66,6 +64,8 @@
private static final ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
private static final RectF EMPTY_RECT_F = new RectF();
+ private static final FullscreenDrawParams TEMP_PARAMS = new FullscreenDrawParams();
+
public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
new FloatProperty<TaskThumbnailView>("dimAlpha") {
@Override
@@ -87,12 +87,11 @@
private final Paint mClearPaint = new Paint();
private final Paint mDimmingPaintAfterClearing = new Paint();
- private final Matrix mMatrix = new Matrix();
-
- private float mClipBottom = -1;
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
- private RectF mClippedInsets = new RectF();
- private TaskView.FullscreenDrawParams mFullscreenParams;
+ private final Rect mPreviewRect = new Rect();
+ private final PreviewPositionHelper mPreviewPositionHelper;
+ // Initialize with dummy value. It is overridden later by TaskView
+ private TaskView.FullscreenDrawParams mFullscreenParams = TEMP_PARAMS;
private Task mTask;
private ThumbnailData mThumbnailData;
@@ -103,7 +102,6 @@
private float mSaturation = 1f;
private boolean mOverlayEnabled;
- private boolean mIsOrientationChanged;
private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
public TaskThumbnailView(Context context) {
@@ -123,7 +121,7 @@
mDimmingPaintAfterClearing.setColor(Color.BLACK);
mActivity = BaseActivity.fromContext(context);
mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
- mFullscreenParams = new TaskView.FullscreenDrawParams(TaskCornerRadius.get(context));
+ mPreviewPositionHelper = new PreviewPositionHelper(context);
}
public void bind(Task task) {
@@ -172,8 +170,7 @@
mOverlay.reset();
}
if (mOverviewScreenshotActionsPlugin != null) {
- mOverviewScreenshotActionsPlugin
- .setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
+ mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
}
updateThumbnailPaintFilter();
}
@@ -270,9 +267,8 @@
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
}
- public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
- // Don't show insets in multi window mode.
- return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
+ public PreviewPositionHelper getPreviewPositionHelper() {
+ return mPreviewPositionHelper;
}
public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
@@ -294,16 +290,17 @@
// Draw the background in all cases, except when the thumbnail data is opaque
final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
|| mThumbnailData == null;
- if (drawBackgroundOnly || mClipBottom > 0 || mThumbnailData.isTranslucent) {
+ if (drawBackgroundOnly || mPreviewPositionHelper.mClipBottom > 0
+ || mThumbnailData.isTranslucent) {
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
if (drawBackgroundOnly) {
return;
}
}
- if (mClipBottom > 0) {
+ if (mPreviewPositionHelper.mClipBottom > 0) {
canvas.save();
- canvas.clipRect(x, y, width, mClipBottom);
+ canvas.clipRect(x, y, width, mPreviewPositionHelper.mClipBottom);
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
canvas.restore();
} else {
@@ -324,8 +321,9 @@
private void updateOverlay() {
// The overlay doesn't really work when the screenshot is rotated, so don't add it.
- if (mOverlayEnabled && !mIsOrientationChanged && mBitmapShader != null && mThumbnailData != null) {
- mOverlay.initOverlay(mTask, mThumbnailData, mMatrix);
+ if (mOverlayEnabled && !mPreviewPositionHelper.mIsOrientationChanged
+ && mBitmapShader != null && mThumbnailData != null) {
+ mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix);
} else {
mOverlay.reset();
}
@@ -346,76 +344,17 @@
}
private void updateThumbnailMatrix() {
- boolean isRotated = false;
- boolean isOrientationDifferent = false;
- mClipBottom = -1;
+ mPreviewPositionHelper.mClipBottom = -1;
+ mPreviewPositionHelper.mIsOrientationChanged = false;
if (mBitmapShader != null && mThumbnailData != null) {
- float scale = mThumbnailData.scale;
- Rect thumbnailInsets = mThumbnailData.insets;
- final float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
- (thumbnailInsets.left + thumbnailInsets.right) * scale;
- final float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
- (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
+ mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
+ mThumbnailData.thumbnail.getHeight());
+ mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
+ mActivity.isInMultiWindowMode(), getMeasuredWidth(), getMeasuredHeight());
- final float thumbnailScale;
- int thumbnailRotation = mThumbnailData.rotation;
- int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
- getResources().getConfiguration());
- int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
- // Landscape vs portrait change
- boolean windowingModeSupportsRotation = !mActivity.isInMultiWindowMode()
- && mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
- isOrientationDifferent = isOrientationChange(deltaRotate)
- && windowingModeSupportsRotation;
- if (getMeasuredWidth() == 0) {
- // If we haven't measured , skip the thumbnail drawing and only draw the background
- // color
- thumbnailScale = 0f;
- } else {
- // Rotate the screenshot if not in multi-window mode
- isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
- // Scale the screenshot to always fit the width of the card.
-
- thumbnailScale = isOrientationDifferent
- ? getMeasuredWidth() / thumbnailHeight
- : getMeasuredWidth() / thumbnailWidth;
- }
-
- if (!isRotated) {
- // No Rotation
- mClippedInsets.offsetTo(thumbnailInsets.left * scale,
- thumbnailInsets.top * scale);
- mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top);
- } else {
- setThumbnailRotation(deltaRotate, thumbnailInsets, scale);
- }
-
- final float widthWithInsets;
- final float heightWithInsets;
- if (isOrientationDifferent) {
- widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
- heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
- } else {
- widthWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
- heightWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
- }
- mClippedInsets.left *= thumbnailScale;
- mClippedInsets.top *= thumbnailScale;
- mClippedInsets.right = widthWithInsets - mClippedInsets.left - getMeasuredWidth();
- mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - getMeasuredHeight();
-
- mMatrix.postScale(thumbnailScale, thumbnailScale);
- mBitmapShader.setLocalMatrix(mMatrix);
-
- float bitmapHeight = Math.max((isOrientationDifferent ? thumbnailWidth : thumbnailHeight)
- * thumbnailScale, 0);
- if (Math.round(bitmapHeight) < getMeasuredHeight()) {
- mClipBottom = bitmapHeight;
- }
+ mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
mPaint.setShader(mBitmapShader);
}
-
- mIsOrientationChanged = isOrientationDifferent;
invalidate();
// Update can be called from {@link #onSizeChanged} during layout, post handling of overlay
@@ -423,51 +362,6 @@
post(this::updateOverlay);
}
- private int getRotationDelta(int oldRotation, int newRotation) {
- int delta = newRotation - oldRotation;
- if (delta < 0) delta += 4;
- return delta;
- }
-
- /**
- * @param deltaRotation the number of 90 degree turns from the current orientation
- * @return {@code true} if the change in rotation results in a shift from landscape to portrait
- * or vice versa, {@code false} otherwise
- */
- private boolean isOrientationChange(int deltaRotation) {
- return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
- }
-
- private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale) {
- int newLeftInset = 0;
- int newTopInset = 0;
- int translateX = 0;
- int translateY = 0;
-
- mMatrix.setRotate(90 * deltaRotate);
- switch (deltaRotate) { /* Counter-clockwise */
- case Surface.ROTATION_90:
- newLeftInset = thumbnailInsets.bottom;
- newTopInset = thumbnailInsets.left;
- translateX = mThumbnailData.thumbnail.getHeight();
- break;
- case Surface.ROTATION_270:
- newLeftInset = thumbnailInsets.top;
- newTopInset = thumbnailInsets.right;
- translateY = mThumbnailData.thumbnail.getWidth();
- break;
- case Surface.ROTATION_180:
- newLeftInset = -thumbnailInsets.top;
- newTopInset = -thumbnailInsets.left;
- translateX = mThumbnailData.thumbnail.getWidth();
- translateY = mThumbnailData.thumbnail.getHeight();
- break;
- }
- mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
- mMatrix.postTranslate(translateX - mClippedInsets.left,
- translateY - mClippedInsets.top);
- }
-
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -511,4 +405,158 @@
}
return mThumbnailData.thumbnail;
}
+
+ /**
+ * Utility class to position the thumbnail in the TaskView
+ */
+ public static class PreviewPositionHelper {
+
+ // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
+ private final RectF mClippedInsets = new RectF();
+ private final Matrix mMatrix = new Matrix();
+ private float mClipBottom = -1;
+ private boolean mIsOrientationChanged;
+
+ private final Context mContext;
+
+ public PreviewPositionHelper(Context context) {
+ mContext = context;
+ }
+
+ public int getCurrentRotation() {
+ return ConfigurationCompat.getWindowConfigurationRotation(
+ mContext.getResources().getConfiguration());
+ }
+
+ public Matrix getMatrix() {
+ return mMatrix;
+ }
+
+ /**
+ * Updates the matrix based on the provided parameters
+ */
+ public void updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData,
+ boolean isInMultiWindowMode, int canvasWidth, int canvasHeight) {
+ boolean isRotated = false;
+ boolean isOrientationDifferent;
+ mClipBottom = -1;
+
+ float scale = thumbnailData.scale;
+ Rect thumbnailInsets = thumbnailData.insets;
+ final float thumbnailWidth = thumbnailPosition.width()
+ - (thumbnailInsets.left + thumbnailInsets.right) * scale;
+ final float thumbnailHeight = thumbnailPosition.height()
+ - (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
+
+ final float thumbnailScale;
+ int thumbnailRotation = thumbnailData.rotation;
+ int currentRotation = getCurrentRotation();
+ int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+
+ // Landscape vs portrait change
+ boolean windowingModeSupportsRotation = !isInMultiWindowMode
+ && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
+ isOrientationDifferent = isOrientationChange(deltaRotate)
+ && windowingModeSupportsRotation;
+ if (canvasWidth == 0) {
+ // If we haven't measured , skip the thumbnail drawing and only draw the background
+ // color
+ thumbnailScale = 0f;
+ } else {
+ // Rotate the screenshot if not in multi-window mode
+ isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
+ // Scale the screenshot to always fit the width of the card.
+ thumbnailScale = isOrientationDifferent
+ ? canvasWidth / thumbnailHeight
+ : canvasWidth / thumbnailWidth;
+ }
+
+ if (!isRotated) {
+ // No Rotation
+ mClippedInsets.offsetTo(thumbnailInsets.left * scale,
+ thumbnailInsets.top * scale);
+ mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top);
+ } else {
+ setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
+ }
+ mMatrix.postTranslate(-thumbnailPosition.left, -thumbnailPosition.top);
+
+ final float widthWithInsets;
+ final float heightWithInsets;
+ if (isOrientationDifferent) {
+ widthWithInsets = thumbnailPosition.height() * thumbnailScale;
+ heightWithInsets = thumbnailPosition.width() * thumbnailScale;
+ } else {
+ widthWithInsets = thumbnailPosition.width() * thumbnailScale;
+ heightWithInsets = thumbnailPosition.height() * thumbnailScale;
+ }
+ mClippedInsets.left *= thumbnailScale;
+ mClippedInsets.top *= thumbnailScale;
+ mClippedInsets.right = widthWithInsets - mClippedInsets.left - canvasWidth;
+ mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - canvasHeight;
+
+ mMatrix.postScale(thumbnailScale, thumbnailScale);
+
+ float bitmapHeight = Math.max(0,
+ (isOrientationDifferent ? thumbnailWidth : thumbnailHeight) * thumbnailScale);
+ if (Math.round(bitmapHeight) < canvasHeight) {
+ mClipBottom = bitmapHeight;
+ }
+ mIsOrientationChanged = isOrientationDifferent;
+ }
+
+ private int getRotationDelta(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ /**
+ * @param deltaRotation the number of 90 degree turns from the current orientation
+ * @return {@code true} if the change in rotation results in a shift from landscape to
+ * portrait or vice versa, {@code false} otherwise
+ */
+ private boolean isOrientationChange(int deltaRotation) {
+ return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ }
+
+ private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale,
+ Rect thumbnailPosition) {
+ int newLeftInset = 0;
+ int newTopInset = 0;
+ int translateX = 0;
+ int translateY = 0;
+
+ mMatrix.setRotate(90 * deltaRotate);
+ switch (deltaRotate) { /* Counter-clockwise */
+ case Surface.ROTATION_90:
+ newLeftInset = thumbnailInsets.bottom;
+ newTopInset = thumbnailInsets.left;
+ translateX = thumbnailPosition.height();
+ break;
+ case Surface.ROTATION_270:
+ newLeftInset = thumbnailInsets.top;
+ newTopInset = thumbnailInsets.right;
+ translateY = thumbnailPosition.width();
+ break;
+ case Surface.ROTATION_180:
+ newLeftInset = -thumbnailInsets.top;
+ newTopInset = -thumbnailInsets.left;
+ translateX = thumbnailPosition.width();
+ translateY = thumbnailPosition.height();
+ break;
+ }
+ mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
+ mMatrix.postTranslate(translateX - mClippedInsets.left,
+ translateY - mClippedInsets.top);
+ }
+
+ /**
+ * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
+ */
+ public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
+ // Don't show insets in multi window mode.
+ return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 470b720..3cd2b32 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -56,6 +56,7 @@
import android.widget.Toast;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -77,11 +78,11 @@
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
+import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -157,8 +158,6 @@
private float mCurveScale;
private float mFullscreenProgress;
private final FullscreenDrawParams mCurrentFullscreenParams;
- private final float mCornerRadius;
- private final float mWindowCornerRadius;
private final BaseDraggingActivity mActivity;
private ObjectAnimator mIconAndDimAnimator;
@@ -175,11 +174,12 @@
// Order in which the footers appear. Lower order appear below higher order.
public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
- public static final int INDEX_PROACTIVE_SUGGEST = 1;
private final FooterWrapper[] mFooters = new FooterWrapper[2];
private float mFooterVerticalOffset = 0;
private float mFooterAlpha = 1;
private int mStackHeight;
+ private View mContextualChipWrapper;
+ private View mContextualChip;
public TaskView(Context context) {
this(context, null);
@@ -211,9 +211,8 @@
TaskUtils.getLaunchComponentKeyForTask(getTask().key));
mActivity.getStatsLogManager().log(TASK_LAUNCH_TAP, buildProto());
});
- mCornerRadius = TaskCornerRadius.get(context);
- mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
- mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
+
+ mCurrentFullscreenParams = new FullscreenDrawParams(context);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
@@ -236,11 +235,6 @@
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
mIconView = findViewById(R.id.icon);
- final Context context = getContext();
-
- TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams();
- thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(context);
- mSnapshotView.setLayoutParams(thumbnailParams);
}
public boolean isTaskOverlayModal() {
@@ -262,8 +256,14 @@
footer.animateHide();
}
}
+ if (mContextualChip != null) {
+ mContextualChip.animate().scaleX(0f).scaleY(0f).setDuration(300);
+ }
mIconView.animate().alpha(0.0f);
} else {
+ if (mContextualChip != null) {
+ mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(300);
+ }
mIconView.animate().alpha(1.0f);
}
@@ -474,7 +474,6 @@
PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources());
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
- snapshotParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getContext());
int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
int rotation = orientationState.getTouchRotationDegrees();
@@ -482,7 +481,8 @@
case Surface.ROTATION_90:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
iconParams.rightMargin = -thumbnailPadding;
- iconParams.leftMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+ iconParams.leftMargin = 0;
+ iconParams.topMargin = snapshotParams.topMargin / 2;
break;
case Surface.ROTATION_180:
iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
@@ -492,18 +492,21 @@
case Surface.ROTATION_270:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
iconParams.leftMargin = -thumbnailPadding;
- iconParams.rightMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+ iconParams.rightMargin = 0;
+ iconParams.topMargin = snapshotParams.topMargin / 2;
break;
case Surface.ROTATION_0:
default:
iconParams.gravity = TOP | CENTER_HORIZONTAL;
- iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin =
- iconParams.bottomMargin = 0;
+ iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
break;
}
- mSnapshotView.setLayoutParams(snapshotParams);
mIconView.setLayoutParams(iconParams);
mIconView.setRotation(rotation);
+
+ if (mMenuView != null) {
+ mMenuView.onRotationChanged();
+ }
}
private void setIconAndDimTransitionProgress(float progress, boolean invert) {
@@ -610,7 +613,10 @@
}
if (mMenuView != null) {
- mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY());
+ PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler();
+ RecentsView recentsView = getRecentsView();
+ mMenuView.setPosition(getX() - recentsView.getScrollX(),
+ getY() - recentsView.getScrollY(), pagedOrientationHandler);
mMenuView.setScaleX(getScaleX());
mMenuView.setScaleY(getScaleY());
}
@@ -667,6 +673,51 @@
return oldFooter;
}
+ /**
+ * Sets the contextual chip.
+ *
+ * @param view Wrapper view containing contextual chip.
+ */
+ public void setContextualChip(View view) {
+ if (mContextualChipWrapper != null) {
+ removeView(mContextualChipWrapper);
+ }
+ if (view != null) {
+ mContextualChipWrapper = view;
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+ layoutParams.bottomMargin = (int)
+ (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin
+ - getExpectedViewHeight(view) + getResources().getDimension(
+ R.dimen.chip_hint_vertical_offset));
+ mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
+ mContextualChip.setScaleX(0f);
+ mContextualChip.setScaleY(0f);
+ addView(view, getChildCount(), layoutParams);
+ view.setAlpha(mFooterAlpha);
+ if (mContextualChip != null) {
+ mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
+ }
+ }
+
+ }
+
+ /**
+ * Clears the contextual chip from TaskView.
+ *
+ * @return The contextual chip wrapper view to be recycled.
+ */
+ public View clearContextualChip() {
+ if (mContextualChipWrapper != null) {
+ removeView(mContextualChipWrapper);
+ }
+ View oldContextualChipWrapper = mContextualChipWrapper;
+ mContextualChipWrapper = null;
+ mContextualChip = null;
+ return oldContextualChipWrapper;
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -699,21 +750,16 @@
return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
}
- public void setCurveScale(float curveScale) {
+ private void setCurveScale(float curveScale) {
mCurveScale = curveScale;
- onScaleChanged();
+ setScaleX(mCurveScale);
+ setScaleY(mCurveScale);
}
public float getCurveScale() {
return mCurveScale;
}
- private void onScaleChanged() {
- float scale = mCurveScale;
- setScaleX(scale);
- setScaleY(scale);
- }
-
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
@@ -723,13 +769,11 @@
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;
- private final int mMarginBottom;
private FullscreenDrawParams mFullscreenParams;
TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
mMarginTop = context.getResources().getDimensionPixelSize(
R.dimen.task_thumbnail_top_margin);
- mMarginBottom = LayoutUtils.thumbnailBottomMargin(context);
mFullscreenParams = fullscreenParams;
}
@@ -744,7 +788,7 @@
outline.setRoundRect(0,
(int) (mMarginTop * scale),
(int) ((insets.left + view.getWidth() + insets.right) * scale),
- (int) ((insets.top + view.getHeight() + insets.bottom - mMarginBottom) * scale),
+ (int) ((insets.top + view.getHeight() + insets.bottom) * scale),
mFullscreenParams.mCurrentDrawnCornerRadius);
}
}
@@ -767,14 +811,7 @@
mDelegate = mOldOutlineProvider == null
? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider;
- int h = view.getLayoutParams().height;
- if (h > 0) {
- mExpectedHeight = h;
- } else {
- int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
- view.measure(m, m);
- mExpectedHeight = view.getMeasuredHeight();
- }
+ mExpectedHeight = getExpectedViewHeight(view);
mOldPaddingBottom = view.getPaddingBottom();
if (mOldOutlineProvider != null) {
@@ -836,6 +873,19 @@
}
}
+ private int getExpectedViewHeight(View view) {
+ int expectedHeight;
+ int h = view.getLayoutParams().height;
+ if (h > 0) {
+ expectedHeight = h;
+ } else {
+ int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
+ view.measure(m, m);
+ expectedHeight = view.getMeasuredHeight();
+ }
+ return expectedHeight;
+ }
+
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
@@ -891,6 +941,10 @@
return (RecentsView) getParent();
}
+ PagedOrientationHandler getPagedOrientationHandler() {
+ return getRecentsView().mOrientationState.getOrientationHandler();
+ }
+
public void notifyTaskLaunchFailed(String tag) {
String msg = "Failed to launch task";
if (mTask != null) {
@@ -917,23 +971,11 @@
setClipToPadding(!isFullscreen);
TaskThumbnailView thumbnail = getThumbnail();
- boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode;
- RectF insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
- float currentInsetsLeft = insets.left * mFullscreenProgress;
- float currentInsetsRight = insets.right * mFullscreenProgress;
- mCurrentFullscreenParams.setInsets(currentInsetsLeft,
- insets.top * mFullscreenProgress,
- currentInsetsRight,
- insets.bottom * mFullscreenProgress);
- float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius;
- mCurrentFullscreenParams.setCornerRadius(Utilities.mapRange(mFullscreenProgress,
- mCornerRadius, fullscreenCornerRadius) / getRecentsView().getScaleX());
- // We scaled the thumbnail to fit the content (excluding insets) within task view width.
- // Now that we are drawing left/right insets again, we need to scale down to fit them.
- if (getWidth() > 0) {
- mCurrentFullscreenParams.setScale(getWidth()
- / (getWidth() + currentInsetsLeft + currentInsetsRight));
- }
+ mCurrentFullscreenParams.setProgress(
+ mFullscreenProgress,
+ getRecentsView().getScaleX(),
+ getWidth(), mActivity.getDeviceProfile(),
+ thumbnail.getPreviewPositionHelper());
if (!getRecentsView().isTaskIconScaledDown(this)) {
// Some of the items in here are dependent on the current fullscreen params, but don't
@@ -971,26 +1013,51 @@
/**
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
*/
- static class FullscreenDrawParams {
- RectF mCurrentDrawnInsets = new RectF();
- float mCurrentDrawnCornerRadius;
+ public static class FullscreenDrawParams {
+
+ private final float mCornerRadius;
+ private final float mWindowCornerRadius;
+
+ public RectF mCurrentDrawnInsets = new RectF();
+ public float mCurrentDrawnCornerRadius;
/** The current scale we apply to the thumbnail to adjust for new left/right insets. */
- float mScale = 1;
+ public float mScale = 1;
- public FullscreenDrawParams(float cornerRadius) {
- setCornerRadius(cornerRadius);
+ public FullscreenDrawParams(Context context) {
+ mCornerRadius = TaskCornerRadius.get(context);
+ mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
+
+ mCurrentDrawnCornerRadius = mCornerRadius;
}
- public void setInsets(float left, float top, float right, float bottom) {
- mCurrentDrawnInsets.set(left, top, right, bottom);
+ public FullscreenDrawParams() {
+ mCurrentDrawnCornerRadius = mWindowCornerRadius = mCornerRadius = 0;
}
- public void setCornerRadius(float cornerRadius) {
- mCurrentDrawnCornerRadius = cornerRadius;
+ /**
+ * Sets the progress in range [0, 1]
+ */
+ public void setProgress(float fullscreenProgress, float parentScale, int previewWidth,
+ DeviceProfile dp, PreviewPositionHelper pph) {
+ boolean isMultiWindowMode = dp.isMultiWindowMode;
+ RectF insets = pph.getInsetsToDrawInFullscreen(isMultiWindowMode);
+
+ float currentInsetsLeft = insets.left * fullscreenProgress;
+ float currentInsetsRight = insets.right * fullscreenProgress;
+ mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
+ currentInsetsRight, insets.bottom * fullscreenProgress);
+ float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius;
+
+ mCurrentDrawnCornerRadius =
+ Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
+ / parentScale;
+
+ // We scaled the thumbnail to fit the content (excluding insets) within task view width.
+ // Now that we are drawing left/right insets again, we need to scale down to fit them.
+ if (previewWidth > 0) {
+ mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
+ }
}
- public void setScale(float scale) {
- mScale = scale;
- }
}
}
diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml
deleted file mode 100644
index ad5efb6..0000000
--- a/quickstep/res/layout/overview_actions.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:id="@+id/action_buttons"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:orientation="horizontal">
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
- <Button
- android:id="@+id/action_screenshot"
- style="@style/OverviewActionButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableTop="@drawable/ic_screenshot"
- android:text="@string/action_screenshot" />
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
-
- <Button
- android:id="@+id/action_share"
- style="@style/OverviewActionButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableTop="@drawable/ic_share"
- android:text="@string/action_share" />
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
- </LinearLayout>
-
-</merge>
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 328c20b..e163991 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -16,8 +16,48 @@
-->
<com.android.quickstep.views.OverviewActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone">
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
+ android:layout_marginRight="@dimen/overview_actions_horizontal_margin" >
+
+ <LinearLayout
+ android:id="@+id/action_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" >
+ </Space>
+ <Button
+ android:id="@+id/action_screenshot"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_screenshot"
+ android:text="@string/action_screenshot" />
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" >
+ </Space>
+
+ <Button
+ android:id="@+id/action_share"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_share"
+ android:text="@string/action_share" />
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" >
+ </Space>
+ </LinearLayout>
</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index a688f9a..2b11ca0 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -13,13 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:tools="http://schemas.android.com/tools">
+<resources>
<string name="task_overlay_factory_class" translatable="false"/>
<!-- Activities which block home gesture -->
- <string-array name="gesture_blocking_activities" tools:ignore="InconsistentArrays">
- <item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
- </string-array>
+ <string-array name="gesture_blocking_activities" translatable="false"/>
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 48e25bd..af63a25 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -28,9 +28,7 @@
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.view.View;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WellbeingModel;
@@ -39,7 +37,6 @@
import com.android.launcher3.proxy.StartActivityParams;
import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.UiThreadHelper;
@@ -52,6 +49,7 @@
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -75,7 +73,7 @@
private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
- private View mActionsView;
+ private OverviewActionsView mActionsView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -163,20 +161,20 @@
@Override
protected void setupViews() {
super.setupViews();
- mActionsView = findViewById(R.id.overview_actions_view);
+ SysUINavigationMode.INSTANCE.get(this).updateMode();
+ mActionsView = findViewById(R.id.overview_actions_view);
+ ((RecentsView) getOverviewPanel()).init(mActionsView);
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
// Overview is above all other launcher elements, including qsb, so move it to the top.
getOverviewPanel().bringToFront();
- if (mActionsView != null) {
- mActionsView.bringToFront();
- }
+ mActionsView.bringToFront();
}
}
- public View getActionsView() {
- return mActionsView;
+ public <T extends OverviewActionsView> T getActionsView() {
+ return (T) mActionsView;
}
@Override
@@ -207,17 +205,6 @@
}
@Override
- protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
- if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
- PagedOrientationHandler layoutVertical =
- ((RecentsView)getOverviewPanel()).getPagedViewOrientedState().getOrientationHandler();
- return layoutVertical.getScaleAndTranslation(getDeviceProfile(),
- getOverviewPanel());
- }
- return super.getOverviewScaleAndTranslationForNormalState();
- }
-
- @Override
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
QuickstepAppTransitionManagerImpl appTransitionManager =
(QuickstepAppTransitionManagerImpl) getAppTransitionManager();
@@ -240,6 +227,12 @@
}
@Override
+ public float[] getNormalOverviewScaleAndOffset() {
+ return SysUINavigationMode.getMode(this) == Mode.NO_BUTTON
+ ? new float[] {1, 1} : new float[] {1.1f, 0};
+ }
+
+ @Override
public void onDragLayerHierarchyChanged() {
onLauncherStateOrFocusChanged();
}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index fbd7a8a..7fb0d43 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -23,7 +23,6 @@
import android.os.CancellationSignal;
import android.os.Handler;
-import com.android.launcher3.util.ActivityTracker;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -45,7 +44,7 @@
}
@Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
+ public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
if (mRemoteAnimationProvider != null) {
QuickstepAppTransitionManagerImpl appTransitionManager =
(QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
@@ -71,7 +70,7 @@
}, cancellationSignal);
}
launcher.deferOverlayCallbacksUntilNextResumeOrStop();
- return super.init(launcher, alreadyOnHome);
+ return super.handleInit(launcher, alreadyOnHome);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 70cbd82..1cb0aa4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -611,7 +611,6 @@
.withWindowCrop(target.screenSpaceBounds)
.withAlpha(1f);
}
- builder.withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING));
params[i] = builder.build();
}
surfaceApplier.scheduleApply(params);
@@ -718,7 +717,6 @@
params[i] = new SurfaceParams.Builder(target.leash)
.withAlpha(1f)
.withWindowCrop(target.screenSpaceBounds)
- .withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING))
.withCornerRadius(cornerRadius)
.build();
}
@@ -775,7 +773,6 @@
}
params[i] = builder
.withWindowCrop(target.screenSpaceBounds)
- .withLayer(RemoteAnimationProvider.getLayer(target, MODE_CLOSING))
.build();
}
surfaceApplier.scheduleApply(params);
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index f83737e..5f5d6dc 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -177,7 +177,14 @@
}
private void setDepth(float depth) {
- mDepth = depth;
+ // Round out the depth to dedupe frequent, non-perceptable updates
+ int depthI = (int) (depth * 256);
+ float depthF = depthI / 256f;
+ if (Float.compare(mDepth, depthF) == 0) {
+ return;
+ }
+
+ mDepth = depthF;
if (mSurface == null || !mSurface.isValid()) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 0f45196..33011ac 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,8 +17,6 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
@@ -26,23 +24,22 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import android.util.FloatProperty;
-import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.RecentsView;
/**
* State handler for recents view. Manages UI changes and animations for recents view based off the
@@ -50,7 +47,7 @@
*
* @param <T> the recents view
*/
-public abstract class BaseRecentsViewStateController<T extends View>
+public abstract class BaseRecentsViewStateController<T extends RecentsView>
implements StateHandler {
protected final T mRecentsView;
protected final BaseQuickstepLauncher mLauncher;
@@ -62,14 +59,9 @@
@Override
public void setState(@NonNull LauncherState state) {
- ScaleAndTranslation scaleAndTranslation = state.getOverviewScaleAndTranslation(mLauncher);
- float translationX = scaleAndTranslation.translationX;
- if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- translationX = -translationX;
- }
- SCALE_PROPERTY.set(mRecentsView, scaleAndTranslation.scale);
- mRecentsView.setTranslationX(translationX);
- mRecentsView.setTranslationY(scaleAndTranslation.translationY);
+ float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
+ SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
+ ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
@@ -98,17 +90,11 @@
*/
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
@NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
- ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
- float translationX = scaleAndTranslation.translationX;
- if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- translationX = -translationX;
- }
- setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale,
+ float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
+ setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
- setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, translationX,
+ setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
- setter.setFloat(mRecentsView, VIEW_TRANSLATE_Y, scaleAndTranslation.translationY,
- config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 93e02a1..ea71d97 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -87,7 +87,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 1f;
}
@@ -102,9 +102,8 @@
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
- return new ScaleAndTranslation(0.9f, 0f, slightParallax);
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {0.9f, 0};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index cc3fd97..e5c9fc9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -22,10 +22,12 @@
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.TimeInterpolator;
@@ -132,7 +134,12 @@
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else if (fromState == OVERVIEW) {
- return isDragTowardPositive ? ALL_APPS : NORMAL;
+ LauncherState positiveDragTarget = ALL_APPS;
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
+ // Don't allow swiping up to all apps.
+ positiveDragTarget = OVERVIEW;
+ }
+ return isDragTowardPositive ? positiveDragTarget : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mAllowDragToOverview && TouchInteractionService.isConnected()
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 94ef15a..e4bb9aa 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -135,8 +135,6 @@
void createActivityInterface(long transitionLength);
- default void adjustActivityControllerInterpolators() { }
-
default void onTransitionCancelled() { }
default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 495c092..2e99500 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -22,6 +22,7 @@
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import android.content.res.Resources;
@@ -138,7 +139,8 @@
* @param info The current displayInfo
*/
void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
- mEnableMultipleRegions = enableMultipleRegions;
+ mEnableMultipleRegions = enableMultipleRegions &&
+ mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
if (!enableMultipleRegions) {
mQuickStepStartingRotation = -1;
resetSwipeRegions(info);
@@ -364,16 +366,4 @@
return false;
}
}
-
- /**
- * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
- * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
- * A value of 0 means no rotation has been applied
- */
- @SurfaceRotation
- private static int deltaRotation(int oldRotation, int newRotation) {
- int delta = newRotation - oldRotation;
- if (delta < 0) delta += 4;
- return delta;
- }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 0a00a61..a6ce2b5 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -21,6 +21,7 @@
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
@@ -35,7 +36,6 @@
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -51,8 +51,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
@@ -176,7 +174,7 @@
}
private void setupOrientationSwipeHandler() {
- if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+ if (!isFixedRotationTransformEnabled(mContext)) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 375e589..c715c93 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -26,6 +26,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -73,15 +74,20 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- Mode oldMode = mMode;
- initializeMode();
- if (mMode != oldMode) {
- dispatchModeChange();
- }
+ updateMode();
}
}, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
}
+ /** Updates navigation mode when needed. */
+ public void updateMode() {
+ Mode oldMode = mMode;
+ initializeMode();
+ if (mMode != oldMode) {
+ dispatchModeChange();
+ }
+ }
+
private void initializeMode() {
int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
for(Mode m : Mode.values()) {
@@ -128,6 +134,11 @@
return getMode(context) != Mode.TWO_BUTTONS;
}
+ public void dump(PrintWriter pw) {
+ pw.println("SysUINavigationMode:");
+ pw.println(" mode=" + mMode.name());
+ }
+
public interface NavigationModeChangeListener {
void onNavigationModeChanged(Mode newMode);
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
index b1c72ce..dfb8c1d 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -28,9 +28,11 @@
public class ActivityInitListener<T extends BaseActivity> implements SchedulerCallback<T> {
- private final BiPredicate<T, Boolean> mOnInitListener;
+ private BiPredicate<T, Boolean> mOnInitListener;
private final ActivityTracker<T> mActivityTracker;
+ private boolean mIsRegistered = false;
+
/**
* @param onInitListener a callback made when the activity is initialized. The callback should
* return true to continue receiving callbacks (ie. for if the activity is
@@ -43,27 +45,46 @@
}
@Override
- public boolean init(T activity, boolean alreadyOnHome) {
+ public final boolean init(T activity, boolean alreadyOnHome) {
+ if (!mIsRegistered) {
+ return false;
+ }
+ return handleInit(activity, alreadyOnHome);
+ }
+
+ protected boolean handleInit(T activity, boolean alreadyOnHome) {
return mOnInitListener.test(activity, alreadyOnHome);
}
/**
* Registers the activity-created listener. If the activity is already created, then the
* callback provided in the constructor will be called synchronously.
+ * @param intent The intent that will be used to initialize the activity, if the activity
+ * doesn't already exist. We add the callback as an extra on this intent.
*/
- public void register() {
- mActivityTracker.schedule(this);
+ public void register(Intent intent) {
+ mIsRegistered = true;
+ mActivityTracker.runCallbackWhenActivityExists(this, intent);
}
+ /**
+ * After calling this, we won't {@link #init} even when the activity is ready.
+ */
public void unregister() {
- mActivityTracker.clearReference(this);
+ mIsRegistered = false;
+ mOnInitListener = null;
}
+ /**
+ * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this
+ * method will not call {@link #init} if the activity already exists, it will only call it when
+ * we get handleIntent() for the provided intent that we're starting.
+ */
public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
Context context, Handler handler, long duration) {
- register();
+ mIsRegistered = true;
Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
- context.startActivity(addToIntent(new Intent((intent))), options);
+ context.startActivity(addToIntent(new Intent(intent)), options);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 1f1a999..4edf2fb 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -63,7 +63,7 @@
if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
//TODO: this needs to account for the swipe gesture height and accessibility
// UI when shown.
- extraSpace = 0;
+ extraSpace = res.getDimensionPixelSize(R.dimen.overview_actions_height);
} else {
extraSpace = getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight
+ res.getDimensionPixelSize(
@@ -75,7 +75,14 @@
}
public static void calculateFallbackTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- calculateTaskSize(context, dp, 0, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE, outRect);
+ float extraSpace;
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+ extraSpace = context.getResources()
+ .getDimensionPixelSize(R.dimen.overview_actions_height);
+ } else {
+ extraSpace = 0;
+ }
+ calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE, outRect);
}
@AnyThread
@@ -123,8 +130,6 @@
}
float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
- float bottomMargin = thumbnailBottomMargin(context);
-
float paddingVert = overviewActionsEnabled && removeShelfFromOverview(context)
? 0 : res.getDimension(R.dimen.task_card_vert_space);
@@ -134,7 +139,7 @@
int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
float availableHeight = launcherVisibleHeight
- - topIconMargin - extraVerticalSpace - paddingVert - bottomMargin;
+ - topIconMargin - extraVerticalSpace - paddingVert;
float availableWidth = launcherVisibleWidth - paddingHorz;
float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
@@ -144,7 +149,7 @@
// Center in the visible space
float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
float y = insets.top + Math.max(topIconMargin,
- (launcherVisibleHeight - extraVerticalSpace - outHeight - bottomMargin) / 2);
+ (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
outRect.set(Math.round(x), Math.round(y),
Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
}
@@ -163,14 +168,16 @@
}
/**
- * Get the margin that the task thumbnail view should use.
- * @return the margin in pixels.
+ * Gets the scale that should be applied to the TaskView so that it matches the target
*/
- public static int thumbnailBottomMargin(Context context) {
- if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
- return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
+ public static float getTaskScale(RecentsOrientedState orientedState,
+ float srcWidth, float srcHeight, float targetWidth, float targetHeight) {
+ if (orientedState == null
+ || orientedState.isHomeRotationAllowed()
+ || orientedState.isDisplayPhoneNatural()) {
+ return srcWidth / targetWidth;
} else {
- return 0;
+ return srcHeight / targetHeight;
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index ab2484d..a678cb5 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -18,6 +18,8 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import android.content.SharedPreferences;
@@ -58,7 +60,14 @@
});
}
- if (!getBoolean(SHELF_BOUNCE_SEEN)) {
+ boolean shelfBounceSeen = getBoolean(SHELF_BOUNCE_SEEN);
+ if (!shelfBounceSeen && ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(launcher)) {
+ // There's no shelf in overview, so don't bounce it (can't get to all apps anyway).
+ shelfBounceSeen = true;
+ mSharedPrefs.edit().putBoolean(SHELF_BOUNCE_SEEN, shelfBounceSeen).apply();
+ }
+ if (!shelfBounceSeen) {
mStateManager.addStateListener(new StateListener() {
@Override
public void onStateTransitionStart(LauncherState toState) { }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index f72e458..74daeca 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -16,25 +16,42 @@
package com.android.quickstep.util;
-import static android.hardware.camera2.params.OutputConfiguration.ROTATION_180;
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
import android.view.MotionEvent;
+import android.view.OrientationEventListener;
import android.view.Surface;
import androidx.annotation.IntDef;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
import java.lang.annotation.Retention;
+import java.util.function.IntConsumer;
/**
* Container to hold orientation/rotation related information for Launcher.
@@ -44,8 +61,19 @@
* This class has initial default state assuming the device and foreground app have
* no ({@link Surface#ROTATION_0} rotation.
*/
-public final class RecentsOrientedState {
+public final class RecentsOrientedState implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = "RecentsOrientedState";
+ private static final boolean DEBUG = false;
+
+ private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
+
+ private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAutoRotateSetting();
+ }
+ };
@Retention(SOURCE)
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
public @interface SurfaceRotation {}
@@ -54,15 +82,83 @@
private @SurfaceRotation int mTouchRotation = ROTATION_0;
private @SurfaceRotation int mDisplayRotation = ROTATION_0;
- /**
- * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
- * launcher orientations.
- */
- private boolean mDisableMultipleOrientations;
+ private @SurfaceRotation int mLauncherRotation = Surface.ROTATION_0;
+
+ // Launcher activity supports multiple orientation, but fallback activity does not
+ private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
+ // Multiple orientation is only supported if density is < 600
+ private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY = 1 << 1;
+ // Feature flag controlling the multi-orientation feature
+ private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG = 1 << 2;
+ // Shared prefs for rotation, only if activity supports it
+ private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 3;
+ // If the user has enabled system rotation
+ private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 4;
+ // Whether to rotation sensor is supported on the device
+ private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 5;
+ // Whether to enable rotation watcher when multi-rotation is supported
+ private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
+
+ private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
+ FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
+ | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY
+ | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
+
+ private static final int MASK_ACTIVITY_ROTATING =
+ FLAG_HOME_ROTATION_ALLOWED_IN_PREFS | FLAG_SYSTEM_ROTATION_ALLOWED;
+
+ // State for which rotation watcher will be enabled.
+ // We skip it when home rotation is enabled as in that case, activity itself rotates
+ private static final int VALUE_ROTATION_WATCHER_ENABLED =
+ MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
+ | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED;
+
+ private final ContentResolver mContentResolver;
+ private final SharedPreferences mSharedPrefs;
+ private final OrientationEventListener mOrientationListener;
private final Matrix mTmpMatrix = new Matrix();
private final Matrix mTmpInverseMatrix = new Matrix();
+ private int mFlags;
+ private int mPreviousRotation = ROTATION_0;
+
+ /**
+ * @param rotationChangeListener Callback for receiving rotation events when rotation watcher
+ * is enabled
+ * @see #setRotationWatcherEnabled(boolean)
+ */
+ public RecentsOrientedState(Context context, boolean rotationSupportedByActivity,
+ IntConsumer rotationChangeListener) {
+ mContentResolver = context.getContentResolver();
+ mSharedPrefs = Utilities.getPrefs(context);
+ mOrientationListener = new OrientationEventListener(context) {
+ @Override
+ public void onOrientationChanged(int degrees) {
+ int newRotation = getRotationForUserDegreesRotated(degrees);
+ if (newRotation != mPreviousRotation) {
+ mPreviousRotation = newRotation;
+ rotationChangeListener.accept(newRotation);
+ }
+ }
+ };
+
+ mFlags = rotationSupportedByActivity ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
+
+ Resources res = context.getResources();
+ int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
+ * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
+ if (originalSmallestWidth < 600) {
+ mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
+ }
+ if (isFixedRotationTransformEnabled(context)) {
+ mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
+ }
+ if (mOrientationListener.canDetectOrientation()) {
+ mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED;
+ }
+ }
+
/**
* Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
* @param touchRotation The rotation the nav bar region that is touched is in
@@ -72,19 +168,29 @@
* false otherwise
*/
public boolean update(
- @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
- if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+ @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation,
+ @SurfaceRotation int launcherRotation) {
+ if (!isMultipleOrientationSupportedByDevice()) {
return false;
}
- if (mDisableMultipleOrientations) {
- return false;
- }
- if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation) {
+ if (mDisplayRotation == displayRotation && mTouchRotation == touchRotation
+ && launcherRotation == mLauncherRotation) {
return false;
}
+ mLauncherRotation = launcherRotation;
mDisplayRotation = displayRotation;
mTouchRotation = touchRotation;
+
+ if (canLauncherRotate() || mLauncherRotation == mTouchRotation) {
+ // TODO(b/153476489) Need to determine when launcher is rotated
+ mOrientationHandler = PagedOrientationHandler.HOME_ROTATED;
+ if (DEBUG) {
+ Log.d(TAG, "Set Orientation Handler: " + mOrientationHandler);
+ }
+ return true;
+ }
+
if (mTouchRotation == ROTATION_90) {
mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
} else if (mTouchRotation == ROTATION_270) {
@@ -92,22 +198,81 @@
} else {
mOrientationHandler = PagedOrientationHandler.PORTRAIT;
}
+ if (DEBUG) {
+ Log.d(TAG, "Set Orientation Handler: " + mOrientationHandler);
+ }
return true;
}
- public boolean areMultipleLayoutOrientationsDisabled() {
- return mDisableMultipleOrientations;
+ private void setFlag(int mask, boolean enabled) {
+ boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
+ && mFlags == VALUE_ROTATION_WATCHER_ENABLED;
+ if (enabled) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+
+ boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation
+ && mFlags == VALUE_ROTATION_WATCHER_ENABLED;
+ if (wasRotationEnabled != isRotationEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (isRotationEnabled) {
+ mOrientationListener.enable();
+ } else {
+ mOrientationListener.disable();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ updateHomeRotationSetting();
+ }
+
+ private void updateAutoRotateSetting() {
+ setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver,
+ Settings.System.ACCELEROMETER_ROTATION, 1) == 1);
+ }
+
+ private void updateHomeRotationSetting() {
+ setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS,
+ mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false));
}
/**
- * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
+ * Initializes aany system values and registers corresponding change listeners. It must be
+ * paired with {@link #destroy()} call
*/
- public void disableMultipleOrientations(boolean disable) {
- mDisableMultipleOrientations = disable;
- if (disable) {
- mDisplayRotation = mTouchRotation = ROTATION_0;
- mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ public void init() {
+ if (isMultipleOrientationSupportedByDevice()) {
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+ false, mSystemAutoRotateObserver);
}
+ initWithoutListeners();
+ }
+
+ /**
+ * Unregisters any previously registered listeners.
+ */
+ public void destroy() {
+ if (isMultipleOrientationSupportedByDevice()) {
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
+ }
+ setRotationWatcherEnabled(false);
+ }
+
+ /**
+ * Initializes the OrientationState without attaching any listeners. This can be used when
+ * the object is short lived.
+ */
+ public void initWithoutListeners() {
+ updateAutoRotateSetting();
+ updateHomeRotationSetting();
}
@SurfaceRotation
@@ -120,6 +285,31 @@
return mTouchRotation;
}
+ @SurfaceRotation
+ public int getLauncherRotation() {
+ return mLauncherRotation;
+ }
+
+ public boolean isMultipleOrientationSupportedByDevice() {
+ return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+ == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE;
+ }
+
+ public boolean isHomeRotationAllowed() {
+ return (mFlags & FLAG_HOME_ROTATION_ALLOWED_IN_PREFS) != 0;
+ }
+
+ public boolean canLauncherRotate() {
+ return (mFlags & MASK_ACTIVITY_ROTATING) == MASK_ACTIVITY_ROTATING;
+ }
+
+ /**
+ * Enables or disables the rotation watcher for listening to rotation callbacks
+ */
+ public void setRotationWatcherEnabled(boolean isEnabled) {
+ setFlag(FLAG_ROTATION_WATCHER_ENABLED, isEnabled);
+ }
+
public int getTouchRotationDegrees() {
switch (mTouchRotation) {
case ROTATION_90:
@@ -134,6 +324,25 @@
}
}
+ /**
+ * Returns the scale and pivot so that the provided taskRect can fit the provided full size
+ */
+ public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
+ Rect insets = dp.getInsets();
+ float fullWidth = dp.widthPx - insets.left - insets.right;
+ float fullHeight = dp.heightPx - insets.top - insets.bottom;
+ final float scale = LayoutUtils.getTaskScale(this,
+ fullWidth, fullHeight, taskView.width(), taskView.height());
+
+ if (scale == 1) {
+ outPivot.set(fullWidth / 2, fullHeight / 2);
+ } else {
+ float factor = scale / (scale - 1);
+ outPivot.set(taskView.left * factor, taskView.top * factor);
+ }
+ return scale;
+ }
+
public PagedOrientationHandler getOrientationHandler() {
return mOrientationHandler;
}
@@ -166,8 +375,12 @@
}
public void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight) {
+ mapRectFromRotation(mDisplayRotation, src, screenWidth, screenHeight);
+ }
+
+ public void mapRectFromRotation(int rotation, RectF src, int screenWidth, int screenHeight) {
mTmpMatrix.reset();
- postDisplayRotation(mDisplayRotation, screenWidth, screenHeight, mTmpMatrix);
+ postDisplayRotation(rotation, screenWidth, screenHeight, mTmpMatrix);
mTmpMatrix.mapRect(src);
}
@@ -192,6 +405,10 @@
}
}
+ public boolean isDisplayPhoneNatural() {
+ return mDisplayRotation == Surface.ROTATION_0 || mDisplayRotation == Surface.ROTATION_180;
+ }
+
/**
* Posts the transformation on the matrix representing the provided display rotation
*/
@@ -214,4 +431,13 @@
break;
}
}
+
+ /**
+ * Returns true if system can keep Launcher fixed to portrait layout even if the
+ * foreground app is rotated
+ */
+ public static boolean isFixedRotationTransformEnabled(Context context) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), FIXED_ROTATION_TRANSFORM_SETTING_NAME, 1) == 1;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 21b97ec..4cd0206 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -62,15 +62,12 @@
static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets,
TransactionCompat t, int boostModeTargets) {
for (RemoteAnimationTargetCompat target : targets) {
- t.setLayer(target.leash, getLayer(target, boostModeTargets));
t.show(target.leash);
}
}
public static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
- return target.mode == boostModeTarget
- ? Z_BOOST_BASE + target.prefixOrderIndex
- : target.prefixOrderIndex;
+ return target.prefixOrderIndex;
}
/**
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 0afe4a8..40265c4 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -203,7 +203,7 @@
+ launcher.getNavigationModeMismatchError(),
() -> launcher.getNavigationModeMismatchError() == null,
60000 /* b/148422894 */, launcher);
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(launcher);
return true;
}
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 8ecd88a..115294a 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -17,10 +17,11 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static com.android.launcher3.common.WidgetUtils.createWidgetInfo;
import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
-import static com.android.launcher3.ui.widget.BindWidgetTest.createWidgetInfo;
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
import static org.junit.Assert.assertEquals;
@@ -187,7 +188,7 @@
LauncherSettings.Settings.call(mResolver,
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
addItemToScreen(item);
assertTrue("Widget is not present",
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index de13277..a137908 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -45,12 +45,8 @@
<include
android:id="@+id/overview_panel"
- layout="@layout/overview_panel"
- android:visibility="gone" />
+ layout="@layout/overview_panel" />
- <include
- android:id="@+id/overview_actions_view"
- layout="@layout/overview_actions_container" />
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 2637f03..f513688 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -17,4 +17,5 @@
<Space
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
- android:layout_height="0dp" />
\ No newline at end of file
+ android:layout_height="0dp"
+ android:visibility="gone" />
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
index 98cfc34..fdf4446 100644
--- a/res/layout/secondary_launcher.xml
+++ b/res/layout/secondary_launcher.xml
@@ -17,7 +17,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/drag_layer" >
+ android:id="@+id/drag_layer"
+ android:padding="@dimen/dynamic_grid_edge_margin">
<GridView
android:layout_width="match_parent"
@@ -119,8 +120,7 @@
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
- android:textSize="16sp"
- android:translationY="24dp" />
+ android:textSize="16sp" />
<include layout="@layout/all_apps_fast_scroller" />
</com.android.launcher3.allapps.AllAppsContainerView>
diff --git a/res/layout/system_shortcut_icon_only.xml b/res/layout/system_shortcut_icon_only.xml
index b8b5b8c..5a81f70 100644
--- a/res/layout/system_shortcut_icon_only.xml
+++ b/res/layout/system_shortcut_icon_only.xml
@@ -19,7 +19,7 @@
android:layout_width="@dimen/system_shortcut_header_icon_touch_size"
android:layout_height="@dimen/system_shortcut_header_icon_touch_size"
android:background="?android:attr/selectableItemBackgroundBorderless"
- android:tint="?android:attr/textColorHint"
+ android:tint="?attr/iconOnlyShortcutColor"
android:tintMode="src_in"
android:padding="@dimen/system_shortcut_header_icon_padding"
android:theme="@style/PopupItem" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e946835..d4cb6d7 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -34,6 +34,7 @@
<attr name="workspaceStatusBarScrim" format="reference" />
<attr name="widgetsTheme" format="reference" />
<attr name="loadingIconColor" format="color" />
+ <attr name="iconOnlyShortcutColor" format="color"/>
<attr name="folderDotColor" format="color" />
<attr name="folderFillColor" format="color" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 0657b86..41bb909 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -125,11 +125,11 @@
<item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
<item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
- <item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.5</item>
- <item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">1500</item>
+ <item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.73</item>
+ <item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">800</item>
- <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.5</item>
- <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">1500</item>
+ <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.73</item>
+ <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">800</item>
<item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.8</item>
<item name="horizontal_spring_stiffness" type="dimen" format="float">400</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0b589a2..31b12ad 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -200,7 +200,7 @@
<dimen name="system_shortcut_icon_size">24dp</dimen>
<!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
<dimen name="system_shortcut_margin_start">16dp</dimen>
- <dimen name="system_shortcut_header_height">40dp</dimen>
+ <dimen name="system_shortcut_header_height">48dp</dimen>
<dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
<!-- (touch_size - icon_size) / 2 -->
<dimen name="system_shortcut_header_icon_padding">12dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 65e0816..5f4bd8e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -182,7 +182,7 @@
<string name="wallpaper_button_text">Wallpapers</string>
<!-- Text for wallpaper change button -->
<string name="styles_wallpaper_button_text">Styles & wallpapers</string>
- <!-- Text for settings button -->
+ <!-- Text for settings button [CHAR LIMIT=20]-->
<string name="settings_button_text">Home settings</string>
<!-- Message shown when a feature is disabled by the administrator -->
<string name="msg_disabled_by_admin">Disabled by your admin</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fcc651b..a2c0f23 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -30,11 +30,12 @@
</style>
<style name="LauncherTheme" parent="@style/BaseLauncherTheme">
- <item name="allAppsScrimColor">#EAFFFFFF</item>
+ <item name="android:textColorSecondary">#DE000000</item>
+ <item name="allAppsScrimColor">#FFFFFFFF</item>
<item name="allAppsInterimScrimAlpha">46</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
<item name="popupColorPrimary">#FFF</item>
- <item name="popupColorSecondary">#F5F5F5</item> <!-- Gray 100 -->
+ <item name="popupColorSecondary">#F1F3F4</item>
<item name="popupColorTertiary">#E0E0E0</item> <!-- Gray 300 -->
<item name="isMainColorDark">false</item>
<item name="isWorkspaceDarkText">false</item>
@@ -50,6 +51,7 @@
<item name="folderTextColor">#FF212121</item>
<item name="folderHintColor">#FF616161</item>
<item name="loadingIconColor">#CCFFFFFF</item>
+ <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
<item name="workProfileOverlayTextColor">#FF212121</item>
<item name="android:windowTranslucentStatus">false</item>
@@ -88,11 +90,11 @@
<item name="android:textColorHint">#A0FFFFFF</item>
<item name="android:colorControlHighlight">#A0FFFFFF</item>
<item name="android:colorPrimary">#FF212121</item>
- <item name="allAppsScrimColor">#EA212121</item>
+ <item name="allAppsScrimColor">#FF212121</item>
<item name="allAppsInterimScrimAlpha">102</item>
<item name="allAppsNavBarScrimColor">#80000000</item>
<item name="popupColorPrimary">#3C4043</item> <!-- Gray 800 -->
- <item name="popupColorSecondary">#5F6368</item> <!-- Gray 700 -->
+ <item name="popupColorSecondary">#202124</item>
<item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="folderDotColor">#FF464646</item>
@@ -102,6 +104,7 @@
<item name="folderHintColor">#FFCCCCCC</item>
<item name="isMainColorDark">true</item>
<item name="loadingIconColor">#99FFFFFF</item>
+ <item name="iconOnlyShortcutColor">#B3FFFFFF</item>
<item name="workProfileOverlayTextColor">@android:color/white</item>
</style>
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index bbc62e9..3fa9b0a 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -22,7 +22,10 @@
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SDK_VERSION := system_current
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../tests/src_common)
+
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.test.runner \
androidx.test.rules \
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index 0579400..c162255 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -1,5 +1,6 @@
sdk=29
shadows= \
+ com.android.launcher3.shadows.LShadowApplicationPackageManager \
com.android.launcher3.shadows.LShadowAppPredictionManager \
com.android.launcher3.shadows.LShadowAppWidgetManager \
com.android.launcher3.shadows.LShadowBackupManager \
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index b7340cf..bbbe21e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -130,7 +130,7 @@
}
helper.close();
- helper = new DatabaseHelper(mContext, DB_FILE) {
+ helper = new DatabaseHelper(mContext, DB_FILE, false) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
@@ -161,7 +161,7 @@
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
- DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE) {
+ DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 58174c7..ee73b82 100644
--- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -95,7 +95,7 @@
private final long mProfileId;
MyDatabaseHelper(long profileId) {
- super(RuntimeEnvironment.application, null);
+ super(RuntimeEnvironment.application, null, false);
mProfileId = profileId;
}
diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
new file mode 100644
index 0000000..0ca5ce6
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.secondarydisplay;
+
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.LauncherUIHelper.doLayout;
+import static com.android.launcher3.util.Preconditions.assertNotNull;
+
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.allapps.AllAppsRecyclerView;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.shadows.ShadowOverrides;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.LauncherModelHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowUserManager;
+
+/**
+ * Tests for {@link SecondaryDisplayLauncher} with work profile
+ */
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class SDWorkModeTest {
+
+ private static final int SYSTEM_USER = 0;
+ private static final int FLAG_SYSTEM = 0x00000800;
+ private static final int WORK_PROFILE_ID = 10;
+ private static final int FLAG_PROFILE = 0x00001000;
+
+ private Context mTargetContext;
+ private InvariantDeviceProfile mIdp;
+ private LauncherModelHelper mModelHelper;
+
+ private LauncherLayoutBuilder mLayoutBuilder;
+
+ @Before
+ public void setup() throws Exception {
+ mModelHelper = new LauncherModelHelper();
+ mTargetContext = RuntimeEnvironment.application;
+ mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+ ShadowOverrides.setProvider(UserEventDispatcher.class,
+ c -> mock(UserEventDispatcher.class));
+ Settings.Global.putFloat(mTargetContext.getContentResolver(),
+ Settings.Global.WINDOW_ANIMATION_SCALE, 0);
+
+ mModelHelper.installApp(TEST_PACKAGE);
+ mLayoutBuilder = new LauncherLayoutBuilder();
+ }
+
+ @Test
+ public void testAllAppsList_noWorkProfile() throws Exception {
+ SecondaryDisplayLauncher launcher = loadLauncher();
+ launcher.showAppDrawer(true);
+ doLayout(launcher);
+
+ verifyRecyclerViewCount(launcher.getAppsView().getActiveRecyclerView());
+ }
+
+ @Test
+ public void testAllAppsList_workProfile() throws Exception {
+ ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class));
+ sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
+ sum.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE);
+
+ SecondaryDisplayLauncher launcher = loadLauncher();
+ launcher.showAppDrawer(true);
+ doLayout(launcher);
+
+ AllAppsRecyclerView rv1 = launcher.getAppsView().getActiveRecyclerView();
+ verifyRecyclerViewCount(rv1);
+
+ assertNotNull(launcher.getAppsView().getWorkModeSwitch());
+ assertTrue(launcher.getAppsView().getRecyclerViewContainer() instanceof AllAppsPagedView);
+
+ AllAppsPagedView pagedView =
+ (AllAppsPagedView) launcher.getAppsView().getRecyclerViewContainer();
+ pagedView.snapToPageImmediately(1);
+ doLayout(launcher);
+
+ AllAppsRecyclerView rv2 = launcher.getAppsView().getActiveRecyclerView();
+ verifyRecyclerViewCount(rv2);
+ assertNotSame(rv1, rv2);
+ }
+
+ private SecondaryDisplayLauncher loadLauncher() throws Exception {
+ // Install 100 apps
+ for (int i = 0; i < 100; i++) {
+ mModelHelper.installApp(TEST_PACKAGE + i);
+ }
+ mModelHelper.setupDefaultLayoutProvider(new LauncherLayoutBuilder()).loadModelSync();
+ SecondaryDisplayLauncher launcher =
+ Robolectric.buildActivity(SecondaryDisplayLauncher.class).setup().get();
+ doLayout(launcher);
+ return launcher;
+ }
+
+ private void verifyRecyclerViewCount(AllAppsRecyclerView rv) {
+ int childCount = rv.getChildCount();
+ assertTrue(childCount > 0);
+ assertTrue(childCount < 100);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java
new file mode 100644
index 0000000..da8b919
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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.os.Process;
+import android.os.UserHandle;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowApplicationPackageManager;
+
+/**
+ * Shadow for {@link ShadowApplicationPackageManager} which create mock predictors
+ */
+@Implements(className = "android.app.ApplicationPackageManager")
+public class LShadowApplicationPackageManager extends ShadowApplicationPackageManager {
+
+ @Implementation
+ public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
+ return Process.myUserHandle().equals(user) ? label : "Work " + label;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
index 76cb747..6a6f0fb 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -30,7 +30,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
-import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -80,14 +79,15 @@
protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
.resolveActivity(intent, 0);
- return ri == null ? null : getLauncherActivityInfo(ri.activityInfo);
+ return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user);
}
- public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) {
+ 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, Process.myUserHandle()));
+ ClassParameter.from(UserHandle.class, user));
}
@Implementation
@@ -104,7 +104,7 @@
.setPackage(packageName);
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
.stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo))
+ .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
.collect(Collectors.toList());
}
@@ -130,7 +130,7 @@
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
.stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo))
+ .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
.collect(Collectors.toList());
}
}
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index f69c8ed..e4f201c 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -181,7 +181,9 @@
sourceContainer);
}
getUserEventDispatcher().logAppLaunch(v, intent, user);
- getStatsLogManager().log(APP_LAUNCH_TAP, item.buildProto(null, null));
+
+ getStatsLogManager().log(APP_LAUNCH_TAP, item == null ? null
+ : item.buildProto(null, null));
return true;
} catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 41eeb78..8eceec0 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -183,10 +183,6 @@
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
- }
-
if (state == SCROLL_STATE_IDLE) {
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
@@ -196,10 +192,6 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (isLayoutSuppressed()) info.setScrollable(false);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
- "onInitializeAccessibilityNodeInfo, scrollable: " + info.isScrollable());
- }
}
@Override
@@ -207,12 +199,8 @@
final boolean changing = frozen != isLayoutSuppressed();
super.setLayoutFrozen(frozen);
if (changing) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
- + " @ " + Log.getStackTraceString(new Throwable()));
- ActivityContext.lookupContext(getContext()).getDragLayer()
- .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
- }
+ ActivityContext.lookupContext(getContext()).getDragLayer()
+ .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
}
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 93247ab..d7d4a27 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
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;
@@ -70,7 +71,7 @@
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
- IconLabelDotView, DraggableView {
+ IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -78,6 +79,10 @@
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 mScaleForReorderBounce = 1f;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@@ -672,6 +677,45 @@
return mIconSize;
}
+ private void updateTranslation() {
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ }
+
+ 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);
+ }
+
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+
+ 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 99416c4..ed71ddc 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -33,6 +33,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -54,7 +55,6 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
@@ -66,7 +66,6 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -99,6 +98,7 @@
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@Thunk final int[] mTmpPoint = new int[2];
@Thunk final int[] mTempLocation = new int[2];
+ final PointF mTmpPointF = new PointF();
// Used to visualize / debug the Grid of the CellLayout
private static final boolean VISUALIZE_GRID = false;
@@ -136,7 +136,7 @@
private final Paint mDragOutlinePaint = new Paint();
@Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
- @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
+ @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
private boolean mItemPlacementDirty = false;
@@ -852,9 +852,10 @@
int delay, boolean permanent, boolean adjustOccupied) {
ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
- if (clc.indexOfChild(child) != -1) {
+ if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final ItemInfo info = (ItemInfo) child.getTag();
+ final Reorderable item = (Reorderable) child;
// We cancel any existing animations
if (mReorderAnimators.containsKey(lp)) {
@@ -862,13 +863,18 @@
mReorderAnimators.remove(lp);
}
- final int oldX = lp.x;
- final int oldY = lp.y;
+
if (adjustOccupied) {
GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
}
+
+ // Compute the new x and y position based on the new cellX and cellY
+ // We leverage the actual layout logic in the layout params and hence need to modify
+ // state and revert that state.
+ final int oldX = lp.x;
+ final int oldY = lp.y;
lp.isLockedToGrid = true;
if (permanent) {
lp.cellX = info.cellX = cellX;
@@ -878,15 +884,23 @@
lp.tmpCellY = cellY;
}
clc.setupLp(child);
- lp.isLockedToGrid = false;
final int newX = lp.x;
final int newY = lp.y;
-
lp.x = oldX;
lp.y = oldY;
+ lp.isLockedToGrid = false;
+ // End compute new x and y
+
+ item.getReorderPreviewOffset(mTmpPointF);
+ final float initPreviewOffsetX = mTmpPointF.x;
+ final float initPreviewOffsetY = mTmpPointF.y;
+ final float finalPreviewOffsetX = newX - oldX;
+ final float finalPreviewOffsetY = newY - oldY;
+
// Exit early if we're not actually moving the view
- if (oldX == newX && oldY == newY) {
+ if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
+ && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
lp.isLockedToGrid = true;
return true;
}
@@ -899,9 +913,9 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float r = (Float) animation.getAnimatedValue();
- lp.x = (int) ((1 - r) * oldX + r * newX);
- lp.y = (int) ((1 - r) * oldY + r * newY);
- child.requestLayout();
+ float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
+ float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
+ item.setReorderPreviewOffset(x, y);
}
});
va.addListener(new AnimatorListenerAdapter() {
@@ -912,6 +926,7 @@
// place just yet.
if (!cancelled) {
lp.isLockedToGrid = true;
+ item.setReorderPreviewOffset(0, 0);
child.requestLayout();
}
if (mReorderAnimators.containsKey(lp)) {
@@ -1868,10 +1883,11 @@
boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
!= null && !solution.intersectingViews.contains(child);
+
LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (c != null && !skip) {
- ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
- lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
+ if (c != null && !skip && (child instanceof Reorderable)) {
+ ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+ mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
rha.animate();
}
}
@@ -1893,7 +1909,7 @@
// 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 View child;
+ final Reorderable child;
float finalDeltaX;
float finalDeltaY;
float initDeltaX;
@@ -1913,8 +1929,8 @@
float animationProgress = 0;
ValueAnimator a;
- public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
- int cellY1, int spanX, int spanY) {
+ public ReorderPreviewAnimation(Reorderable child, 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];
final int y0 = mTmpPoint[1];
@@ -1926,63 +1942,60 @@
this.child = child;
this.mode = mode;
+ finalDeltaX = 0;
+ finalDeltaY = 0;
- // TODO issue!
- setInitialAnimationValues(false);
- finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
- finalDeltaX = initDeltaX;
- finalDeltaY = initDeltaY;
+ child.getReorderBounceOffset(mTmpPointF);
+ initDeltaX = mTmpPointF.x;
+ initDeltaY = mTmpPointF.y;
+ initScale = child.getReorderBounceScale();
+ finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+
int dir = mode == MODE_HINT ? -1 : 1;
if (dX == dY && dX == 0) {
} else {
if (dY == 0) {
- finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+ finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
} else if (dX == 0) {
- finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+ finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
} else {
double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX += (int) (- dir * Math.signum(dX) *
- Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
- finalDeltaY += (int) (- dir * Math.signum(dY) *
- Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaX = (int) (-dir * Math.signum(dX)
+ * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaY = (int) (-dir * Math.signum(dY)
+ * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
}
}
}
- void setInitialAnimationValues(boolean restoreOriginalValues) {
- if (restoreOriginalValues) {
- if (child instanceof LauncherAppWidgetHostView) {
- LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
- initScale = lahv.getScaleToFit();
- initDeltaX = lahv.getTranslationForCentering().x;
- initDeltaY = lahv.getTranslationForCentering().y;
- } else {
- initScale = mChildScale;
- initDeltaX = 0;
- initDeltaY = 0;
- }
- } else {
- initScale = child.getScaleX();
- initDeltaX = child.getTranslationX();
- initDeltaY = child.getTranslationY();
- }
+ void setInitialAnimationValuesToBaseline() {
+ initScale = mChildScale;
+ initDeltaX = 0;
+ initDeltaY = 0;
}
void animate() {
- boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+ boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
if (mShakeAnimators.containsKey(child)) {
ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
- oldAnimation.cancel();
mShakeAnimators.remove(child);
+
if (noMovement) {
- completeAnimationImmediately();
+ // A previous animation for this item exists, and no new animation will exist.
+ // Finish the old animation smoothly.
+ oldAnimation.finishAnimation();
return;
+ } else {
+ // A previous animation for this item exists, and a new one will exist. Stop
+ // the old animation in its tracks, and proceed with the new one.
+ oldAnimation.cancel();
}
}
if (noMovement) {
return;
}
+
ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
a = va;
@@ -1999,7 +2012,7 @@
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationRepeat(Animator animation) {
// We make sure to end only after a full period
- setInitialAnimationValues(true);
+ setInitialAnimationValuesToBaseline();
repeating = true;
}
});
@@ -2012,11 +2025,9 @@
float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
- child.setTranslationX(x);
- child.setTranslationY(y);
+ child.setReorderBounceOffset(x, y);
float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
- child.setScaleX(s);
- child.setScaleY(s);
+ child.setReorderBounceScale(s);
}
private void cancel() {
@@ -2025,27 +2036,27 @@
}
}
- @Thunk void completeAnimationImmediately() {
+ /**
+ * Smoothly returns the item to its baseline position / scale
+ */
+ @Thunk void finishAnimation() {
if (a != null) {
a.cancel();
}
- setInitialAnimationValues(true);
- a = new PropertyListBuilder()
- .scale(initScale)
- .translationX(initDeltaX)
- .translationY(initDeltaY)
- .build(child)
- .setDuration(REORDER_ANIMATION_DURATION);
- Launcher.cast(mActivity).getDragController().addFirstFrameAnimationHelper(a);
+ setInitialAnimationValuesToBaseline();
+ ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
+ animationProgress, 0);
+ a = va;
a.setInterpolator(DEACCEL_1_5);
+ a.setDuration(REORDER_ANIMATION_DURATION);
a.start();
}
}
private void completeAndClearReorderPreviewAnimations() {
for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
- a.completeAnimationImmediately();
+ a.finishAnimation();
}
mShakeAnimators.clear();
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index fc3c9fd..a5f98c0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -22,7 +22,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.util.DisplayMetrics;
import android.view.Surface;
import com.android.launcher3.CellLayout.ContainerType;
@@ -34,6 +33,7 @@
public class DeviceProfile {
public final InvariantDeviceProfile inv;
+ private final DefaultDisplay.Info mInfo;
// Device properties
public final boolean isTablet;
@@ -133,9 +133,9 @@
public DotRenderer mDotRendererWorkSpace;
public DotRenderer mDotRendererAllApps;
- public DeviceProfile(Context context, InvariantDeviceProfile inv,
- Point minSize, Point maxSize,
- int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
+ public DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
+ Point minSize, Point maxSize, int width, int height, boolean isLandscape,
+ boolean isMultiWindowMode, boolean transposeLayoutWithOrientation) {
this.inv = inv;
this.isLandscape = isLandscape;
@@ -152,8 +152,8 @@
availableHeightPx = maxSize.y;
}
+ mInfo = info;
Resources res = context.getResources();
- DisplayMetrics dm = res.getDisplayMetrics();
// Constants from resources
isTablet = res.getBoolean(R.bool.is_tablet);
@@ -163,8 +163,7 @@
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
// Some more constants
- transposeLayoutWithOrientation =
- res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
context = getContext(context, isVerticalBarLayout()
? Configuration.ORIENTATION_LANDSCAPE
@@ -207,13 +206,14 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
// Add a bit of space between nav bar and hotseat in vertical bar layout.
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
- hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
+ hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
+ + (isVerticalBarLayout()
? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
: (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
// Calculate all of the remaining variables.
- updateAvailableDimensions(dm, res);
+ updateAvailableDimensions(res);
// Now that we have all of the variables calculated, we can tune certain sizes.
if (!isVerticalBarLayout() && isPhone && isTallDevice) {
@@ -227,7 +227,7 @@
hotseatBarBottomPaddingPx += extraSpace;
// Recalculate the available dimensions using the new hotseat size.
- updateAvailableDimensions(dm, res);
+ updateAvailableDimensions(res);
}
updateWorkspacePadding();
@@ -239,12 +239,21 @@
IconShape.DEFAULT_PATH_SIZE);
}
- public DeviceProfile copy(Context context) {
+ public Builder toBuilder(Context context) {
Point size = new Point(availableWidthPx, availableHeightPx);
- return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape,
- isMultiWindowMode);
+ return new Builder(context, inv, mInfo)
+ .setSizeRange(size, size)
+ .setSize(widthPx, heightPx)
+ .setMultiWindowMode(isMultiWindowMode);
}
+ public DeviceProfile copy(Context context) {
+ return toBuilder(context).build();
+ }
+
+ /**
+ * TODO: Move this to the builder as part of setMultiWindowMode
+ */
public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
// We take the minimum sizes of this profile and it's multi-window variant to ensure that
// the system decor is always excluded.
@@ -253,8 +262,11 @@
// In multi-window mode, we can have widthPx = availableWidthPx
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
// widthPx and heightPx values where it's needed.
- DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
- isLandscape, true);
+ DeviceProfile profile = toBuilder(context)
+ .setSizeRange(mwSize, mwSize)
+ .setSize(mwSize.x, mwSize.y)
+ .setMultiWindowMode(true)
+ .build();
// If there isn't enough vertical cell padding with the labels displayed, hide the labels.
float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
@@ -289,27 +301,30 @@
iconTextSizePx = 0;
iconDrawablePaddingPx = 0;
cellHeightPx = iconSizePx;
+ autoResizeAllAppsCells();
+ }
- // In normal cases, All Apps cell height should equal the Workspace cell height.
- // Since we are removing labels from the Workspace, we need to manually compute the
- // All Apps cell height.
+ /**
+ * Re-computes the all-apps cell size to be independent of workspace
+ */
+ public void autoResizeAllAppsCells() {
int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
+ Utilities.calculateTextHeight(allAppsIconTextSizePx)
+ topBottomPadding * 2;
}
- private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
- updateIconSize(1f, res, dm);
+ private void updateAvailableDimensions(Resources res) {
+ updateIconSize(1f, res);
// Check to see if the icons fit within the available height. If not, then scale down.
float usedHeight = (cellHeightPx * inv.numRows);
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
if (usedHeight > maxHeight) {
float scale = maxHeight / usedHeight;
- updateIconSize(scale, res, dm);
+ updateIconSize(scale, res);
}
- updateAvailableFolderCellDimensions(dm, res);
+ updateAvailableFolderCellDimensions(res);
}
/**
@@ -317,12 +332,13 @@
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
- private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
+ private void updateIconSize(float scale, Resources res) {
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
- iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
- iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
+ iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics)
+ * scale));
+ iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
cellHeightPx = iconSizePx + iconDrawablePaddingPx
@@ -340,8 +356,8 @@
// All apps
if (allAppsHasDifferentNumColumns()) {
- allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm);
- allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm);
+ allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics);
+ allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
} else {
@@ -381,12 +397,12 @@
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
}
- private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
+ private void updateAvailableFolderCellDimensions(Resources res) {
int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
+ res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
+ Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
- updateFolderCellSize(1f, dm, res);
+ updateFolderCellSize(1f, res);
// Don't let the folder get too close to the edges of the screen.
int folderMargin = edgeMarginPx * 2;
@@ -405,12 +421,12 @@
float scale = Math.min(scaleX, scaleY);
if (scale < 1f) {
- updateFolderCellSize(scale, dm, res);
+ updateFolderCellSize(scale, res);
}
}
- private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
- folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale);
+ private void updateFolderCellSize(float scale, Resources res) {
+ folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale);
folderChildTextSizePx =
(int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
@@ -627,4 +643,55 @@
*/
void onDeviceProfileChanged(DeviceProfile dp);
}
+
+ public static class Builder {
+ private Context mContext;
+ private InvariantDeviceProfile mInv;
+ private DefaultDisplay.Info mInfo;
+
+ private Point mMinSize, mMaxSize;
+ private int mWidth, mHeight;
+
+ private boolean mIsLandscape;
+ private boolean mIsMultiWindowMode = false;
+ private boolean mTransposeLayoutWithOrientation;
+
+ public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) {
+ mContext = context;
+ mInv = inv;
+ mInfo = info;
+ mTransposeLayoutWithOrientation = context.getResources()
+ .getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ }
+
+ public Builder setSizeRange(Point minSize, Point maxSize) {
+ mMinSize = minSize;
+ mMaxSize = maxSize;
+ return this;
+ }
+
+ public Builder setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mIsLandscape = mWidth > mHeight;
+ return this;
+ }
+
+ public Builder setMultiWindowMode(boolean isMultiWindowMode) {
+ mIsMultiWindowMode = isMultiWindowMode;
+ return this;
+ }
+
+ public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
+ mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
+ return this;
+ }
+
+ public DeviceProfile build() {
+ return new DeviceProfile(mContext, mInv, mInfo, mMinSize, mMaxSize,
+ mWidth, mHeight, mIsLandscape, mIsMultiWindowMode,
+ mTransposeLayoutWithOrientation);
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index d2d0863..63b90ae 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -28,6 +29,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -68,6 +70,9 @@
public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
+ public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
+ public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
+
private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
@@ -159,10 +164,16 @@
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
- String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
- ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
- : null;
- initGrid(context, gridName);
+ String gridName = getCurrentGridName(context);
+ String newGridName = initGrid(context, gridName);
+ if (!newGridName.equals(gridName)) {
+ Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply();
+ }
+ Utilities.getPrefs(context).edit()
+ .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, numHotseatIcons)
+ .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(numColumns, numRows))
+ .apply();
+
mConfigMonitor = new ConfigMonitor(context,
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
mOverlayMonitor = new OverlayMonitor(context);
@@ -178,17 +189,36 @@
}
}
-/**
+ /**
* This constructor should NOT have any monitors by design.
*/
public InvariantDeviceProfile(Context context, Display display) {
- initGrid(context, null, new Info(display));
+ // Ensure that the main device profile is initialized
+ InvariantDeviceProfile originalProfile = INSTANCE.get(context);
+ String gridName = getCurrentGridName(context);
+
+ // Get the display info based on default display and interpolate it to existing display
+ DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
+ DefaultDisplay.INSTANCE.get(context).getInfo(),
+ getPredefinedDeviceProfiles(context, gridName));
+
+ Info myInfo = new Info(display);
+ DisplayOption myDisplayOption = invDistWeightedInterpolate(
+ myInfo, getPredefinedDeviceProfiles(context, gridName));
+
+ DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
+ .add(myDisplayOption);
+ result.iconSize = defaultDisplayOption.iconSize;
+ result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
+ result.allAppsIconSize = Math.min(
+ defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+ initGrid(context, myInfo, result);
}
public static String getCurrentGridName(Context context) {
- return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
- ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
- : null;
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ return prefs.getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+ ? prefs.getString(KEY_IDP_GRID_NAME, null) : null;
}
/**
@@ -203,27 +233,17 @@
}
private String initGrid(Context context, String gridName) {
- return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo());
+ DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+ ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
+
+ DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
+ initGrid(context, displayInfo, displayOption);
+ return displayOption.grid.name;
}
- private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) {
- Point smallestSize = new Point(displayInfo.smallestSize);
- Point largestSize = new Point(displayInfo.largestSize);
-
- ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
- // This guarantees that width < height
- float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
- displayInfo.metrics);
- float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
- displayInfo.metrics);
- // Sort the profiles based on the closeness to the device size
- Collections.sort(allOptions, (a, b) ->
- Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
- dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
- DisplayOption interpolatedDisplayOption =
- invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
-
- GridOption closestProfile = allOptions.get(0).grid;
+ private void initGrid(
+ Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) {
+ GridOption closestProfile = displayOption.grid;
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numHotseatIcons = closestProfile.numHotseatIcons;
@@ -236,21 +256,16 @@
mExtraAttrs = closestProfile.extraAttrs;
- if (!closestProfile.name.equals(gridName)) {
- Utilities.getPrefs(context).edit()
- .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
- }
-
- iconSize = interpolatedDisplayOption.iconSize;
+ iconSize = displayOption.iconSize;
iconShapePath = getIconShapePath(context);
- landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
+ landscapeIconSize = displayOption.landscapeIconSize;
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
- iconTextSize = interpolatedDisplayOption.iconTextSize;
+ iconTextSize = displayOption.iconTextSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
- allAppsIconSize = interpolatedDisplayOption.allAppsIconSize;
- allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize;
+ allAppsIconSize = displayOption.allAppsIconSize;
+ allAppsIconTextSize = displayOption.allAppsIconTextSize;
} else {
allAppsIconSize = iconSize;
allAppsIconTextSize = iconTextSize;
@@ -266,10 +281,12 @@
int smallSide = Math.min(realSize.x, realSize.y);
int largeSide = Math.max(realSize.x, realSize.y);
- landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
- largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
- portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
- smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
+ DeviceProfile.Builder builder = new DeviceProfile.Builder(context, this, displayInfo)
+ .setSizeRange(new Point(displayInfo.smallestSize),
+ new Point(displayInfo.largestSize));
+
+ landscapeProfile = builder.setSize(largeSide, smallSide).build();
+ portraitProfile = builder.setSize(smallSide, largeSide).build();
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
@@ -283,8 +300,6 @@
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
-
- return closestProfile.name;
}
@Nullable
@@ -454,6 +469,41 @@
}
@VisibleForTesting
+ static DisplayOption invDistWeightedInterpolate(
+ DefaultDisplay.Info displayInfo, ArrayList<DisplayOption> points) {
+ Point smallestSize = new Point(displayInfo.smallestSize);
+ Point largestSize = new Point(displayInfo.largestSize);
+
+ // This guarantees that width < height
+ float width = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
+ displayInfo.metrics);
+ float height = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
+ displayInfo.metrics);
+
+ // Sort the profiles based on the closeness to the device size
+ Collections.sort(points, (a, b) ->
+ Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
+ dist(width, height, b.minWidthDps, b.minHeightDps)));
+
+ GridOption closestOption = points.get(0).grid;
+ float weights = 0;
+
+ DisplayOption p = points.get(0);
+ if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
+ return p;
+ }
+
+ DisplayOption out = new DisplayOption(closestOption);
+ for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
+ p = points.get(i);
+ float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
+ weights += w;
+ out.add(new DisplayOption().add(p).multiply(w));
+ }
+ return out.multiply(1.0f / weights);
+ }
+
+ @VisibleForTesting
static DisplayOption invDistWeightedInterpolate(float width, float height,
ArrayList<DisplayOption> points) {
float weights = 0;
@@ -573,7 +623,6 @@
private static final class DisplayOption {
private final GridOption grid;
- private final String name;
private final float minWidthDps;
private final float minHeightDps;
private final boolean canBeDefault;
@@ -590,7 +639,6 @@
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ProfileDisplayOption);
- name = a.getString(R.styleable.ProfileDisplayOption_name);
minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
canBeDefault = a.getBoolean(
@@ -609,8 +657,11 @@
}
DisplayOption() {
- grid = null;
- name = null;
+ this(null);
+ }
+
+ DisplayOption(GridOption grid) {
+ this.grid = grid;
minWidthDps = 0;
minHeightDps = 0;
canBeDefault = false;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bf05a24..c4eab8f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -25,6 +25,8 @@
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.NO_OFFSET;
+import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
import static com.android.launcher3.Utilities.postAsyncCallback;
@@ -85,7 +87,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
@@ -2698,10 +2699,6 @@
return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
}
- protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
- return new ScaleAndTranslation(1.1f, 0f, 0f);
- }
-
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
public void onDragLayerHierarchyChanged() { }
@@ -2724,6 +2721,14 @@
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
+
+ /**
+ * @see LauncherState#getOverviewScaleAndOffset(Launcher)
+ */
+ public float[] getNormalOverviewScaleAndOffset() {
+ return new float[] {NO_SCALE, NO_OFFSET};
+ }
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -2735,6 +2740,7 @@
return (T) activityContext;
}
+
/**
* Callback for listening for onResume
*/
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8d20bd6..308d84f 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -146,7 +146,8 @@
*/
protected synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
- mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext());
+ mOpenHelper = DatabaseHelper.createDatabaseHelper(
+ getContext(), false /* forMigration */);
if (RestoreDbTask.isPending(getContext())) {
if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -430,7 +431,8 @@
InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
Favorites.TMP_TABLE,
() -> mOpenHelper,
- () -> DatabaseHelper.createDatabaseHelper(getContext())));
+ () -> DatabaseHelper.createDatabaseHelper(
+ getContext(), true /* forMigration */)));
return result;
}
}
@@ -441,7 +443,8 @@
prepForMigration(
arg /* dbFile */,
Favorites.PREVIEW_TABLE_NAME,
- () -> DatabaseHelper.createDatabaseHelper(getContext(), arg),
+ () -> DatabaseHelper.createDatabaseHelper(
+ getContext(), arg, true /* forMigration */),
() -> mOpenHelper));
return result;
}
@@ -609,20 +612,22 @@
public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
LayoutParserCallback {
private final Context mContext;
+ private final boolean mForMigration;
private int mMaxItemId = -1;
private int mMaxScreenId = -1;
private boolean mBackupTableExists;
- static DatabaseHelper createDatabaseHelper(Context context) {
- return createDatabaseHelper(context, null);
+ static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
+ return createDatabaseHelper(context, null, forMigration);
}
- static DatabaseHelper createDatabaseHelper(Context context, String dbName) {
+ static DatabaseHelper createDatabaseHelper(Context context, String dbName,
+ boolean forMigration) {
if (dbName == null) {
dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
context).dbFile : LauncherFiles.LAUNCHER_DB;
}
- DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName);
+ DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
// would eventually be able to recover.
@@ -643,9 +648,10 @@
/**
* Constructor used in tests and for restore.
*/
- public DatabaseHelper(Context context, String dbName) {
+ public DatabaseHelper(Context context, String dbName, boolean forMigration) {
super(context, dbName, SCHEMA_VERSION);
mContext = context;
+ mForMigration = forMigration;
}
protected void initIds() {
@@ -670,7 +676,9 @@
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
- onEmptyDbCreated();
+ if (!mForMigration) {
+ onEmptyDbCreated();
+ }
}
protected void onAddOrDeleteOp(SQLiteDatabase db) {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 504666a..54d8f0d 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -90,6 +90,9 @@
protected static final int FLAG_HIDE_BACK_BUTTON = 1 << 8;
protected static final int FLAG_HAS_SYS_UI_SCRIM = 1 << 9;
+ public static final float NO_OFFSET = 0;
+ public static final float NO_SCALE = 1;
+
protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
new PageAlphaProvider(ACCEL_2) {
@Override
@@ -220,7 +223,7 @@
public abstract int getTransitionDuration(Launcher launcher);
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1, 0, 0);
+ return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
}
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
@@ -228,12 +231,18 @@
return getWorkspaceScaleAndTranslation(launcher);
}
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return launcher.getOverviewScaleAndTranslationForNormalState();
+ /**
+ * Returns an array of two elements.
+ * The first specifies the scale for the overview
+ * The second is the factor ([0, 1], 0 => center-screen; 1 => offscreen) by which overview
+ * should be shifted horizontally.
+ */
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return launcher.getNormalOverviewScaleAndOffset();
}
public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1, 0, 0);
+ return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
}
public float getOverviewFullscreenProgress() {
@@ -276,7 +285,14 @@
*
* 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
*/
- public float getDepth(Context context) {
+ public final float getDepth(Context context) {
+ if (BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode) {
+ return 0;
+ }
+ return getDepthUnchecked(context);
+ }
+
+ protected float getDepthUnchecked(Context context) {
return 0f;
}
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
new file mode 100644
index 0000000..047fb01
--- /dev/null
+++ b/src/com/android/launcher3/Reorderable.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import android.graphics.PointF;
+import android.view.View;
+
+public interface Reorderable {
+
+ /**
+ * Set the offset related to reorder hint and bounce animations
+ */
+ 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);
+
+ /**
+ * 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();
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 3ee1293..f97eb28 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -15,206 +15,90 @@
*/
package com.android.launcher3.allapps;
-import com.android.launcher3.util.Thunk;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import java.util.HashSet;
-import java.util.List;
+import com.android.launcher3.allapps.AlphabeticalAppsList.FastScrollSectionInfo;
-import androidx.recyclerview.widget.RecyclerView;
+public class AllAppsFastScrollHelper {
-public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
+ private static final int NO_POSITION = -1;
- private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
- private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
+ private int mTargetFastScrollPosition = NO_POSITION;
private AllAppsRecyclerView mRv;
- private AlphabeticalAppsList mApps;
+ private ViewHolder mLastSelectedViewHolder;
- // Keeps track of the current and targeted fast scroll section (the section to scroll to after
- // the initial delay)
- int mTargetFastScrollPosition = -1;
- @Thunk String mCurrentFastScrollSection;
- @Thunk String mTargetFastScrollSection;
-
- // The settled states affect the delay before the fast scroll animation is applied
- private boolean mHasFastScrollTouchSettled;
- private boolean mHasFastScrollTouchSettledAtLeastOnce;
-
- // Set of all views animated during fast scroll. We keep track of these ourselves since there
- // is no way to reset a view once it gets scrapped or recycled without other hacks
- private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
-
- // Smooth fast-scroll animation frames
- @Thunk int mFastScrollFrameIndex;
- @Thunk final int[] mFastScrollFrames = new int[10];
-
- /**
- * This runnable runs a single frame of the smooth scroll animation and posts the next frame
- * if necessary.
- */
- @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
- @Override
- public void run() {
- if (mFastScrollFrameIndex < mFastScrollFrames.length) {
- mRv.scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
- mFastScrollFrameIndex++;
- mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
- }
- }
- };
-
- /**
- * This runnable updates the current fast scroll section to the target fastscroll section.
- */
- Runnable mFastScrollToTargetSectionRunnable = new Runnable() {
- @Override
- public void run() {
- // Update to the target section
- mCurrentFastScrollSection = mTargetFastScrollSection;
- mHasFastScrollTouchSettled = true;
- mHasFastScrollTouchSettledAtLeastOnce = true;
- updateTrackedViewsFastScrollFocusState();
- }
- };
-
- public AllAppsFastScrollHelper(AllAppsRecyclerView rv, AlphabeticalAppsList apps) {
+ public AllAppsFastScrollHelper(AllAppsRecyclerView rv) {
mRv = rv;
- mApps = apps;
- }
-
- public void onSetAdapter(AllAppsGridAdapter adapter) {
- adapter.setBindViewCallback(this);
}
/**
* Smooth scrolls the recycler view to the given section.
- *
- * @return whether the fastscroller can scroll to the new section.
*/
- public boolean smoothScrollToSection(int scrollY, int availableScrollHeight,
- AlphabeticalAppsList.FastScrollSectionInfo info) {
- if (mTargetFastScrollPosition != info.fastScrollToItem.position) {
- mTargetFastScrollPosition = info.fastScrollToItem.position;
- smoothSnapToPosition(scrollY, availableScrollHeight, info);
- return true;
+ public void smoothScrollToSection(FastScrollSectionInfo info) {
+ if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+ return;
}
- return false;
- }
-
- /**
- * Smoothly snaps to a given position. We do this manually by calculating the keyframes
- * ourselves and animating the scroll on the recycler view.
- */
- private void smoothSnapToPosition(int scrollY, int availableScrollHeight,
- AlphabeticalAppsList.FastScrollSectionInfo info) {
- mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
- mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
- trackAllChildViews();
- if (mHasFastScrollTouchSettled) {
- // In this case, the user has already settled once (and the fast scroll state has
- // animated) and they are just fine-tuning their section from the last section, so
- // we should make it feel fast and update immediately.
- mCurrentFastScrollSection = info.sectionName;
- mTargetFastScrollSection = null;
- updateTrackedViewsFastScrollFocusState();
- } else {
- // Otherwise, the user has scrubbed really far, and we don't want to distract the user
- // with the flashing fast scroll state change animation in addition to the fast scroll
- // section popup, so reset the views to normal, and wait for the touch to settle again
- // before animating the fast scroll state.
- mCurrentFastScrollSection = null;
- mTargetFastScrollSection = info.sectionName;
- mHasFastScrollTouchSettled = false;
- updateTrackedViewsFastScrollFocusState();
-
- // Delay scrolling to a new section until after some duration. If the user has been
- // scrubbing a while and makes multiple big jumps, then reduce the time needed for the
- // fast scroll to settle so it doesn't feel so long.
- mRv.postDelayed(mFastScrollToTargetSectionRunnable,
- mHasFastScrollTouchSettledAtLeastOnce ?
- REPEAT_TOUCH_SETTLING_DURATION :
- INITIAL_TOUCH_SETTLING_DURATION);
- }
-
- // Calculate the full animation from the current scroll position to the final scroll
- // position, and then run the animation for the duration. If we are scrolling to the
- // first fast scroll section, then just scroll to the top of the list itself.
- List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
- mApps.getFastScrollerSections();
- int newPosition = info.fastScrollToItem.position;
- int newScrollY = fastScrollSections.size() > 0 && fastScrollSections.get(0) == info
- ? 0
- : Math.min(availableScrollHeight, mRv.getCurrentScrollY(newPosition, 0));
- int numFrames = mFastScrollFrames.length;
- int deltaY = newScrollY - scrollY;
- float ySign = Math.signum(deltaY);
- int step = (int) (ySign * Math.ceil((float) Math.abs(deltaY) / numFrames));
- for (int i = 0; i < numFrames; i++) {
- // TODO(winsonc): We can interpolate this as well.
- mFastScrollFrames[i] = (int) (ySign * Math.min(Math.abs(step), Math.abs(deltaY)));
- deltaY -= step;
- }
- mFastScrollFrameIndex = 0;
- mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
+ mTargetFastScrollPosition = info.fastScrollToItem.position;
+ mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
}
public void onFastScrollCompleted() {
- // TODO(winsonc): Handle the case when the user scrolls and releases before the animation
- // runs
-
- // Stop animating the fast scroll position and state
- mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
- mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
-
- // Reset the tracking variables
- mHasFastScrollTouchSettled = false;
- mHasFastScrollTouchSettledAtLeastOnce = false;
- mCurrentFastScrollSection = null;
- mTargetFastScrollSection = null;
- mTargetFastScrollPosition = -1;
-
- updateTrackedViewsFastScrollFocusState();
- mTrackedFastScrollViews.clear();
+ mTargetFastScrollPosition = NO_POSITION;
+ setLastHolderSelected(false);
+ mLastSelectedViewHolder = null;
}
- @Override
- public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
- // Update newly bound views to the current fast scroll state if we are fast scrolling
- if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
- mTrackedFastScrollViews.add(holder);
+
+ private void setLastHolderSelected(boolean isSelected) {
+ if (mLastSelectedViewHolder != null) {
+ mLastSelectedViewHolder.itemView.setActivated(isSelected);
+ mLastSelectedViewHolder.setIsRecyclable(!isSelected);
}
}
- /**
- * Starts tracking all the recycler view's children which are FastScrollFocusableViews.
- */
- private void trackAllChildViews() {
- int childCount = mRv.getChildCount();
- for (int i = 0; i < childCount; i++) {
- RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
- if (viewHolder != null) {
- mTrackedFastScrollViews.add(viewHolder);
- }
- }
- }
+ private class MyScroller extends LinearSmoothScroller {
- /**
- * Updates the fast scroll focus on all the children.
- */
- private void updateTrackedViewsFastScrollFocusState() {
- for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
- int pos = viewHolder.getAdapterPosition();
- boolean isActive = false;
- if (mCurrentFastScrollSection != null
- && pos > RecyclerView.NO_POSITION
- && pos < mApps.getAdapterItems().size()) {
- AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
- isActive = item != null &&
- mCurrentFastScrollSection.equals(item.sectionName) &&
- item.position == mTargetFastScrollPosition;
+ private final int mTargetPosition;
+
+ public MyScroller(int targetPosition) {
+ super(mRv.getContext());
+
+ mTargetPosition = targetPosition;
+ setTargetPosition(targetPosition);
+ }
+
+ @Override
+ protected int getVerticalSnapPreference() {
+ return SNAP_TO_START;
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mTargetPosition != mTargetFastScrollPosition) {
+ // Target changed, before the last scroll can finish
+ return;
}
- viewHolder.itemView.setActivated(isActive);
+
+ ViewHolder currentHolder = mRv.findViewHolderForAdapterPosition(mTargetPosition);
+ if (currentHolder == mLastSelectedViewHolder) {
+ return;
+ }
+
+ setLastHolderSelected(false);
+ mLastSelectedViewHolder = currentHolder;
+ setLastHolderSelected(true);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mTargetPosition != mTargetFastScrollPosition) {
+ setLastHolderSelected(false);
+ mLastSelectedViewHolder = null;
+ }
}
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 4aebec0..3afa756 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -71,11 +71,6 @@
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
-
- public interface BindViewCallback {
- void onBindView(ViewHolder holder);
- }
-
/**
* ViewHolder for each icon.
*/
@@ -186,7 +181,6 @@
private int mAppsPerRow;
- private BindViewCallback mBindViewCallback;
private OnFocusChangeListener mIconFocusListener;
// The text to show when there are no search results and no market search handler.
@@ -248,13 +242,6 @@
}
/**
- * Sets the callback for when views are bound.
- */
- public void setBindViewCallback(BindViewCallback cb) {
- mBindViewCallback = cb;
- }
-
- /**
* Returns the grid layout manager.
*/
public GridLayoutManager getLayoutManager() {
@@ -319,9 +306,6 @@
// nothing to do
break;
}
- if (mBindViewCallback != null) {
- mBindViewCallback.onBindView(holder);
- }
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 069472f..cbf02b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -53,12 +53,12 @@
public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
private AlphabeticalAppsList mApps;
- private AllAppsFastScrollHelper mFastScrollHelper;
private final int mNumAppsPerRow;
// The specific view heights that we use to calculate scroll
- private SparseIntArray mViewHeights = new SparseIntArray();
- private SparseIntArray mCachedScrollPositions = new SparseIntArray();
+ private final SparseIntArray mViewHeights = new SparseIntArray();
+ private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
+ private final AllAppsFastScrollHelper mFastScrollHelper;
// The empty-search result background
private AllAppsBackgroundDrawable mEmptySearchBackground;
@@ -85,6 +85,7 @@
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
R.dimen.all_apps_empty_search_bg_top_offset);
mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+ mFastScrollHelper = new AllAppsFastScrollHelper(this);
}
/**
@@ -92,7 +93,6 @@
*/
public void setApps(AlphabeticalAppsList apps) {
mApps = apps;
- mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
public AlphabeticalAppsList getApps() {
@@ -221,9 +221,6 @@
return "";
}
- // Stop the scroller if it is scrolling
- stopScroll();
-
// Find the fastscroll section that maps to this touch fraction
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
mApps.getFastScrollerSections();
@@ -236,10 +233,7 @@
lastInfo = info;
}
- // Update the fast scroll
- int scrollY = getCurrentScrollY();
- int availableScrollHeight = getAvailableScrollHeight();
- mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
+ mFastScrollHelper.smoothScrollToSection(lastInfo);
return lastInfo.sectionName;
}
@@ -257,7 +251,6 @@
mCachedScrollPositions.clear();
}
});
- mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
}
@Override
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index fc29a30..0648682 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -172,8 +172,7 @@
if (withDelay) {
new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS);
return;
- } else if (Launcher.ACTIVITY_TRACKER.hasPending()
- || AbstractFloatingView.getTopOpenView(launcher) != null) {
+ } else if (AbstractFloatingView.getTopOpenView(launcher) != null) {
// TODO: Move these checks to the top and call this method after invalidate handler.
return;
}
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 33262b6..6692af5 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.AsyncTask;
@@ -30,7 +31,6 @@
import android.widget.Switch;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
@@ -79,9 +79,8 @@
@Override
public void toggle() {
- Launcher launcher = Launcher.getLauncher(getContext());
// don't show tip if user uses toggle
- launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
+ Utilities.getPrefs(getContext()).edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
trySetQuietModeEnabledToAllProfilesAsync(isChecked());
}
@@ -203,9 +202,8 @@
}
private boolean shouldShowWorkSwitch() {
- Launcher launcher = Launcher.getLauncher(getContext());
- return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
- || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+ return Utilities.ATLEAST_P && (hasShortcutsPermission(getContext())
+ || getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
== PackageManager.PERMISSION_GRANTED);
}
@@ -213,12 +211,14 @@
* Shows a work tip on the Nth work tab open
*/
public void showTipifNeeded() {
- Launcher launcher = Launcher.getLauncher(getContext());
- int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
+ Context context = getContext();
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
if (tipCounter < 0) return;
if (tipCounter == 0) {
- new ArrowTipView(launcher).show(launcher.getString(R.string.work_switch_tip), getTop());
+ new ArrowTipView(context)
+ .show(context.getString(R.string.work_switch_tip), getTop());
}
- launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
+ prefs.edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
}
}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index f12789a..e11917b 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -27,7 +27,6 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.util.FloatProperty;
import androidx.annotation.Nullable;
@@ -64,19 +63,6 @@
return new AnimatorPlaybackController(anim, duration, childAnims);
}
- private static final FloatProperty<ValueAnimator> CURRENT_PLAY_TIME =
- new FloatProperty<ValueAnimator>("current-play-time") {
- @Override
- public void setValue(ValueAnimator animator, float v) {
- animator.setCurrentPlayTime((long) v);
- }
-
- @Override
- public Float get(ValueAnimator animator) {
- return (float) animator.getCurrentPlayTime();
- }
- };
-
// Progress factor after which an animation is considered almost completed.
private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f;
@@ -177,21 +163,22 @@
long springDuration = animationDuration;
for (Holder h : mChildAnimations) {
if ((h.springProperty.flags & springFlag) != 0) {
- SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME)
- .setStartValue(clampDuration(mCurrentFraction))
- .setEndValue(goingToEnd ? h.anim.getDuration() : 0)
- .setStartVelocity(scaledVelocity * h.anim.getDuration())
+ SpringAnimationBuilder s = new SpringAnimationBuilder(context)
+ .setStartValue(mCurrentFraction)
+ .setEndValue(goingToEnd ? 1 : 0)
+ .setStartVelocity(scaledVelocity)
.setMinimumVisibleChange(scaleInverse)
.setDampingRatio(h.springProperty.mDampingRatio)
- .setStiffness(h.springProperty.mStiffness);
+ .setStiffness(h.springProperty.mStiffness)
+ .computeParams();
- long expectedDurationL = s.build(context).getDuration();
+ long expectedDurationL = s.getDuration();
springDuration = Math.max(expectedDurationL, springDuration);
float expectedDuration = expectedDurationL;
- h.setter = (a, l) ->
- s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
- h.anim.setInterpolator(LINEAR);
+ h.setter = (a, l) -> a.setCurrentFraction(
+ mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
+ h.anim.setInterpolator(s::getInterpolatedValue);
}
}
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index f22a9f0..bc77aab 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -15,7 +15,9 @@
*/
package com.android.launcher3.anim;
-import android.animation.ObjectAnimator;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.ValueAnimator;
import android.content.Context;
import android.util.FloatProperty;
@@ -28,10 +30,9 @@
* Utility class to build an object animator which follows the same path as a spring animation for
* an underdamped spring.
*/
-public class SpringAnimationBuilder<T> extends FloatProperty<T> {
+public class SpringAnimationBuilder {
- private final T mTarget;
- private final FloatProperty<T> mProperty;
+ private final Context mContext;
private float mStartValue;
private float mEndValue;
@@ -64,27 +65,23 @@
private double mValueThreshold;
private double mVelocityThreshold;
- private float mCurrentTime = 0;
+ private float mDuration = 0;
- public SpringAnimationBuilder(T target, FloatProperty<T> property) {
- super("dynamic-spring-property");
- mTarget = target;
- mProperty = property;
-
- mStartValue = mProperty.get(target);
+ public SpringAnimationBuilder(Context context) {
+ mContext = context;
}
- public SpringAnimationBuilder<T> setEndValue(float value) {
+ public SpringAnimationBuilder setEndValue(float value) {
mEndValue = value;
return this;
}
- public SpringAnimationBuilder<T> setStartValue(float value) {
+ public SpringAnimationBuilder setStartValue(float value) {
mStartValue = value;
return this;
}
- public SpringAnimationBuilder<T> setValues(float... values) {
+ public SpringAnimationBuilder setValues(float... values) {
if (values.length > 1) {
mStartValue = values[0];
mEndValue = values[values.length - 1];
@@ -94,7 +91,7 @@
return this;
}
- public SpringAnimationBuilder<T> setStiffness(
+ public SpringAnimationBuilder setStiffness(
@FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
if (stiffness <= 0) {
throw new IllegalArgumentException("Spring stiffness constant must be positive.");
@@ -103,7 +100,7 @@
return this;
}
- public SpringAnimationBuilder<T> setDampingRatio(
+ public SpringAnimationBuilder setDampingRatio(
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
float dampingRatio) {
if (dampingRatio <= 0 || dampingRatio >= 1) {
@@ -113,7 +110,7 @@
return this;
}
- public SpringAnimationBuilder<T> setMinimumVisibleChange(
+ public SpringAnimationBuilder setMinimumVisibleChange(
@FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) {
if (minimumVisibleChange <= 0) {
throw new IllegalArgumentException("Minimum visible change must be positive.");
@@ -122,25 +119,21 @@
return this;
}
- public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) {
+ public SpringAnimationBuilder setStartVelocity(float startVelocity) {
mVelocity = startVelocity;
return this;
}
- @Override
- public void setValue(T object, float time) {
- mCurrentTime = time;
- mProperty.setValue(
- object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue);
+ public float getInterpolatedValue(float fraction) {
+ return getValue(mDuration * fraction);
}
- @Override
- public Float get(T t) {
- return mCurrentTime;
+ private float getValue(float time) {
+ return (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue;
}
- public ObjectAnimator build(Context context) {
- int singleFrameMs = DefaultDisplay.getSingleFrameMs(context);
+ public SpringAnimationBuilder computeParams() {
+ int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext);
double naturalFreq = Math.sqrt(mStiffness);
double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
@@ -187,12 +180,21 @@
}
} while (true);
+ mDuration = (float) duration;
+ return this;
+ }
- long durationMs = (long) (1000.0 * duration);
- ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
- animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
- animator.addListener(AnimationSuccessListener.forRunnable(
- () -> mProperty.setValue(mTarget, mEndValue)));
+ public long getDuration() {
+ return (long) (1000.0 * mDuration);
+ }
+
+ public <T> ValueAnimator build(T target, FloatProperty<T> property) {
+ computeParams();
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0, mDuration);
+ animator.setDuration(getDuration()).setInterpolator(LINEAR);
+ animator.addUpdateListener(anim ->
+ property.set(target, getInterpolatedValue(anim.getAnimatedFraction())));
return animator;
}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 737c97b..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,9 +75,6 @@
}
public static void sendScrollFinishedEventToTest(Context context) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
- }
final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
if (accessibilityManager == null) return;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index bcd91da..65d3cd2 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -159,15 +159,12 @@
"ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
"Always use hardware optimization for folder animations.");
- public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
- FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
- "Launch/close apps without rotation animation. Fix Launcher to portrait");
-
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
flag.initialize(context);
}
+ sDebugFlags.sort((f1, f2) -> f1.key.compareToIgnoreCase(f2.key));
}
}
@@ -178,10 +175,20 @@
}
public static void dump(PrintWriter pw) {
- pw.println("FeatureFlags:");
+ pw.println("DeviceFlags:");
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
- pw.println(" " + flag.key + "=" + flag.get());
+ if (flag instanceof DeviceFlag) {
+ pw.println(" " + flag.toString());
+ }
+ }
+ }
+ pw.println("DebugFlags:");
+ synchronized (sDebugFlags) {
+ for (DebugFlag flag : sDebugFlags) {
+ if (!(flag instanceof DeviceFlag)) {
+ pw.println(" " + flag.toString());
+ }
}
}
}
@@ -202,13 +209,11 @@
@Override
public String toString() {
- return appendProps(new StringBuilder()
- .append(getClass().getSimpleName()).append('{'))
- .append('}').toString();
+ return appendProps(new StringBuilder()).toString();
}
protected StringBuilder appendProps(StringBuilder src) {
- return src.append("key=").append(key).append(", defaultValue=").append(defaultValue);
+ return src.append(key).append(", defaultValue=").append(defaultValue);
}
public void addChangeListener(Context context, Runnable r) { }
@@ -240,8 +245,7 @@
@Override
protected StringBuilder appendProps(StringBuilder src) {
- return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue)
- .append(", description=").append(description);
+ return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue);
}
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 6c40b8a..0df6713 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -170,16 +170,14 @@
}
}, null, View.DRAG_FLAG_GLOBAL);
-
- Intent homeIntent = listener.addToIntent(
- new Intent(Intent.ACTION_MAIN)
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-
- Launcher.ACTIVITY_TRACKER.schedule(listener);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Launcher.ACTIVITY_TRACKER.runCallbackWhenActivityExists(listener, homeIntent);
startActivity(homeIntent,
- ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+ ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
+ .toBundle());
mFinishOnPause = true;
return false;
}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index e47a16f..707fd06 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -161,7 +161,6 @@
}
protected void postCleanup() {
- Launcher.ACTIVITY_TRACKER.clearReference(this);
if (mLauncher != null) {
// Remove any drag params from the launcher intent since the drag operation is complete.
Intent newIntent = new Intent(mLauncher.getIntent());
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4fe1d1a..c287190 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -164,6 +164,7 @@
@Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
private AnimatorSet mCurrentAnimator;
+ private boolean mIsAnimatingClosed = false;
protected final Launcher mLauncher;
protected DragController mDragController;
@@ -331,7 +332,7 @@
.map(info -> info.suggestedFolderNames)
.map(folderNames -> (FolderNameInfo[]) folderNames
.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
- .ifPresent(nameInfos -> showLabelSuggestions(nameInfos));
+ .ifPresent(this::showLabelSuggestions);
}
mFolderName.setHint("");
mIsEditingName = true;
@@ -729,15 +730,24 @@
}
private void animateClosed() {
+ if (mIsAnimatingClosed) {
+ return;
+ }
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
a.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingClosed = true;
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
closeComplete(true);
announceAccessibilityChanges();
+ mIsAnimatingClosed = false;
}
});
startAnimation(a);
@@ -1440,7 +1450,7 @@
if (hasFocus) {
startEditingFolderName();
} else {
- logEditFolderLabel();
+ logCurrentFolderLabelState();
mFolderName.dispatchBackKey();
}
}
@@ -1639,7 +1649,7 @@
return mContent;
}
- private void logEditFolderLabel() {
+ protected void logCurrentFolderLabelState() {
LauncherEvent launcherEvent = LauncherEvent.newBuilder()
.setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
.addSrcTarget(newEditTextTargetBuilder()
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e29971e..b875a0b 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -26,6 +26,7 @@
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;
@@ -49,6 +50,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators;
@@ -79,7 +81,7 @@
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
- DraggableView {
+ DraggableView, Reorderable {
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
@@ -119,6 +121,10 @@
private float mDotScale;
private Animator mDotScaleAnim;
+ 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
= new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
@Override
@@ -226,16 +232,6 @@
mBackground.getBounds(outBounds);
}
- @Override
- public int getViewType() {
- return DRAGGABLE_ICON;
- }
-
- @Override
- public void getVisualDragBounds(Rect bounds) {
- getPreviewBounds(bounds);
- }
-
public float getBackgroundStrokeWidth() {
return mBackground.getStrokeWidth();
}
@@ -421,6 +417,7 @@
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
setLabelSuggestion(nameInfos);
+ mFolder.logCurrentFolderLabelState();
invalidate();
}, DROP_IN_ANIMATION_DURATION);
}
@@ -443,7 +440,6 @@
onTitleChanged(mInfo.title);
mFolder.mFolderName.setText(mInfo.title);
mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
- // TODO: Add logging while folder creation.
}
@@ -716,4 +712,53 @@
public void onFolderClose(int currentPage) {
mPreviewItemManager.onFolderClose(currentPage);
}
+
+ private void updateTranslation() {
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ }
+
+ 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);
+ }
+
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_ICON;
+ }
+
+ @Override
+ public void getVisualDragBounds(Rect bounds) {
+ getPreviewBounds(bounds);
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
index 1287219..1841cd9 100644
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -50,6 +50,10 @@
return mLabel;
}
+ public double getScore() {
+ return mScore;
+ }
+
/**
* Used to package this object into a {@link Parcel}.
*
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index b27e4ea..e8a52bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -1,5 +1,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.Utilities.parsePoint;
@@ -53,9 +55,6 @@
private static final String TAG = "GridSizeMigrationTask";
private static final boolean DEBUG = true;
- private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
- private static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
-
// These are carefully selected weights for various item types (Math.random?), to allow for
// the least absurd migration experience.
private static final float WT_SHORTCUT = 1;
@@ -894,8 +893,7 @@
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
- || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons);
+ || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
}
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 1c44fc3..4a28218 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
@@ -63,9 +65,6 @@
*/
public class GridSizeMigrationTaskV2 {
- public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
- public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
-
private static final String TAG = "GridSizeMigrationTaskV2";
private static final boolean DEBUG = true;
@@ -110,8 +109,7 @@
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
- || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons);
+ || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
}
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
@@ -148,14 +146,6 @@
SharedPreferences prefs = Utilities.getPrefs(context);
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
- if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
- && idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons)) {
- // Skip if workspace and hotseat sizes have not changed.
- return true;
- }
-
HashSet<String> validPackages = getValidPackages(context);
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 0d97596..2fa6051 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -42,7 +42,6 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -86,10 +85,6 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " +
- Arrays.toString(mPackages));
- }
final Context context = app.getContext();
final IconCache iconCache = app.getIconCache();
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index bd843e7..14f9a3e 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -37,6 +37,8 @@
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.util.ContentWriter;
+import java.util.Optional;
+
/**
* Represents an item in the launcher.
*/
@@ -248,24 +250,29 @@
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
itemBuilder.setIsWork(user != Process.myUserHandle());
- ComponentName cn = getTargetComponent();
+ Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
switch (itemType) {
case ITEM_TYPE_APPLICATION:
- itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
- .setComponentName(cn.flattenToShortString())
- .setPackageName(cn.getPackageName()));
+ itemBuilder
+ .setApplication(nullableComponent
+ .map(component -> LauncherAtom.Application.newBuilder()
+ .setComponentName(component.flattenToShortString())
+ .setPackageName(component.getPackageName()))
+ .orElse(LauncherAtom.Application.newBuilder()));
break;
case ITEM_TYPE_DEEP_SHORTCUT:
case ITEM_TYPE_SHORTCUT:
- itemBuilder.setShortcut(LauncherAtom.Shortcut.newBuilder()
- .setShortcutName(cn.flattenToShortString()));
+ itemBuilder
+ .setShortcut(nullableComponent
+ .map(component -> LauncherAtom.Shortcut.newBuilder()
+ .setShortcutName(component.flattenToShortString()))
+ .orElse(LauncherAtom.Shortcut.newBuilder()));
break;
case ITEM_TYPE_APPWIDGET:
setItemBuilder(itemBuilder);
break;
default:
break;
-
}
if (fInfo != null) {
LauncherAtom.FolderContainer.Builder folderBuilder =
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index f1f271f..dd6fc49 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -89,17 +89,16 @@
if (mDragLayer != null) {
return;
}
- InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
InvariantDeviceProfile currentDisplayIdp =
new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
- // Pick the device profile with the smaller icon size so that the cached icons are
- // shown properly
- if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
- mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
- } else {
- mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
- }
+ // Disable transpose layout and use multi-window mode so that the icons are scaled properly
+ mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
+ .toBuilder(this)
+ .setMultiWindowMode(true)
+ .setTransposeLayoutWithOrientation(false)
+ .build();
+ mDeviceProfile.autoResizeAllAppsCells();
setContentView(R.layout.secondary_launcher);
mDragLayer = findViewById(R.id.drag_layer);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index e35e884..40630d3 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -117,10 +117,12 @@
if (child == mAppsView) {
int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
+ grid.cellLayoutPaddingLeftRightPx);
- int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
+ int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
int appsWidth = Math.min(width, maxWidth);
- int appsHeight = Math.round(appsWidth * (float) height / (float) width);
+
+ int maxHeight = grid.allAppsCellHeightPx * idp.numAllAppsColumns + padding;
+ int appsHeight = Math.min(height, maxHeight);
mAppsView.measure(
makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 2e0521f..5a60f53 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -15,47 +15,43 @@
*/
package com.android.launcher3.states;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
-import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.app.Activity;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
import android.provider.Settings;
+import android.util.Log;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.UiThreadHelper;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Utility class to manage launcher rotation
*/
public class RotationHelper implements OnSharedPreferenceChangeListener {
+ private static final String TAG = "RotationHelper";
+
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
- public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
private final ContentResolver mContentResolver;
+ private boolean mSystemAutoRotateEnabled;
- /**
- * Listener to receive changes when {@link #FIXED_ROTATION_TRANSFORM_SETTING_NAME} flag changes.
- */
- public interface ForcedRotationChangedListener {
- void onForcedRotationChanged(boolean isForcedRotation);
- }
+ private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAutoRotateSetting();
+ }
+ };
public static boolean getAllowRotationDefaultValue() {
// If the device's pixel density was scaled (usually via settings for A11y), use the
@@ -72,12 +68,9 @@
private final Activity mActivity;
private final SharedPreferences mSharedPrefs;
- private final SharedPreferences mFeatureFlagsPrefs;
private boolean mIgnoreAutoRotateSettings;
- private boolean mAutoRotateEnabled;
- private boolean mForcedRotation;
- private List<ForcedRotationChangedListener> mForcedRotationChangedListeners = new ArrayList<>();
+ private boolean mHomeRotationEnabled;
/**
* Rotation request made by
@@ -108,67 +101,35 @@
if (!mIgnoreAutoRotateSettings) {
mSharedPrefs = Utilities.getPrefs(mActivity);
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
- mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
} else {
mSharedPrefs = null;
}
mContentResolver = activity.getContentResolver();
- mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mActivity);
- mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
- updateForcedRotation(true);
}
- /**
- * @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
- * from the home developer settings. Otherwise it will not.
- * This is primarily to allow tests to set their own conditions.
- */
- private void updateForcedRotation(boolean setValueFromPrefs) {
- boolean isForcedRotation = mFeatureFlagsPrefs
- .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
- && !getAllowRotationDefaultValue();
- if (mForcedRotation == isForcedRotation) {
- return;
+ private void updateAutoRotateSetting() {
+ int autoRotateEnabled = 0;
+ try {
+ autoRotateEnabled = Settings.System.getInt(mContentResolver,
+ Settings.System.ACCELEROMETER_ROTATION);
+ } catch (Settings.SettingNotFoundException e) {
+ Log.e(TAG, "autorotate setting not found", e);
}
- if (setValueFromPrefs) {
- mForcedRotation = isForcedRotation;
- }
- UI_HELPER_EXECUTOR.execute(() -> {
- if (mActivity.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
- Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
- mForcedRotation ? 1 : 0);
- }
- });
- for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
- listener.onForcedRotationChanged(mForcedRotation);
- }
- }
- /**
- * will not be called when first registering the listener.
- */
- public void addForcedRotationCallback(ForcedRotationChangedListener listener) {
- mForcedRotationChangedListeners.add(listener);
- }
-
- public void removeForcedRotationCallback(ForcedRotationChangedListener listener) {
- mForcedRotationChangedListeners.remove(listener);
+ mSystemAutoRotateEnabled = autoRotateEnabled == 1;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
- if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
- updateForcedRotation(true);
- return;
- }
-
- boolean wasRotationEnabled = mAutoRotateEnabled;
- mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ boolean wasRotationEnabled = mHomeRotationEnabled;
+ mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
- if (mAutoRotateEnabled != wasRotationEnabled) {
+ if (mHomeRotationEnabled != wasRotationEnabled) {
notifyChange();
+ updateAutoRotateSetting();
}
}
@@ -197,10 +158,6 @@
public void forceAllowRotationForTesting(boolean allowRotation) {
mIgnoreAutoRotateSettings =
allowRotation || mActivity.getResources().getBoolean(R.bool.allow_rotation);
- // TODO(b/150214193) Tests currently expect launcher to be able to be rotated
- // Modify tests for this new behavior
- mForcedRotation = !allowRotation;
- updateForcedRotation(false);
notifyChange();
}
@@ -208,6 +165,11 @@
if (!mInitialized) {
mInitialized = true;
notifyChange();
+
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+ false, mSystemAutoRotateObserver);
+ updateAutoRotateSetting();
}
}
@@ -217,8 +179,7 @@
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
- mForcedRotationChangedListeners.clear();
- mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
}
}
@@ -228,10 +189,7 @@
}
final int activityFlags;
- if (mForcedRotation) {
- // TODO(b/150214193) Properly address this
- activityFlags = SCREEN_ORIENTATION_PORTRAIT;
- } else if (mStateHandlerRequest != REQUEST_NONE) {
+ if (mStateHandlerRequest != REQUEST_NONE) {
activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -240,7 +198,7 @@
} else if (mCurrentStateRequest == REQUEST_LOCK) {
activityFlags = SCREEN_ORIENTATION_LOCKED;
} else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
- || mAutoRotateEnabled) {
+ || mHomeRotationEnabled) {
activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
} else {
// If auto rotation is off, allow rotation on the activity, in case the user is using
@@ -253,11 +211,23 @@
}
}
+ /**
+ * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
+ * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
+ * A value of 0 means no rotation has been applied
+ */
+ public static int deltaRotation(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
@Override
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
- + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mAutoRotateEnabled=%b]",
+ + " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b,"
+ + " mSystemAutoRotateEnabled=%b]",
mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
- mIgnoreAutoRotateSettings, mAutoRotateEnabled);
+ mIgnoreAutoRotateSettings, mHomeRotationEnabled, mSystemAutoRotateEnabled);
}
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 2ba624c..b2ff69a 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -78,7 +78,7 @@
}
@Override
- public float getDepth(Context context) {
+ protected float getDepthUnchecked(Context context) {
return 0.5f;
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index e7449bb..a5a06b4 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -96,7 +96,4 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-
- public static final String APP_NOT_DISABLED = "b/139891609";
- public static final String NO_SCROLL_END_WIDGETS = "b/152354290";
}
diff --git a/src/com/android/launcher3/touch/HomeRotatedPageHandler.java b/src/com/android/launcher3/touch/HomeRotatedPageHandler.java
new file mode 100644
index 0000000..710b676
--- /dev/null
+++ b/src/com/android/launcher3/touch/HomeRotatedPageHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.touch;
+
+import android.graphics.RectF;
+import android.view.Surface;
+
+public class HomeRotatedPageHandler extends PortraitPagedViewHandler {
+ @Override
+ public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
+ if (launcherRotation == Surface.ROTATION_0) {
+ super.offsetTaskRect(rect, value, displayRotation, launcherRotation);
+ } else if (launcherRotation == Surface.ROTATION_90) {
+ if (displayRotation == Surface.ROTATION_0) {
+ rect.offset(0, value);
+ } else if (displayRotation == Surface.ROTATION_90) {
+ rect.offset(value, 0);
+ } else if (displayRotation == Surface.ROTATION_180) {
+ rect.offset(-value, 0);
+ } else {
+ rect.offset(-value, 0);
+ }
+ } else if (launcherRotation == Surface.ROTATION_270) {
+ if (displayRotation == Surface.ROTATION_0) {
+ rect.offset(0, -value);
+ } else if (displayRotation == Surface.ROTATION_90) {
+ rect.offset(value, 0);
+ } else if (displayRotation == Surface.ROTATION_180) {
+ rect.offset(0, -value);
+ } else {
+ rect.offset(value, 0);
+ }
+ } // TODO (b/149609488) handle 180 case as well
+ }
+}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index bab5747..dc50053 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -16,6 +16,7 @@
package com.android.launcher3.touch;
+import static android.widget.ListPopupWindow.WRAP_CONTENT;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -30,9 +31,9 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.PagedView;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
@@ -78,6 +79,11 @@
}
@Override
+ public boolean isLayoutNaturalToLauncher() {
+ return false;
+ }
+
+ @Override
public void adjustFloatingIconStartVelocity(PointF velocity) {
float oldX = velocity.x;
float oldY = velocity.y;
@@ -115,11 +121,6 @@
}
@Override
- public int getPrimarySize(Rect rect) {
- return rect.height();
- }
-
- @Override
public float getPrimarySize(RectF rect) {
return rect.height();
}
@@ -130,17 +131,6 @@
}
@Override
- public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
- float offscreenTranslationY = dp.heightPx - view.getPaddingTop();
- return new ScaleAndTranslation(1f, 0f, offscreenTranslationY);
- }
-
- @Override
- public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
- return scaleAndTranslation.translationY;
- }
-
- @Override
public FloatProperty<View> getPrimaryViewTranslate() {
return VIEW_TRANSLATE_Y;
}
@@ -182,7 +172,7 @@
}
@Override
- public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+ public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(0, value);
} else if (displayRotation == Surface.ROTATION_90) {
@@ -236,6 +226,33 @@
}
@Override
+ public float getTaskMenuX(float x, View thumbnailView) {
+ return thumbnailView.getMeasuredWidth() + x;
+ }
+
+ @Override
+ public float getTaskMenuY(float y, View thumbnailView) {
+ return y;
+ }
+
+ @Override
+ public int getTaskMenuWidth(View view) {
+ return view.getMeasuredHeight();
+ }
+
+ @Override
+ public int getTaskMenuLayoutOrientation() {
+ return LinearLayout.HORIZONTAL;
+ }
+
+ @Override
+ public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp) {
+ lp.width = 0;
+ lp.height = WRAP_CONTENT;
+ lp.weight = 1;
+ }
+
+ @Override
public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
boolean layoutChild) {
final int childHeight = child.getMeasuredHeight();
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 50606ec..cc15f99 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -26,9 +27,9 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
import com.android.launcher3.util.OverScroller;
@@ -42,6 +43,7 @@
PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
+ PagedOrientationHandler HOME_ROTATED = new HomeRotatedPageHandler();
interface Int2DAction<T> {
void call(T target, int x, int y);
@@ -52,16 +54,15 @@
Int2DAction<View> VIEW_SCROLL_BY = View::scrollBy;
Int2DAction<View> VIEW_SCROLL_TO = View::scrollTo;
Float2DAction<Canvas> CANVAS_TRANSLATE = Canvas::translate;
+ Float2DAction<Matrix> MATRIX_POST_TRANSLATE = Matrix::postTranslate;
+
<T> void set(T target, Int2DAction<T> action, int param);
<T> void set(T target, Float2DAction<T> action, float param);
float getPrimaryDirection(MotionEvent event, int pointerIndex);
float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
int getMeasuredSize(View view);
- int getPrimarySize(Rect rect);
float getPrimarySize(RectF rect);
int getSecondaryDimension(View view);
- LauncherState.ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view);
- float getTranslationValue(LauncherState.ScaleAndTranslation scaleAndTranslation);
FloatProperty<View> getPrimaryViewTranslate();
FloatProperty<View> getSecondaryViewTranslate();
void setPrimaryAndResetSecondaryTranslate(View view, float translation);
@@ -79,7 +80,7 @@
void setMaxScroll(AccessibilityEvent event, int maxScroll);
boolean getRecentsRtlSetting(Resources resources);
float getDegreesRotated();
- void offsetTaskRect(RectF rect, float value, int delta);
+ void offsetTaskRect(RectF rect, float value, int delta, int launcherRotation);
int getPrimaryValue(int x, int y);
int getSecondaryValue(int x, int y);
void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
@@ -89,6 +90,12 @@
void scrollerStartScroll(OverScroller scroller, int newPosition);
void getCurveProperties(PagedView view, Rect insets, CurveProperties out);
boolean isGoingUp(float displacement);
+ boolean isLayoutNaturalToLauncher();
+ float getTaskMenuX(float x, View thumbnailView);
+ float getTaskMenuY(float y, View thumbnailView);
+ int getTaskMenuWidth(View view);
+ int getTaskMenuLayoutOrientation();
+ void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
/**
* Maps the velocity from the coordinate plane of the foreground app to that
@@ -96,7 +103,6 @@
*/
void adjustFloatingIconStartVelocity(PointF velocity);
-
class CurveProperties {
public int scroll;
public int halfPageSize;
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 245138f..7c30e29 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -30,9 +30,9 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.PagedView;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
@@ -78,6 +78,11 @@
}
@Override
+ public boolean isLayoutNaturalToLauncher() {
+ return true;
+ }
+
+ @Override
public void adjustFloatingIconStartVelocity(PointF velocity) {
//no-op
}
@@ -113,11 +118,6 @@
}
@Override
- public int getPrimarySize(Rect rect) {
- return rect.width();
- }
-
- @Override
public float getPrimarySize(RectF rect) {
return rect.width();
}
@@ -128,17 +128,6 @@
}
@Override
- public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
- float offscreenTranslationX = dp.widthPx - view.getPaddingStart();
- return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
- }
-
- @Override
- public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
- return scaleAndTranslation.translationX;
- }
-
- @Override
public FloatProperty<View> getPrimaryViewTranslate() {
return VIEW_TRANSLATE_X;
}
@@ -180,7 +169,7 @@
}
@Override
- public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+ public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(value, 0);
} else if (displayRotation == Surface.ROTATION_90) {
@@ -234,6 +223,31 @@
}
@Override
+ public float getTaskMenuX(float x, View thumbnailView) {
+ return x;
+ }
+
+ @Override
+ public float getTaskMenuY(float y, View thumbnailView) {
+ return y;
+ }
+
+ @Override
+ public int getTaskMenuWidth(View view) {
+ return view.getMeasuredWidth();
+ }
+
+ @Override
+ public int getTaskMenuLayoutOrientation() {
+ return LinearLayout.VERTICAL;
+ }
+
+ @Override
+ public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp) {
+ // no-op, defaults are fine
+ }
+
+ @Override
public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
boolean layoutChild) {
final int childWidth = child.getMeasuredWidth();
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index eebd87f..7beb7f7 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -20,6 +20,7 @@
import android.graphics.PointF;
import android.graphics.RectF;
import android.view.Surface;
+import android.view.View;
import com.android.launcher3.Utilities;
@@ -36,7 +37,7 @@
}
@Override
- public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+ public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
if (displayRotation == Surface.ROTATION_0) {
rect.offset(0, value);
} else if (displayRotation == Surface.ROTATION_90) {
@@ -64,4 +65,14 @@
float oldY = velocity.y;
velocity.set(oldY, -oldX);
}
+
+ @Override
+ public float getTaskMenuX(float x, View thumbnailView) {
+ return x;
+ }
+
+ @Override
+ public float getTaskMenuY(float y, View thumbnailView) {
+ return y + thumbnailView.getMeasuredHeight();
+ }
}
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 499f655..59266b4 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -15,27 +15,23 @@
*/
package com.android.launcher3.util;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.Log;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
-import com.android.launcher3.testing.TestProtocol;
import java.lang.ref.WeakReference;
/**
* Helper class to statically track activity creation
+ * @param <T> The activity type to track
*/
-public final class ActivityTracker<T extends BaseActivity> implements Runnable {
+public final class ActivityTracker<T extends BaseActivity> {
private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
- private WeakReference<SchedulerCallback<T>> mPendingCallback = new WeakReference<>(null);
private static final String EXTRA_SCHEDULER_CALLBACK = "launcher.scheduler_callback";
@@ -51,76 +47,32 @@
}
/**
- * Schedules the callback to be notified when the activity is created.
- * @return true if the activity is already created, false otherwise
+ * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the activity is ready.
+ * If the activity is already created, this is called immediately, otherwise we add the
+ * callback as an extra on the intent, and will call init() when we get handleIntent().
+ * @param callback The callback to call init() on when the activity is ready.
+ * @param intent The intent that will be used to initialize the activity, if the activity
+ * doesn't already exist. We add the callback as an extra on this intent.
*/
- public boolean schedule(SchedulerCallback<? extends T> callback) {
- synchronized (this) {
- mPendingCallback = new WeakReference<>((SchedulerCallback<T>) callback);
- }
- if (!notifyInitIfPending()) {
- // If the activity doesn't already exist, then post and wait for the activity to start
- MAIN_EXECUTOR.execute(this);
- return false;
- }
- return true;
- }
-
- @Override
- public void run() {
- notifyInitIfPending();
- }
-
- /**
- * Notifies the pending callback if the activity is now created.
- * @return true if the activity is now created.
- */
- private boolean notifyInitIfPending() {
+ public void runCallbackWhenActivityExists(SchedulerCallback<T> callback, Intent intent) {
T activity = mCurrentActivity.get();
if (activity != null) {
- notifyInitIfPending(activity, activity.isStarted());
- return true;
+ callback.init(activity, activity.isStarted());
+ } else {
+ callback.addToIntent(intent);
}
- return false;
- }
-
- public boolean notifyInitIfPending(T activity, boolean alreadyOnHome) {
- SchedulerCallback<T> pendingCallback = mPendingCallback.get();
- if (pendingCallback != null) {
- if (!pendingCallback.init(activity, alreadyOnHome)) {
- clearReference(pendingCallback);
- }
- return true;
- }
- return false;
- }
-
- public boolean clearReference(SchedulerCallback<? extends T> handler) {
- synchronized (this) {
- if (mPendingCallback.get() == handler) {
- mPendingCallback.clear();
- return true;
- }
- return false;
- }
- }
-
- public boolean hasPending() {
- return mPendingCallback.get() != null;
}
public boolean handleCreate(T activity) {
mCurrentActivity = new WeakReference<>(activity);
- return handleIntent(activity, activity.getIntent(), false, false);
+ return handleIntent(activity, activity.getIntent(), false);
}
public boolean handleNewIntent(T activity, Intent intent) {
- return handleIntent(activity, intent, activity.isStarted(), true);
+ return handleIntent(activity, intent, activity.isStarted());
}
- private boolean handleIntent(
- T activity, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
- boolean result = false;
+ private boolean handleIntent(T activity, Intent intent, boolean alreadyOnHome) {
if (intent != null && intent.getExtras() != null) {
IBinder stateBinder = intent.getExtras().getBinder(EXTRA_SCHEDULER_CALLBACK);
if (stateBinder instanceof ObjectWrapper) {
@@ -129,19 +81,26 @@
if (!handler.init(activity, alreadyOnHome)) {
intent.getExtras().remove(EXTRA_SCHEDULER_CALLBACK);
}
- result = true;
+ return true;
}
}
- if (!result && !explicitIntent) {
- result = notifyInitIfPending(activity, alreadyOnHome);
- }
- return result;
+ return false;
}
public interface SchedulerCallback<T extends BaseActivity> {
+ /**
+ * Called when the activity is ready.
+ * @param alreadyOnHome Whether the activity is already started.
+ * @return Whether to continue receiving callbacks (i.e. if the activity is recreated).
+ */
boolean init(T activity, boolean alreadyOnHome);
+ /**
+ * Adds this callback as an extra on the intent, so we can retrieve it in handleIntent() and
+ * call {@link #init}. The intent should be used to start the activity after calling this
+ * method in order for us to get handleIntent().
+ */
default Intent addToIntent(Intent intent) {
Bundle extras = new Bundle();
extras.putBinder(EXTRA_SCHEDULER_CALLBACK, ObjectWrapper.wrap(this));
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 07f835d..a8642b0 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -16,7 +16,7 @@
package com.android.launcher3.util;
-import android.util.Property;
+import android.util.FloatProperty;
import android.view.View;
import java.util.Arrays;
@@ -26,8 +26,8 @@
*/
public class MultiValueAlpha {
- public static final Property<AlphaProperty, Float> VALUE =
- new Property<AlphaProperty, Float>(Float.TYPE, "value") {
+ public static final FloatProperty<AlphaProperty> VALUE =
+ new FloatProperty<AlphaProperty>("value") {
@Override
public Float get(AlphaProperty alphaProperty) {
@@ -35,7 +35,7 @@
}
@Override
- public void set(AlphaProperty object, Float value) {
+ public void setValue(AlphaProperty object, float value) {
object.setValue(value);
}
};
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index 60470dc..a7575d1 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -32,7 +32,7 @@
import androidx.core.content.ContextCompat;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
@@ -48,13 +48,13 @@
private static final long SHOW_DURATION_MS = 300;
private static final long HIDE_DURATION_MS = 100;
- protected final Launcher mLauncher;
+ protected final BaseDraggingActivity mActivity;
private final Handler mHandler = new Handler();
private Runnable mOnClosed;
public ArrowTipView(Context context) {
super(context, null, 0);
- mLauncher = Launcher.getLauncher(context);
+ mActivity = BaseDraggingActivity.fromContext(context);
init(context);
}
@@ -75,11 +75,11 @@
.setStartDelay(0)
.setDuration(HIDE_DURATION_MS)
.setInterpolator(Interpolators.ACCEL)
- .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
+ .withEndAction(() -> mActivity.getDragLayer().removeView(this))
.start();
} else {
animate().cancel();
- mLauncher.getDragLayer().removeView(this);
+ mActivity.getDragLayer().removeView(this);
}
if (mOnClosed != null) mOnClosed.run();
mIsOpen = false;
@@ -126,12 +126,12 @@
*/
public ArrowTipView show(String text, int top) {
((TextView) findViewById(R.id.text)).setText(text);
- mLauncher.getDragLayer().addView(this);
+ mActivity.getDragLayer().addView(this);
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL;
- params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
- params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
+ params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left;
+ params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right;
post(() -> setY(top - getHeight()));
setAlpha(0);
animate()
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 2fc3eaf..6915953 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -275,9 +275,6 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
- }
switch (ev.getAction()) {
case ACTION_DOWN: {
mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index e114cf8..6e21512 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.Animator;
@@ -100,6 +99,7 @@
private AnimatorSet mFadeAnimatorSet;
private ListenerView mListenerView;
+ private Runnable mFastFinishRunnable;
public FloatingIconView(Context context) {
this(context, null);
@@ -124,7 +124,6 @@
super.onAttachedToWindow();
if (!mIsOpening) {
getViewTreeObserver().addOnGlobalLayoutListener(this);
- mLauncher.getRotationHelper().setCurrentTransitionRequest(REQUEST_LOCK);
}
}
@@ -444,9 +443,21 @@
}
}
+ /**
+ * Sets a runnable that is called after a call to {@link #fastFinish()}.
+ */
+ public void setFastFinishRunnable(Runnable runnable) {
+ mFastFinishRunnable = runnable;
+ }
+
public void fastFinish() {
+ if (mFastFinishRunnable != null) {
+ mFastFinishRunnable.run();
+ mFastFinishRunnable = null;
+ }
if (mLoadIconSignal != null) {
mLoadIconSignal.cancel();
+ mLoadIconSignal = null;
}
if (mEndRunnable != null) {
mEndRunnable.run();
@@ -656,6 +667,7 @@
sTmpObjArray[0] = null;
mIconLoadResult = null;
mClipIconView.recycle();
+ mFastFinishRunnable = null;
}
private static class IconLoadResult {
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 5653801..6a83332 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -67,7 +67,6 @@
private final static int MAX_TRACK_ALPHA = 30;
private final static int SCROLL_BAR_VIS_DURATION = 150;
- private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f;
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
@@ -184,7 +183,7 @@
if (mThumbOffsetY == y) {
return;
}
- updatePopupY((int) y);
+ updatePopupY(y);
mThumbOffsetY = y;
invalidate();
}
@@ -237,7 +236,7 @@
} else if (mRv.supportsFastScrolling()
&& isNearScrollBar(mDownX)) {
calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
- updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+ updateFastScrollSectionNameAndThumbOffset(y);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -252,7 +251,7 @@
calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY);
}
if (mIsDragging) {
- updateFastScrollSectionNameAndThumbOffset(mLastY, y);
+ updateFastScrollSectionNameAndThumbOffset(y);
}
break;
case MotionEvent.ACTION_UP:
@@ -281,7 +280,7 @@
showActiveScrollbar(true);
}
- private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+ private void updateFastScrollSectionNameAndThumbOffset(int y) {
// Update the fastscroller section name at this touch position
int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5d33f13..6f2e179 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,8 +19,6 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -40,7 +38,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.Executors;
@@ -51,7 +48,7 @@
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
+ implements TouchCompleteListener, View.OnLongClickListener {
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -73,15 +70,7 @@
private boolean mIsAutoAdvanceRegistered;
private Runnable mAutoAdvanceRunnable;
- /**
- * 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);
public LauncherAppWidgetHostView(Context context) {
super(context);
@@ -307,26 +296,6 @@
scheduleNextAdvance();
}
- public void setScaleToFit(float scale) {
- mScaleToFit = scale;
- setScaleX(scale);
- setScaleY(scale);
- }
-
- public float getScaleToFit() {
- return mScaleToFit;
- }
-
- public void setTranslationForCentering(float x, float y) {
- mTranslationForCentering.set(x, y);
- setTranslationX(x);
- setTranslationY(y);
- }
-
- public PointF getTranslationForCentering() {
- return mTranslationForCentering;
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -357,17 +326,4 @@
}
return false;
}
-
- @Override
- public int getViewType() {
- return DRAGGABLE_WIDGET;
- }
-
- @Override
- public void getVisualDragBounds(Rect bounds) {
- int width = (int) (getMeasuredWidth() * mScaleToFit);
- int height = (int) (getMeasuredHeight() * mScaleToFit);
-
- bounds.set(0, 0 , width, height);
- }
}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 104ad77..a4e7daa 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -18,12 +18,14 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
import java.util.ArrayList;
@@ -32,7 +34,21 @@
* Extension of AppWidgetHostView with support for controlled keyboard navigation.
*/
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
- implements DraggableView {
+ implements DraggableView, Reorderable {
+
+ /**
+ * 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 mTranslationForReorderBounce = new PointF(0, 0);
+ private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+ private float mScaleForReorderBounce = 1f;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
@@ -137,6 +153,65 @@
setSelected(childIsFocused);
}
+ public View getView() {
+ return this;
+ }
+
+ private void updateTranslation() {
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+ + mTranslationForCentering.x);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+ + mTranslationForCentering.y);
+ }
+
+ public void setTranslationForCentering(float x, float y) {
+ mTranslationForCentering.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);
+ }
+
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ updateScale();
+ }
+
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+
+ public void setScaleToFit(float scale) {
+ mScaleToFit = scale;
+ updateScale();
+ }
+
+ public float getScaleToFit() {
+ return mScaleToFit;
+ }
+
@Override
public int getViewType() {
return DRAGGABLE_WIDGET;
@@ -144,6 +219,9 @@
@Override
public void getVisualDragBounds(Rect bounds) {
- bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+ int width = (int) (getMeasuredWidth() * mScaleToFit);
+ int height = (int) (getMeasuredHeight() * mScaleToFit);
+
+ bounds.set(0, 0 , width, height);
}
}
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 37a30af..536b766 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -71,14 +71,6 @@
}
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
- }
- return super.dispatchTouchEvent(ev);
- }
-
public WidgetsFullSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 17baa27..82d4110 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -158,23 +158,13 @@
mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
}
if (mTouchDownOnScroller) {
- final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 1 " + result);
- }
- return result;
- }
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 2 false");
+ return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
}
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView.onTouchEvent");
- }
if (mTouchDownOnScroller) {
mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
}
@@ -182,31 +172,5 @@
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onRequestDisallowInterceptTouchEvent "
- + disallowIntercept);
- }
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- final boolean result = super.dispatchTouchEvent(ev);
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView: state: "
- + getScrollState()
- + " can scroll: " + getLayoutManager().canScrollVertically()
- + " result: " + result
- + " layout suppressed: " + isLayoutSuppressed()
- + " event: " + ev);
- }
- return result;
- }
-
- @Override
- public void stopNestedScroll() {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
- Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "stopNestedScroll");
- }
- super.stopNestedScroll();
}
}
\ No newline at end of file
diff --git a/tests/Android.mk b/tests/Android.mk
index a9fff8e..4d1bfa6 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -62,7 +62,11 @@
LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
endif
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, src_common)
+
+
LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 86faddb..9c8e278 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,7 +17,6 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -26,7 +25,6 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -53,6 +51,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
+import com.android.launcher3.common.WidgetUtils;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -60,7 +59,6 @@
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Wait;
@@ -100,9 +98,10 @@
public static final long DEFAULT_UI_TIMEOUT = 10000;
private static final String TAG = "AbstractLauncherUiTest";
- private static String sDetectedActivityLeak;
+ private static String sStrictmodeDetectedActivityLeak;
private static boolean sActivityLeakReported;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+ private static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -115,30 +114,51 @@
if (TestHelpers.isInLauncherProcess()) {
StrictMode.VmPolicy.Builder builder =
new StrictMode.VmPolicy.Builder()
- .detectActivityLeaks()
+// b/154772063
+// .detectActivityLeaks()
.penaltyLog()
.penaltyListener(Runnable::run, violation -> {
- // Runs in the main thread. We can't dumpheap in the main thread,
- // so let's just mark the fact that the leak has happened.
- if (sDetectedActivityLeak == null) {
- sDetectedActivityLeak = violation.toString();
- try {
- Debug.dumpHprofData(
- getInstrumentation().getTargetContext()
- .getFilesDir().getPath()
- + "/ActivityLeakHeapDump.hprof");
- } catch (Throwable e) {
- Log.e(TAG, "dumpHprofData failed", e);
- }
+ if (sStrictmodeDetectedActivityLeak == null) {
+ sStrictmodeDetectedActivityLeak = violation.toString() + ", "
+ + dumpHprofData() + ".";
}
});
StrictMode.setVmPolicy(builder.build());
}
}
- public static void checkDetectedLeaks() {
- if (sDetectedActivityLeak != null && !sActivityLeakReported) {
+ public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
+ if (sActivityLeakReported) return;
+
+ if (sStrictmodeDetectedActivityLeak != null) {
+ // Report from the test thread strictmode violations detected in the main thread.
sActivityLeakReported = true;
+ Assert.fail(sStrictmodeDetectedActivityLeak);
+ }
+
+ // Check whether activity leak detector has found leaked activities.
+ Wait.atMost(AbstractLauncherUiTest::getActivityLeakErrorMessage,
+ () -> {
+ launcher.getTotalPssKb(); // Triggers GC
+ return MAIN_EXECUTOR.submit(
+ () -> ACTIVITY_LEAK_TRACKER.noLeakedActivities()).get();
+ }, DEFAULT_UI_TIMEOUT, launcher);
+ }
+
+ private static String getActivityLeakErrorMessage() {
+ sActivityLeakReported = true;
+ return "Activity leak detector has found leaked activities, " + dumpHprofData() + ".";
+ }
+
+ private static String dumpHprofData() {
+ try {
+ final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
+ + "/ActivityLeakHeapDump.hprof";
+ Debug.dumpHprofData(fileName);
+ return "memory dump filename: " + fileName;
+ } catch (Throwable e) {
+ Log.e(TAG, "dumpHprofData failed", e);
+ return "failed to save memory dump";
}
}
@@ -265,7 +285,7 @@
if (mLauncherPid != 0) {
assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
}
- checkDetectedLeaks();
+ checkDetectedLeaks(mLauncher);
}
protected void clearLauncherData() throws IOException, InterruptedException {
@@ -306,26 +326,7 @@
* Adds {@param item} on the homescreen on the 0th screen
*/
protected void addItemToScreen(ItemInfo item) {
- ContentResolver resolver = mTargetContext.getContentResolver();
- int screenId = FIRST_SCREEN_ID;
- // Update the screen id counter for the provider.
- LauncherSettings.Settings.call(resolver,
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
- if (screenId > FIRST_SCREEN_ID) {
- screenId = FIRST_SCREEN_ID;
- }
-
- // Insert the item
- ContentWriter writer = new ContentWriter(mTargetContext);
- item.id = LauncherSettings.Settings.call(
- resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
- item.screenId = screenId;
- item.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites._ID, item.id);
- resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
- writer.getValues(mTargetContext));
+ WidgetUtils.addItemToScreen(item, mTargetContext);
resetLoaderState();
// Launch the home activity
diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
new file mode 100644
index 0000000..e9258e9
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
@@ -0,0 +1,85 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.launcher3.tapl.TestHelpers;
+
+import java.util.WeakHashMap;
+
+class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks {
+ private final WeakHashMap<Activity, Boolean> mActivities = new WeakHashMap<>();
+
+ ActivityLeakTracker() {
+ if (!TestHelpers.isInLauncherProcess()) return;
+ final Application app =
+ (Application) InstrumentationRegistry.getTargetContext().getApplicationContext();
+ app.registerActivityLifecycleCallbacks(this);
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ mActivities.put(activity, true);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ }
+
+ public boolean noLeakedActivities() {
+ int liveActivities = 0;
+ int destroyedActivities = 0;
+
+ for (Activity activity : mActivities.keySet()) {
+ if (activity.isDestroyed()) {
+ ++destroyedActivities;
+ } else {
+ ++liveActivities;
+ }
+ }
+
+ if (liveActivities > 2) return false;
+
+ // It's OK to have 1 leaked activity if no active activities exist.
+ return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0;
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 38f50c1..266f0ae 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -56,7 +56,7 @@
private void evaluateInPortrait() throws Throwable {
mTest.mDevice.setOrientationNatural();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
base.evaluate();
mTest.getDevice().pressHome();
}
@@ -64,7 +64,7 @@
private void evaluateInLandscape() throws Throwable {
mTest.mDevice.setOrientationLeft();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
base.evaluate();
mTest.getDevice().pressHome();
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 57000a0..34e425d 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -64,7 +64,7 @@
test.waitForResumed("Launcher internal state is still Background");
// Check that we switched to home.
test.mLauncher.getWorkspace();
- AbstractLauncherUiTest.checkDetectedLeaks();
+ AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
}
// Please don't add negative test cases for methods that fail only after a long wait.
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 62ce085..df11557 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -17,18 +17,16 @@
import static androidx.test.InstrumentationRegistry.getTargetContext;
-import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
+import static com.android.launcher3.common.WidgetUtils.createWidgetInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
@@ -39,7 +37,6 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
@@ -50,7 +47,6 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import org.junit.After;
@@ -108,7 +104,7 @@
@Test
public void testBindNormalWidget_withConfig() {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
- LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
addItemToScreen(item);
verifyWidgetPresent(info);
@@ -117,7 +113,7 @@
@Test
public void testBindNormalWidget_withoutConfig() {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
addItemToScreen(item);
verifyWidgetPresent(info);
@@ -126,7 +122,7 @@
@Test
public void testUnboundWidget_removed() {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
item.appWidgetId = -33;
addItemToScreen(item);
@@ -147,7 +143,7 @@
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
// Do not bind the widget
- LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
addItemToScreen(item);
@@ -160,7 +156,7 @@
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
// Do not bind the widget
- LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
addItemToScreen(item);
@@ -282,47 +278,6 @@
}
/**
- * Creates a LauncherAppWidgetInfo corresponding to {@param info}
- *
- * @param bindWidget if true the info is bound and a valid widgetId is assigned to
- * the LauncherAppWidgetInfo
- */
- public static LauncherAppWidgetInfo createWidgetInfo(
- LauncherAppWidgetProviderInfo info, boolean bindWidget) {
- Context targetContext = getTargetContext();
-
- LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
- LauncherAppWidgetInfo.NO_ID, info.provider);
- item.spanX = info.minSpanX;
- item.spanY = info.minSpanY;
- item.minSpanX = info.minSpanX;
- item.minSpanY = info.minSpanY;
- item.user = info.getProfile();
- item.cellX = 0;
- item.cellY = 1;
- item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
-
- if (bindWidget) {
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
- pendingInfo.spanX = item.spanX;
- pendingInfo.spanY = item.spanY;
- pendingInfo.minSpanX = item.minSpanX;
- pendingInfo.minSpanY = item.minSpanY;
- Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
-
- AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
- int widgetId = host.allocateAppWidgetId();
- if (!new WidgetManagerHelper(targetContext)
- .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
- host.deleteAppWidgetId(widgetId);
- throw new IllegalArgumentException("Unable to bind widget id");
- }
- item.appWidgetId = widgetId;
- }
- return item;
- }
-
- /**
* Returns a LauncherAppWidgetInfo with package name which is not present on the device
*/
private LauncherAppWidgetInfo getInvalidWidgetInfo() {
diff --git a/tests/src_common/README.md b/tests/src_common/README.md
new file mode 100644
index 0000000..2bc9e73
--- /dev/null
+++ b/tests/src_common/README.md
@@ -0,0 +1 @@
+Common source code used by both android tests and robolectric tests
\ No newline at end of file
diff --git a/tests/src_common/com/android/launcher3/common/WidgetUtils.java b/tests/src_common/com/android/launcher3/common/WidgetUtils.java
new file mode 100644
index 0000000..c0913bf
--- /dev/null
+++ b/tests/src_common/com/android/launcher3/common/WidgetUtils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.common;
+
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
+
+import android.appwidget.AppWidgetHost;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.launcher3.LauncherAppWidgetHost;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetManagerHelper;
+
+/**
+ * Common method for widget binding
+ */
+public class WidgetUtils {
+
+ /**
+ * Creates a LauncherAppWidgetInfo corresponding to {@param info}
+ *
+ * @param bindWidget if true the info is bound and a valid widgetId is assigned to
+ * the LauncherAppWidgetInfo
+ */
+ public static LauncherAppWidgetInfo createWidgetInfo(
+ LauncherAppWidgetProviderInfo info, Context targetContext, boolean bindWidget) {
+ LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
+ LauncherAppWidgetInfo.NO_ID, info.provider);
+ item.spanX = info.minSpanX;
+ item.spanY = info.minSpanY;
+ item.minSpanX = info.minSpanX;
+ item.minSpanY = info.minSpanY;
+ item.user = info.getProfile();
+ item.cellX = 0;
+ item.cellY = 1;
+ item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+
+ if (bindWidget) {
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
+ pendingInfo.spanX = item.spanX;
+ pendingInfo.spanY = item.spanY;
+ pendingInfo.minSpanX = item.minSpanX;
+ pendingInfo.minSpanY = item.minSpanY;
+ Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
+
+ AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
+ int widgetId = host.allocateAppWidgetId();
+ if (!new WidgetManagerHelper(targetContext)
+ .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
+ host.deleteAppWidgetId(widgetId);
+ throw new IllegalArgumentException("Unable to bind widget id");
+ }
+ item.appWidgetId = widgetId;
+ }
+ return item;
+ }
+
+ /**
+ * Adds {@param item} on the homescreen on the 0th screen
+ */
+ public static void addItemToScreen(ItemInfo item, Context targetContext) {
+ ContentResolver resolver = targetContext.getContentResolver();
+ int screenId = FIRST_SCREEN_ID;
+ // Update the screen id counter for the provider.
+ LauncherSettings.Settings.call(resolver,
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+ if (screenId > FIRST_SCREEN_ID) {
+ screenId = FIRST_SCREEN_ID;
+ }
+
+ // Insert the item
+ ContentWriter writer = new ContentWriter(targetContext);
+ item.id = LauncherSettings.Settings.call(
+ resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ item.screenId = screenId;
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, item.id);
+ resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
+ writer.getValues(targetContext));
+ }
+}