diff --git a/Android.bp b/Android.bp index 4c38205979..fc998809e1 100644 --- a/Android.bp +++ b/Android.bp @@ -30,3 +30,19 @@ android_library { manifest: "tests/tapl/AndroidManifest.xml", platform_apis: true, } + +java_library_static { + name: "launcher-log-protos-lite", + srcs: [ + "protos/*.proto", + "proto_overrides/*.proto", + ], + sdk_version: "current", + proto: { + type: "lite", + local_include_dirs:[ + "protos", + "proto_overrides", + ], + }, +} diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java index e7099ec1c9..3842efbe8f 100644 --- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java +++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -24,4 +24,8 @@ public class ShelfPeekAnim { public enum ShelfAnimState { } + + public boolean isPeeking() { + return false; + } } diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml new file mode 100644 index 0000000000..70a765a562 --- /dev/null +++ b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml @@ -0,0 +1,17 @@ + + + + diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java index fe9fd604e2..78cf44e1b0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -18,6 +18,7 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import android.animation.Animator; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; @@ -40,10 +41,9 @@ import com.android.launcher3.appprediction.DynamicItemCache; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ComponentKey; @@ -66,6 +66,9 @@ public class HotseatPredictionController implements DragController.DragListener, //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) private static final int APPTARGET_ACTION_UNPIN = 4; + private static final String APP_LOCATION_HOTSEAT = "hotseat"; + private static final String APP_LOCATION_WORKSPACE = "workspace"; + private static final String PREDICTION_CLIENT = "hotseat"; private DropTarget.DragObject mDragObject; @@ -81,12 +84,14 @@ public class HotseatPredictionController implements DragController.DragListener, private AppPredictor mAppPredictor; private AllAppsStore mAllAppsStore; + private List mOutlineDrawings = new ArrayList<>(); + public HotseatPredictionController(Launcher launcher) { mLauncher = launcher; mHotseat = launcher.getHotseat(); mAllAppsStore = mLauncher.getAppsView().getAppsStore(); mAllAppsStore.addUpdateListener(this); - mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false)); + mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); @@ -102,16 +107,17 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher.getDragController().removeDragListener(this); } - /** - * Fills gaps in the hotseat with predictions - */ - public void fillGapsWithPrediction(boolean animate) { + private void fillGapsWithPrediction() { + fillGapsWithPrediction(false, null); + } + + private void fillGapsWithPrediction(boolean animate, Runnable callback) { if (mDragObject != null) { return; } List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers); int predictionIndex = 0; - ArrayList newItemsToAdd = new ArrayList<>(); + ArrayList newItems = new ArrayList<>(); for (int rank = 0; rank < mHotSeatItemsCount; rank++) { View child = mHotseat.getChildAt( mHotseat.getCellXFromOrder(rank), @@ -130,21 +136,37 @@ public class HotseatPredictionController implements DragController.DragListener, WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); if (isPredictedIcon(child)) { - BubbleTextView icon = (BubbleTextView) child; + PredictedAppIcon icon = (PredictedAppIcon) child; icon.applyFromWorkspaceItem(predictedItem); + icon.finishBinding(); } else { - newItemsToAdd.add(predictedItem); + newItems.add(predictedItem); } preparePredictionInfo(predictedItem, rank); } - mLauncher.bindItems(newItemsToAdd, animate); - for (BubbleTextView icon : getPredictedIcons()) { - icon.verifyHighRes(); - icon.setOnLongClickListener((v) -> { - PopupContainerWithArrow.showForIcon((BubbleTextView) v); - return true; + bindItems(newItems, animate, callback); + } + + private void bindItems(List itemsToAdd, boolean animate, Runnable callback) { + AnimatorSet animationSet = new AnimatorSet(); + for (WorkspaceItemInfo item : itemsToAdd) { + PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item); + mLauncher.getWorkspace().addInScreenFromBind(icon, item); + icon.finishBinding(); + if (animate) { + animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1)); + } + } + if (animate) { + animationSet.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + if (callback != null) callback.run(); + } }); - icon.setBackgroundResource(R.drawable.predicted_icon_background); + animationSet.start(); + } else { + if (callback != null) callback.run(); } } @@ -179,22 +201,30 @@ public class HotseatPredictionController implements DragController.DragListener, .build()); mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), this::setPredictedApps); + mAppPredictor.requestPredictionUpdate(); } private Bundle getAppPredictionContextExtra() { Bundle bundle = new Bundle(); - ViewGroup vg = mHotseat.getShortcutsAndWidgets(); + bundle.putParcelableArrayList(APP_LOCATION_HOTSEAT, + getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets()))); + bundle.putParcelableArrayList(APP_LOCATION_WORKSPACE, getPinnedAppTargetsInViewGroup( + mLauncher.getWorkspace().getScreenWithId( + Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets())); + return bundle; + } + + private ArrayList getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) { ArrayList pinnedApps = new ArrayList<>(); - for (int i = 0; i < vg.getChildCount(); i++) { - View child = vg.getChildAt(i); + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View child = viewGroup.getChildAt(i); if (isPinnedIcon(child)) { WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag(); pinnedApps.add(getAppTargetFromItemInfo(itemInfo)); } } - bundle.putParcelableArrayList("pinned_apps", pinnedApps); - return bundle; + return pinnedApps; } private void setPredictedApps(List appTargets) { @@ -210,7 +240,7 @@ public class HotseatPredictionController implements DragController.DragListener, mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); } updateDependencies(); - fillGapsWithPrediction(false); + fillGapsWithPrediction(); } private void updateDependencies() { @@ -219,7 +249,7 @@ public class HotseatPredictionController implements DragController.DragListener, } private void pinPrediction(ItemInfo info) { - BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt( + PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt( mHotseat.getCellXFromOrder(info.rank), mHotseat.getCellYFromOrder(info.rank)); if (icon == null) { @@ -230,11 +260,9 @@ public class HotseatPredictionController implements DragController.DragListener, LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId, workspaceItemInfo.cellX, workspaceItemInfo.cellY); ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); - icon.reset(); - icon.applyFromWorkspaceItem(workspaceItemInfo); - icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); + icon.pin(workspaceItemInfo); AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); - notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN); + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); } private List mapToWorkspaceItemInfo( @@ -265,47 +293,51 @@ public class HotseatPredictionController implements DragController.DragListener, return predictedApps; } - private List getPredictedIcons() { - List icons = new ArrayList<>(); + private List getPredictedIcons() { + List icons = new ArrayList<>(); ViewGroup vg = mHotseat.getShortcutsAndWidgets(); for (int i = 0; i < vg.getChildCount(); i++) { View child = vg.getChildAt(i); if (isPredictedIcon(child)) { - icons.add((BubbleTextView) child); + icons.add((PredictedAppIcon) child); } } return icons; } - private void removePredictedApps(boolean animate) { - for (BubbleTextView icon : getPredictedIcons()) { - if (animate) { - icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { - @Override - public void onAnimationSuccess(Animator animator) { - if (icon.getParent() != null) { - mHotseat.removeView(icon); - } + private void removePredictedApps(List outlines) { + for (PredictedAppIcon icon : getPredictedIcons()) { + int rank = ((WorkspaceItemInfo) icon.getTag()).rank; + outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing( + mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon)); + icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + if (icon.getParent() != null) { + mHotseat.removeView(icon); } - }); - } else { - if (icon.getParent() != null) { - mHotseat.removeView(icon); } - } + }); } } - private void notifyItemAction(AppTarget target, int action) { + + private void notifyItemAction(AppTarget target, String location, int action) { if (mAppPredictor != null) { - mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build()); + mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, + action).setLaunchLocation(location).build()); } } @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - removePredictedApps(true); + removePredictedApps(mOutlineDrawings); mDragObject = dragObject; + if (mOutlineDrawings.isEmpty()) return; + for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { + mHotseat.addDelegatedCellDrawing(outlineDrawing); + } + mHotseat.invalidate(); } @Override @@ -315,14 +347,29 @@ public class HotseatPredictionController implements DragController.DragListener, } ItemInfo dragInfo = mDragObject.dragInfo; if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) { + AppTarget appTarget = getAppTargetFromItemInfo(dragInfo); + if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); + } + if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) { + notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + } if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(getAppTargetFromItemInfo(dragInfo), AppTargetEvent.ACTION_PIN); - } else if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(getAppTargetFromItemInfo(dragInfo), APPTARGET_ACTION_UNPIN); + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); + } + if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) { + notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); } } mDragObject = null; - fillGapsWithPrediction(true); + fillGapsWithPrediction(true, () -> { + if (mOutlineDrawings.isEmpty()) return; + for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { + mHotseat.removeDelegatedCellDrawing(outlineDrawing); + } + mHotseat.invalidate(); + mOutlineDrawings.clear(); + }); } @Nullable @@ -351,8 +398,7 @@ public class HotseatPredictionController implements DragController.DragListener, @Override public void onAppsUpdated() { - updateDependencies(); - fillGapsWithPrediction(false); + fillGapsWithPrediction(); } @Override @@ -375,7 +421,7 @@ public class HotseatPredictionController implements DragController.DragListener, } private static boolean isPredictedIcon(View view) { - return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo + return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) view.getTag()).container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; } @@ -385,7 +431,7 @@ public class HotseatPredictionController implements DragController.DragListener, return false; } ItemInfo info = (ItemInfo) view.getTag(); - return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && ( + return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && ( info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); } @@ -394,11 +440,15 @@ public class HotseatPredictionController implements DragController.DragListener, return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; } + private static boolean isInFirstPage(ItemInfo itemInfo) { + return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP + && itemInfo.screenId == Workspace.FIRST_SCREEN_ID; + } + private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { if (info.getTargetComponent() == null) return null; - return new AppTarget.Builder( - new AppTargetId("app:" + info.getTargetComponent().getPackageName()), - info.getTargetComponent().getPackageName(), info.user).setClassName( - info.getTargetComponent().getClassName()).build(); + ComponentName cn = info.getTargetComponent(); + return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), + cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); } } 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 6946508fc7..51ee216139 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -21,8 +21,15 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; @@ -40,6 +47,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.util.AppWindowAnimationHelper; @@ -56,6 +64,9 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti public static final int INDEX_SHELF_ANIM = 0; public static final int INDEX_RECENTS_FADE_ANIM = 1; public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2; + public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3; + + public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; public LauncherAppTransitionManagerImpl(Context context) { super(context); @@ -145,7 +156,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti @Override public int getStateElementAnimationsCount() { - return 3; + return 4; } @Override @@ -191,6 +202,20 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti .setStiffness(250) .setValues(values) .build(mLauncher); + case INDEX_PAUSE_TO_OVERVIEW_ANIM: { + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); + } + LauncherStateManager stateManager = mLauncher.getStateManager(); + return stateManager.createAtomicAnimation( + stateManager.getCurrentStableState(), OVERVIEW, builder, + ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW); + } + default: return super.createStateElementAnimation(index, values); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index 23db5df2ed..f82af62aac 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -276,7 +276,7 @@ public class PredictionRowView extends LinearLayout implements boolean predictionsEnabled = predictionCount > 0; if (predictionsEnabled != mPredictionsEnabled) { mPredictionsEnabled = predictionsEnabled; - mLauncher.reapplyUi(); + mLauncher.reapplyUi(false /* cancelCurrentAnimation */); updateVisibility(); } mParent.onHeightUpdated(); 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 new file mode 100644 index 0000000000..f97d6daeda --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.uioverrides; + +import static com.android.launcher3.graphics.IconShape.getShape; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.core.graphics.ColorUtils; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.graphics.IconPalette; +import com.android.launcher3.icons.IconNormalizer; +import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.views.DoubleShadowBubbleTextView; + +/** + * A BubbleTextView with a ring around it's drawable + */ +public class PredictedAppIcon extends DoubleShadowBubbleTextView { + + private static final float RING_EFFECT_RATIO = 0.12f; + + private DeviceProfile mDeviceProfile; + private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private boolean mIsPinned = false; + private int mNormalizedIconRadius; + + + public PredictedAppIcon(Context context) { + this(context, null, 0); + } + + public PredictedAppIcon(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile(); + mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2; + setOnClickListener(ItemClickHandler.INSTANCE); + setOnFocusChangeListener(Launcher.getLauncher(context).mFocusHandler); + } + + @Override + public void onDraw(Canvas canvas) { + int count = canvas.save(); + if (!mIsPinned) { + drawEffect(canvas); + canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO); + canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO); + } + super.onDraw(canvas); + canvas.restoreToCount(count); + } + + @Override + public void applyFromWorkspaceItem(WorkspaceItemInfo info) { + super.applyFromWorkspaceItem(info); + int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f); + mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200)); + } + + /** + * Removes prediction ring from app icon + */ + public void pin(WorkspaceItemInfo info) { + if (mIsPinned) return; + applyFromWorkspaceItem(info); + setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); + mIsPinned = true; + invalidate(); + } + + /** + * prepares prediction icon for usage after bind + */ + public void finishBinding() { + setOnLongClickListener((v) -> { + PopupContainerWithArrow.showForIcon((BubbleTextView) v); + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + return true; + }); + setTextVisibility(false); + verifyHighRes(); + } + + @Override + public void getIconBounds(Rect outBounds) { + super.getIconBounds(outBounds); + if (!mIsPinned) { + int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO); + outBounds.inset(predictionInset, predictionInset); + } + } + + private int getOutlineOffsetX() { + return (getMeasuredWidth() / 2) - mNormalizedIconRadius; + } + + private int getOutlineOffsetY() { + return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx; + } + + private void drawEffect(Canvas canvas) { + getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(), + mNormalizedIconRadius, mIconRingPaint); + } + + /** + * Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo + */ + public static PredictedAppIcon createIcon(ViewGroup parent, WorkspaceItemInfo info) { + PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.predicted_app_icon, parent, false); + icon.applyFromWorkspaceItem(info); + return icon; + } + + /** + * Draws Predicted Icon outline on cell layout + */ + public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing { + + private int mOffsetX; + private int mOffsetY; + private int mIconRadius; + private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) { + mDelegateCellX = cellX; + mDelegateCellY = cellY; + 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)); + } + + /** + * Draws predicted app icon outline under CellLayout + */ + @Override + public void drawUnderItem(Canvas canvas) { + getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint); + } + + /** + * Draws PredictedAppIcon outline over CellLayout + */ + @Override + public void drawOverItem(Canvas canvas) { + // Does nothing + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 744936b95e..eaaf0b4d0e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -37,6 +37,7 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; +import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController; import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController; @@ -215,7 +216,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { ArrayList list = new ArrayList<>(); list.add(getDragController()); if (mode == NO_BUTTON) { - list.add(new QuickSwitchTouchController(this)); + list.add(new NoButtonQuickSwitchTouchController(this)); list.add(new NavBarToHomeTouchController(this)); list.add(new FlingAndHoldTouchController(this)); } else { 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 25eaab1879..ed5dba1fd5 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 @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; @@ -205,6 +206,7 @@ public class OverviewState extends LauncherState { builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7); builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 626292e867..d388f495bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -16,25 +16,20 @@ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; -import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -46,12 +41,13 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppTransitionManagerImpl; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.quickstep.SystemUiProxy; import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; @@ -79,7 +75,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { @Override protected long getAtomicDuration() { - return 300; + return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; } @Override @@ -179,15 +175,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mPeekAnim.cancel(); } - AnimatorSetBuilder builder = new AnimatorSetBuilder(); - builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); - if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { - builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); - } - AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation( - NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION); + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); overviewAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { 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 new file mode 100644 index 0000000000..613386edfa --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.uioverrides.touchcontrollers; + +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW; +import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_5; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; +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.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; +import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.LauncherStateManager.AnimationConfig; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.graphics.OverviewScrim; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.BothAxesSwipeDetector; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.launcher3.util.TouchController; +import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.MotionPauseDetector; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; +import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.views.LauncherRecentsView; + +/** + * Handles quick switching to a recent task from the home screen. To give as much flexibility to + * the user as possible, also handles swipe up and hold to go to overview and swiping back home. + */ +public class NoButtonQuickSwitchTouchController implements TouchController, + BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener { + + /** The minimum progress of the scale/translationY animation until drag end. */ + private static final float Y_ANIM_MIN_PROGRESS = 0.15f; + private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5; + private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75; + private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL; + + private final BaseQuickstepLauncher mLauncher; + private final BothAxesSwipeDetector mSwipeDetector; + private final ShelfPeekAnim mShelfPeekAnim; + private final float mXRange; + private final float mYRange; + private final MotionPauseDetector mMotionPauseDetector; + private final float mMotionPauseMinDisplacement; + + private boolean mNoIntercept; + private LauncherState mStartState; + + private boolean mIsHomeScreenVisible = true; + + // As we drag, we control 3 animations: one to get non-overview components out of the way, + // and the other two to set overview properties based on x and y progress. + private AnimatorPlaybackController mNonOverviewAnim; + private AnimatorPlaybackController mXOverviewAnim; + private AnimatorPlaybackController mYOverviewAnim; + + public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) { + mLauncher = launcher; + mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this); + mShelfPeekAnim = mLauncher.getShelfPeekAnim(); + mXRange = mLauncher.getDeviceProfile().widthPx / 2f; + mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile()); + mMotionPauseDetector = new MotionPauseDetector(mLauncher); + mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( + R.dimen.motion_pause_detector_min_displacement_from_app); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = !canInterceptTouch(ev); + if (mNoIntercept) { + return false; + } + + // Only detect horizontal swipe for intercept, then we will allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT, + false /* ignoreSlopWhenSettling */); + } + + if (mNoIntercept) { + return false; + } + + onControllerTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mSwipeDetector.onTouchEvent(ev); + } + + private boolean canInterceptTouch(MotionEvent ev) { + if (!mLauncher.isInState(LauncherState.NORMAL)) { + return false; + } + if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) { + return false; + } + int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags(); + if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { + return false; + } + return true; + } + + @Override + public void onDragStart(boolean start) { + mMotionPauseDetector.clear(); + if (start) { + mStartState = mLauncher.getStateManager().getState(); + + mMotionPauseDetector.setOnMotionPauseListener(this); + + // We have detected horizontal drag start, now allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP, + false /* ignoreSlopWhenSettling */); + + setupAnimators(); + } + } + + @Override + public void onMotionPauseChanged(boolean isPaused) { + ShelfAnimState shelfState = isPaused ? PEEK : HIDE; + if (shelfState == PEEK) { + // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking. + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); + allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher), + new AnimationConfig(), builder); + builder.build().setDuration(0).start(); + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Hotseat was hidden, but we need it visible when peeking. + mLauncher.getHotseat().setAlpha(1); + } + } + mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR, + ShelfPeekAnim.DURATION); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); + } + + private void setupAnimators() { + // Animate the non-overview components (e.g. workspace, shelf) out of the way. + AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder(); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR); + updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL); + mNonOverviewAnim.dispatchOnStart(); + + setupOverviewAnimators(); + } + + /** Create state animation to control non-overview components. */ + private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder, + @LauncherStateManager.AnimationComponents int animComponents) { + builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW); + long accuracy = (long) (Math.max(mXRange, mYRange) * 2); + mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState, + builder, accuracy, this::clearState, animComponents); + } + + 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. + LauncherRecentsView recentsView = mLauncher.getOverviewPanel(); + float startScale = Utilities.mapRange( + SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS), + fromScaleAndTranslation.scale, + toScaleAndTranslation.scale); + fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale); + + // Set RecentView's initial properties. + recentsView.setScaleX(fromScaleAndTranslation.scale); + recentsView.setScaleY(fromScaleAndTranslation.scale); + recentsView.setTranslationX(fromScaleAndTranslation.translationX); + recentsView.setTranslationY(fromScaleAndTranslation.translationY); + recentsView.setContentAlpha(1); + + // As we drag right, animate the following properties: + // - RecentsView translationX + // - OverviewScrim + AnimatorSet xOverviewAnim = new AnimatorSet(); + xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, 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); + mXOverviewAnim.dispatchOnStart(); + + // As we drag up, animate the following properties: + // - RecentsView translationY + // - RecentsView scale + // - RecentsView fullscreenProgress + AnimatorSet yAnimation = new AnimatorSet(); + Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y, + toScaleAndTranslation.translationY); + Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, + toScaleAndTranslation.scale); + Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, 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); + mYOverviewAnim.dispatchOnStart(); + } + + @Override + public boolean onDrag(PointF displacement, MotionEvent ev) { + float xProgress = Math.max(0, displacement.x) / mXRange; + float yProgress = Math.max(0, -displacement.y) / mYRange; + yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f); + + boolean wasHomeScreenVisible = mIsHomeScreenVisible; + if (wasHomeScreenVisible && mNonOverviewAnim != null) { + mNonOverviewAnim.setPlayFraction(xProgress); + } + mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) + <= 1 - ALPHA_CUTOFF_THRESHOLD; + + if (wasHomeScreenVisible && !mIsHomeScreenVisible) { + // Get the shelf all the way offscreen so it pops up when we decide to peek it. + mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0); + } + + // Only allow motion pause if the home screen is invisible, since some + // home screen elements will appear in the shelf on motion pause. + mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible + || -displacement.y < mMotionPauseMinDisplacement); + mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime()); + + if (mIsHomeScreenVisible) { + // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim. + mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0); + } + + if (mXOverviewAnim != null) { + mXOverviewAnim.setPlayFraction(xProgress); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.setPlayFraction(yProgress); + } + return true; + } + + @Override + public void onDragEnd(PointF velocity) { + boolean horizontalFling = mSwipeDetector.isFling(velocity.x); + boolean verticalFling = mSwipeDetector.isFling(velocity.y); + boolean noFling = !horizontalFling && !verticalFling; + int logAction = noFling ? Touch.SWIPE : Touch.FLING; + if (mMotionPauseDetector.isPaused() && noFling) { + cancelAnimations(); + + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); + overviewAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onAnimationToStateCompleted(OVERVIEW, logAction); + } + }); + overviewAnim.start(); + return; + } + + final LauncherState targetState; + if (horizontalFling && verticalFling) { + if (velocity.x < 0) { + // Flinging left and up or down both go back home. + targetState = NORMAL; + } else { + if (velocity.y > 0) { + // Flinging right and down goes to quick switch. + targetState = QUICK_SWITCH; + } else { + // Flinging up and right could go either home or to quick switch. + // Determine the target based on the higher velocity. + targetState = Math.abs(velocity.x) > Math.abs(velocity.y) + ? QUICK_SWITCH : NORMAL; + } + } + } else if (horizontalFling) { + targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL; + } else if (verticalFling) { + targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL; + } else { + // If user isn't flinging, just snap to the closest state based on x progress. + boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f; + targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL; + } + + // Animate the various components to the target state. + + float xProgress = mXOverviewAnim.getProgressFraction(); + float startXProgress = Utilities.boundToRange(xProgress + + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f); + final float endXProgress = targetState == NORMAL ? 0 : 1; + long xDuration = BaseSwipeDetector.calculateDuration(velocity.x, + Math.abs(endXProgress - startXProgress)); + ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer(); + xOverviewAnim.setFloatValues(startXProgress, endXProgress); + xOverviewAnim.setDuration(xDuration) + .setInterpolator(scrollInterpolatorForVelocity(velocity.x)); + mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x); + + boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; + + float yProgress = mYOverviewAnim.getProgressFraction(); + float startYProgress = Utilities.boundToRange(yProgress + - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f); + final float endYProgress; + if (flingUpToNormal) { + endYProgress = 1; + } else if (targetState == NORMAL) { + // Keep overview at its current scale/translationY as it slides off the screen. + endYProgress = startYProgress; + } else { + endYProgress = 0; + } + long yDuration = BaseSwipeDetector.calculateDuration(velocity.y, + Math.abs(endYProgress - startYProgress)); + ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer(); + yOverviewAnim.setFloatValues(startYProgress, endYProgress); + yOverviewAnim.setDuration(yDuration); + mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y); + + ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + if (flingUpToNormal && !mIsHomeScreenVisible) { + // We are flinging to home while workspace is invisible, run the same staggered + // animation as from an app. + // Update mNonOverviewAnim to do nothing so it doesn't interfere. + updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */); + nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + + new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */) + .start(); + } else { + boolean canceled = targetState == NORMAL; + if (canceled) { + // Let the state manager know that the animation didn't go to the target state, + // but don't clean up yet (we already clean up when the animation completes). + mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable(); + } + float startProgress = mNonOverviewAnim.getProgressFraction(); + float endProgress = canceled ? 0 : 1; + nonOverviewAnim.setFloatValues(startProgress, endProgress); + mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress, + horizontalFling ? velocity.x : velocity.y); + } + + nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); + mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction)); + + cancelAnimations(); + xOverviewAnim.start(); + yOverviewAnim.start(); + nonOverviewAnim.start(); + } + + private void onAnimationToStateCompleted(LauncherState targetState, int logAction) { + mLauncher.getUserEventDispatcher().logStateChangeAction(logAction, + getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(), + LauncherLogProto.ContainerType.NAVBAR, + mStartState.containerType, + targetState.containerType, + mLauncher.getWorkspace().getCurrentPage()); + mLauncher.getStateManager().goToState(targetState, false, this::clearState); + } + + private int getDirectionForLog() { + return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT; + } + + private void cancelAnimations() { + if (mNonOverviewAnim != null) { + mNonOverviewAnim.getAnimationPlayer().cancel(); + } + if (mXOverviewAnim != null) { + mXOverviewAnim.getAnimationPlayer().cancel(); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.getAnimationPlayer().cancel(); + } + mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); + mMotionPauseDetector.clear(); + } + + private void clearState() { + cancelAnimations(); + mNonOverviewAnim = null; + mXOverviewAnim = null; + mYOverviewAnim = null; + mIsHomeScreenVisible = true; + mSwipeDetector.finishedScrolling(); + } +} 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 844152be28..28fc3dadd5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -24,7 +24,6 @@ import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; 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; @@ -52,10 +51,8 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; @@ -194,18 +191,8 @@ public final class LauncherActivityInterface implements BaseActivityInterface private boolean mIsShelfPeeking; private boolean mContinuingLastGesture; - // To avoid UI jump when gesture is started, we offset the animation by the threshold. - private float mShiftAtGestureStart = 0; private ThumbnailData mTaskSnapshot; @@ -574,9 +572,7 @@ public class LauncherSwipeHandler // Normalize the progress to 0 to 1, as the animation controller will clamp it to that // anyway. The controller mimics the drag length factor by applying it to its interpolators. float progress = mCurrentShift.value / mDragLengthFactor; - mLauncherTransitionController.setPlayFraction( - progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1 - ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart)); + mLauncherTransitionController.setPlayFraction(progress); } /** @@ -623,7 +619,6 @@ public class LauncherSwipeHandler @Override public void onGestureStarted() { notifyGestureStartedAsync(); - mShiftAtGestureStart = mCurrentShift.value; mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; } 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 d644fd6eba..babf13e88d 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 @@ -18,6 +18,7 @@ package com.android.quickstep.util; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; @@ -27,12 +28,11 @@ import android.animation.ObjectAnimator; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.Nullable; - import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; @@ -41,7 +41,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.launcher3.graphics.OverviewScrim; -import com.android.launcher3.views.IconLabelDotView; +import com.android.quickstep.views.RecentsView; /** * Creates an animation where all the workspace items are moved into their final location, @@ -61,18 +61,12 @@ public class StaggeredWorkspaceAnim { private final float mVelocity; private final float mSpringTransY; - // The original view of the {@link FloatingIconView}. - private final View mOriginalView; - private final AnimatorSet mAnimators = new AnimatorSet(); - /** - * @param floatingViewOriginalView The FloatingIconView's original view. - */ - public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView, - float velocity) { + public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) { + prepareToAnimate(launcher); + mVelocity = velocity; - mOriginalView = floatingViewOriginalView; // Scale the translationY based on the initial velocity to better sync the workspace items // with the floating view. @@ -128,8 +122,10 @@ public class StaggeredWorkspaceAnim { addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows); } - addScrimAnimationForState(launcher, BACKGROUND_APP, 0); - addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS); + if (animateOverviewScrim) { + addScrimAnimationForState(launcher, BACKGROUND_APP, 0); + addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS); + } mAnimators.addListener(new AnimatorListenerAdapter() { @Override @@ -144,6 +140,21 @@ public class StaggeredWorkspaceAnim { }); } + /** + * Setup workspace with 0 duration to prepare for our staggered animation. + */ + private void prepareToAnimate(Launcher launcher) { + LauncherStateManager stateManager = launcher.getStateManager(); + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + // setRecentsAttachedToAppWindow() will animate recents out. + builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW); + stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0); + builder.build().start(); + + // Stop scrolling so that it doesn't interfere with the translation offscreen. + launcher.getOverviewPanel().getScroller().forceFinished(true); + } + /** * Starts the animation. */ @@ -176,35 +187,12 @@ public class StaggeredWorkspaceAnim { springTransY.setStartDelay(startDelay); mAnimators.play(springTransY); - ObjectAnimator alpha = getAlphaAnimator(v, startDelay); - if (v == mOriginalView) { - // For IconLabelDotViews, we just want the label to fade in. - // Icon, badge, and dots will animate in separately (controlled via FloatingIconView) - if (v instanceof IconLabelDotView) { - alpha.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - IconLabelDotView view = (IconLabelDotView) v; - view.setIconVisible(false); - view.setForceHideDot(true); - } - }); - } else { - return; - } - } - v.setAlpha(0); - mAnimators.play(alpha); - } - - private ObjectAnimator getAlphaAnimator(View v, long startDelay) { ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); alpha.setInterpolator(LINEAR); alpha.setDuration(ALPHA_DURATION_MS); alpha.setStartDelay(startDelay); - return alpha; - + mAnimators.play(alpha); } private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) { 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 250a333865..32687830fa 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 @@ -176,6 +176,7 @@ public abstract class RecentsView extends PagedView impl 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(); @@ -342,6 +343,8 @@ public abstract class RecentsView extends PagedView impl setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); mTaskTopMargin = getResources() .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); + mTaskBottomMargin = getResources().getDimensionPixelSize( + R.dimen.task_thumbnail_bottom_margin); mSquaredTouchSlop = squaredTouchSlop(context); mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); @@ -695,6 +698,7 @@ public abstract class RecentsView extends PagedView impl 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); @@ -1466,7 +1470,7 @@ public abstract class RecentsView extends PagedView impl // Set the pivot points to match the task preview center setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin) - + (getHeight() - mInsets.bottom - getPaddingBottom())) / 2); + + (getHeight() - mInsets.bottom - getPaddingBottom() - mTaskBottomMargin)) / 2); setPivotX(((mInsets.left + getPaddingLeft()) + (getWidth() - mInsets.right - getPaddingRight())) / 2); } 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 a1775f4284..0bfde64c37 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 @@ -540,8 +540,11 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } addView(view, indexToAdd); - ((LayoutParams) view.getLayoutParams()).gravity = + LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + layoutParams.bottomMargin = + ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin; view.setAlpha(mFooterAlpha); mFooters[index] = new FooterWrapper(view); if (shouldAnimateEntry) { @@ -618,10 +621,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private static final class TaskOutlineProvider extends ViewOutlineProvider { private final int mMarginTop; + private final int mMarginBottom; private FullscreenDrawParams mFullscreenParams; TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) { mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); + mMarginBottom = res.getDimensionPixelSize(R.dimen.task_thumbnail_bottom_margin); mFullscreenParams = fullscreenParams; } @@ -636,7 +641,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { outline.setRoundRect(0, (int) (mMarginTop * scale), (int) ((insets.left + view.getWidth() + insets.right) * scale), - (int) ((insets.top + view.getHeight() + insets.bottom) * scale), + (int) ((insets.top + view.getHeight() + insets.bottom - mMarginBottom) * scale), mFullscreenParams.mCurrentDrawnCornerRadius); } } diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 60cfa0cbaa..7a36416c59 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -25,7 +25,8 @@ android:id="@+id/snapshot" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="@dimen/task_thumbnail_top_margin"/> + android:layout_marginTop="@dimen/task_thumbnail_top_margin" + android:layout_marginBottom="@dimen/task_thumbnail_bottom_margin"/> 24dp 12dp + + 0dp 48dp 2dp diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index 2e118b44df..c47bb4a259 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -105,6 +105,7 @@ public class LayoutUtils { } float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin); + float bottomMargin = res.getDimension(R.dimen.task_thumbnail_bottom_margin); float paddingVert = res.getDimension(R.dimen.task_card_vert_space); // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless @@ -113,7 +114,7 @@ public class LayoutUtils { int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; float availableHeight = launcherVisibleHeight - - topIconMargin - extraVerticalSpace - paddingVert; + - topIconMargin - extraVerticalSpace - paddingVert - bottomMargin; float availableWidth = launcherVisibleWidth - paddingHorz; float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 0e591cac7a..7885f5cb9b 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -18,6 +18,7 @@ package com.android.quickstep.views; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -34,7 +35,9 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.animation.Interpolator; +import com.android.launcher3.BaseQuickstepLauncher; 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; @@ -52,7 +55,8 @@ import com.android.quickstep.util.LayoutUtils; * From normal state to overview state, the shelf just fades in and does not move * From overview state to all-apps state the shelf moves up and fades in to cover the screen */ -public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener { +public class ShelfScrimView extends ScrimView + implements NavigationModeChangeListener { // If the progress is more than this, shelf follows the finger, otherwise it moves faster to // cover the whole screen @@ -193,8 +197,10 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis if (mProgress >= 1) { mRemainingScreenColor = 0; mShelfColor = 0; + LauncherState state = mLauncher.getStateManager().getState(); if (mSysUINavigationMode == Mode.NO_BUTTON - && mLauncher.getStateManager().getState() == BACKGROUND_APP) { + && (state == BACKGROUND_APP || state == QUICK_SWITCH) + && mLauncher.getShelfPeekAnim().isPeeking()) { // Show the shelf background when peeking during swipe up. mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha); } diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index fa4c7b9fa1..5606ac28e7 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -209,7 +209,6 @@ public class NavigationModeSwitchRule implements TestRule { final String error = mLauncher.getNavigationModeMismatchError(); assertTrue("Switching nav mode: " + error, error == null); - Thread.sleep(5000); return true; } diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml index bc658e4830..662b86ebbe 100644 --- a/res/values-land/dimens.xml +++ b/res/values-land/dimens.xml @@ -15,9 +15,6 @@ --> - - 12% - 58dp 48dp diff --git a/res/values/config.xml b/res/values/config.xml index 2a1f6f7864..0dfed97ce3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -114,4 +114,7 @@ + + + diff --git a/res/xml/dynamic_resources.xml b/res/xml/dynamic_resources.xml new file mode 100644 index 0000000000..f5d262868c --- /dev/null +++ b/res/xml/dynamic_resources.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 976ccd52ed..89bec98bd2 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -110,7 +110,7 @@ public class CellLayout extends ViewGroup implements Transposable { private OnTouchListener mInterceptTouchListener; - private final ArrayList mFolderBackgrounds = new ArrayList<>(); + private final ArrayList mDelegatedCellDrawings = new ArrayList<>(); final PreviewBackground mFolderLeaveBehind = new PreviewBackground(); private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active }; @@ -219,8 +219,8 @@ public class CellLayout extends ViewGroup implements Transposable { mPreviousReorderDirection[0] = INVALID_DIRECTION; mPreviousReorderDirection[1] = INVALID_DIRECTION; - mFolderLeaveBehind.delegateCellX = -1; - mFolderLeaveBehind.delegateCellY = -1; + mFolderLeaveBehind.mDelegateCellX = -1; + mFolderLeaveBehind.mDelegateCellY = -1; setAlwaysDrawnWithCacheEnabled(false); final Resources res = getResources(); @@ -466,21 +466,18 @@ public class CellLayout extends ViewGroup implements Transposable { } } - for (int i = 0; i < mFolderBackgrounds.size(); i++) { - PreviewBackground bg = mFolderBackgrounds.get(i); - cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); + for (int i = 0; i < mDelegatedCellDrawings.size(); i++) { + DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i); + cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); - bg.drawBackground(canvas); - if (!bg.isClipping) { - bg.drawBackgroundStroke(canvas); - } + cellDrawing.drawUnderItem(canvas); canvas.restore(); } - if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) { - cellToPoint(mFolderLeaveBehind.delegateCellX, - mFolderLeaveBehind.delegateCellY, mTempLocation); + if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) { + cellToPoint(mFolderLeaveBehind.mDelegateCellX, + mFolderLeaveBehind.mDelegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); mFolderLeaveBehind.drawLeaveBehind(canvas); @@ -492,23 +489,28 @@ public class CellLayout extends ViewGroup implements Transposable { protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - for (int i = 0; i < mFolderBackgrounds.size(); i++) { - PreviewBackground bg = mFolderBackgrounds.get(i); - if (bg.isClipping) { - cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); - canvas.save(); - canvas.translate(mTempLocation[0], mTempLocation[1]); - bg.drawBackgroundStroke(canvas); - canvas.restore(); - } + for (int i = 0; i < mDelegatedCellDrawings.size(); i++) { + DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i); + cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation); + canvas.save(); + canvas.translate(mTempLocation[0], mTempLocation[1]); + bg.drawOverItem(canvas); + canvas.restore(); } } - public void addFolderBackground(PreviewBackground bg) { - mFolderBackgrounds.add(bg); + /** + * Add Delegated cell drawing + */ + public void addDelegatedCellDrawing(DelegatedCellDrawing bg) { + mDelegatedCellDrawings.add(bg); } - public void removeFolderBackground(PreviewBackground bg) { - mFolderBackgrounds.remove(bg); + + /** + * Remove item from DelegatedCellDrawings + */ + public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) { + mDelegatedCellDrawings.remove(bg); } public void setFolderLeaveBehindCell(int x, int y) { @@ -516,14 +518,14 @@ public class CellLayout extends ViewGroup implements Transposable { mFolderLeaveBehind.setup(getContext(), mActivity, null, child.getMeasuredWidth(), child.getPaddingTop()); - mFolderLeaveBehind.delegateCellX = x; - mFolderLeaveBehind.delegateCellY = y; + mFolderLeaveBehind.mDelegateCellX = x; + mFolderLeaveBehind.mDelegateCellY = y; invalidate(); } public void clearFolderLeaveBehind() { - mFolderLeaveBehind.delegateCellX = -1; - mFolderLeaveBehind.delegateCellY = -1; + mFolderLeaveBehind.mDelegateCellX = -1; + mFolderLeaveBehind.mDelegateCellY = -1; invalidate(); } @@ -2743,6 +2745,24 @@ public class CellLayout extends ViewGroup implements Transposable { } } + /** + * A Delegated cell Drawing for drawing on CellLayout + */ + public abstract static class DelegatedCellDrawing { + public int mDelegateCellX; + public int mDelegateCellY; + + /** + * Draw under CellLayout + */ + public abstract void drawUnderItem(Canvas canvas); + + /** + * Draw over CellLayout + */ + public abstract void drawOverItem(Canvas canvas); + } + /** * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing * if necessary). diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 3347b2aef0..bd48aec1ca 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -113,6 +113,7 @@ public class DeleteDropTarget extends ButtonDropTarget { public void onDrop(DragObject d, DragOptions options) { if (canRemove(d.dragInfo)) { mLauncher.getModelWriter().prepareToUndoDelete(); + d.dragInfo.container = NO_ID; } super.onDrop(d, options); } diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index 4e0f2e7446..52a393ff02 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -21,14 +21,19 @@ import android.util.AttributeSet; import android.view.DragEvent; import android.view.KeyEvent; import android.view.View; +import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import com.android.launcher3.folder.FolderNameProvider; import com.android.launcher3.util.UiThreadHelper; +import java.util.List; + /** * The edit text that reports back when the back key has been pressed. + * Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion */ public class ExtendedEditText extends EditText { @@ -85,12 +90,9 @@ public class ExtendedEditText extends EditText { super.onLayout(changed, left, top, right, bottom); if (mShowImeAfterFirstLayout) { // soft input only shows one frame after the layout of the EditText happens, - post(new Runnable() { - @Override - public void run() { - showSoftInput(); - mShowImeAfterFirstLayout = false; - } + post(() -> { + showSoftInput(); + mShowImeAfterFirstLayout = false; }); } } @@ -103,9 +105,27 @@ public class ExtendedEditText extends EditText { UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken()); } + @Override + public void onCommitCompletion(CompletionInfo text) { + setText(text.getText()); + } + + /** + * Currently only used for folder name suggestion. + */ + public void displayCompletions(List suggestList) { + int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX); + CompletionInfo[] cInfo = new CompletionInfo[cnt]; + for (int i = 0; i < cnt; i++) { + cInfo[i] = new CompletionInfo(i, i, suggestList.get(i)); + } + post(() -> getContext().getSystemService(InputMethodManager.class) + .displayCompletions(this, cInfo)); + } + private boolean showSoftInput() { return requestFocus() && - ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) + getContext().getSystemService(InputMethodManager.class) .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b3b2df6afc..cb8fe05c0e 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -508,12 +508,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void reapplyUi() { + reapplyUi(true /* cancelCurrentAnimation */); + } + + public void reapplyUi(boolean cancelCurrentAnimation) { if (supportsFakeLandscapeUI()) { mRotationMode = mStableDeviceProfile == null ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile); } getRootView().dispatchInsets(); - getStateManager().reapplyState(true /* cancelCurrentAnimation */); + getStateManager().reapplyState(cancelCurrentAnimation); } @Override diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java index 8ac9d662cd..eabd283699 100644 --- a/src/com/android/launcher3/anim/AlphaUpdateListener.java +++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; */ public class AlphaUpdateListener extends AnimationSuccessListener implements AnimatorUpdateListener { - private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; + public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; private View mView; diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 2c440bba11..4a52795f89 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -26,15 +26,15 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; - /** * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators * and durations. @@ -250,6 +250,17 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + /** + * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This + * is intended to be used only if you need to cancel but want to defer cleaning up yourself. + */ + public void dispatchOnCancelWithoutCancelRunnable() { + Runnable onCancel = mOnCancelRunnable; + setOnCancelRunnable(null); + dispatchOnCancel(); + setOnCancelRunnable(onCancel); + } + public void dispatchOnCancel() { dispatchOnCancelRecursively(mAnim); } @@ -283,10 +294,6 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mOnCancelRunnable = runnable; } - public Runnable getOnCancelRunnable() { - return mOnCancelRunnable; - } - public void skipToEnd() { mSkipToEnd = true; for (SpringAnimation spring : mSprings) { diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index c45cd85aad..fccc120900 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -39,6 +39,7 @@ public class Interpolators { public static final Interpolator LINEAR = new LinearInterpolator(); public static final Interpolator ACCEL = new AccelerateInterpolator(); + public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f); public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f); public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2); @@ -48,6 +49,7 @@ public class Interpolators { public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2); public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f); public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f); + public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f); public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator(); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index f4b705e99d..ae30380c78 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -69,16 +69,8 @@ public final class FeatureFlags { * * To add a new flag that can be toggled through the flags UI: * - * 1. Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"), + * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"), * and set a default value for the flag. This will be the default value on Debug builds. - * - * 2. Add your flag to mTogglableFlags. - * - * 3. Create a getter method (an 'is' method) for the flag by copying an existing one. - * - * 4. Create a getter method with the same name in the release flags copy of FeatureFlags.java. - * This should returns a constant (true/false). This will be the value of the flag used on - * release builds. */ // When enabled the promise icon is visible in all apps while installation an app. public static final TogglableFlag PROMISE_APPS_IN_ALL_APPS = new TogglableFlag( diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 6b9e205258..33da582293 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -84,6 +84,7 @@ import com.android.launcher3.views.ClipPathView; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -148,7 +149,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public ExtendedEditText mFolderName; private PageIndicatorDots mPageIndicator; - private View mFooter; + protected View mFooter; private int mFooterHeight; // Cell ranks used for drag and drop @@ -217,7 +218,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); - mFolderName.forceDisableSuggestions(true); + mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get()); mFooter = findViewById(R.id.folder_footer); @@ -412,19 +413,20 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo }); } - /** * Show suggested folder title. */ - public void showSuggestedTitle(CharSequence suggestName) { + public void showSuggestedTitle(String[] suggestName) { if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) { - if (!TextUtils.isEmpty(suggestName)) { - mFolderName.setHint(suggestName); - mFolderName.setText(suggestName); + if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) { + mFolderName.setHint(suggestName[0]); + mFolderName.setText(suggestName[0]); + mInfo.title = suggestName[0]; + animateOpen(); mFolderName.showKeyboard(); - mInfo.title = suggestName; + mFolderName.displayCompletions( + Arrays.asList(suggestName).subList(1, suggestName.length)); } - animateOpen(); } } @@ -991,7 +993,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.y = top; } - private int getContentAreaHeight() { + protected int getContentAreaHeight() { DeviceProfile grid = mLauncher.getDeviceProfile(); int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y - mFooterHeight; diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 9eb06938a3..1310d374ea 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -59,6 +59,8 @@ import java.util.List; */ public class FolderAnimationManager { + private static final int FOLDER_NAME_ALPHA_DURATION = 32; + private Folder mFolder; private FolderPagedView mContent; private GradientDrawable mFolderBackground; @@ -130,11 +132,19 @@ public class FolderAnimationManager { * scaleRelativeToDragLayer; final float finalScale = 1f; float scale = mIsOpening ? initialScale : finalScale; - mFolder.setScaleX(scale); - mFolder.setScaleY(scale); mFolder.setPivotX(0); mFolder.setPivotY(0); + // Scale the contents of the folder. + mFolder.mContent.setScaleX(scale); + mFolder.mContent.setScaleY(scale); + mFolder.mContent.setPivotX(0); + mFolder.mContent.setPivotY(0); + mFolder.mFooter.setScaleX(scale); + mFolder.mFooter.setScaleY(scale); + mFolder.mFooter.setPivotX(0); + mFolder.mFooter.setPivotY(0); + // We want to create a small X offset for the preview items, so that they follow their // expected path to their final locations. ie. an icon should not move right, if it's final // location is to its left. This value is arbitrarily defined. @@ -143,14 +153,13 @@ public class FolderAnimationManager { previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX); } - final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft()) - * initialScale); - final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop()) - * initialScale); + final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale); + final int paddingOffsetY = (int) (mContent.getPaddingTop() * initialScale); - int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX - - previewItemOffsetX; - int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY; + int initialX = folderIconPos.left + mFolder.getPaddingLeft() + + mPreviewBackground.getOffsetX() - paddingOffsetX - previewItemOffsetX; + int initialY = folderIconPos.top + mFolder.getPaddingTop() + + mPreviewBackground.getOffsetY() - paddingOffsetY; final float xDistance = initialX - lp.x; final float yDistance = initialY - lp.y; @@ -164,11 +173,10 @@ public class FolderAnimationManager { // Set up the reveal animation that clips the Folder. int totalOffsetX = paddingOffsetX + previewItemOffsetX; - Rect startRect = new Rect( - Math.round(totalOffsetX / initialScale), - Math.round(paddingOffsetY / initialScale), - Math.round((totalOffsetX + initialSize) / initialScale), - Math.round((paddingOffsetY + initialSize) / initialScale)); + Rect startRect = new Rect(totalOffsetX, + paddingOffsetY, + Math.round((totalOffsetX + initialSize)), + Math.round((paddingOffsetY + initialSize))); Rect endRect = new Rect(0, 0, lp.width, lp.height); float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics()); @@ -189,17 +197,46 @@ public class FolderAnimationManager { play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f)); play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f)); - play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale)); + play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale)); + play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale)); play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor)); play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening)); play(a, getShape().createRevealAnimator( mFolder, startRect, endRect, finalRadius, !mIsOpening)); + // Fade in the folder name, as the text can overlap the icons when grid size is small. + mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f); + play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1), + mIsOpening ? FOLDER_NAME_ALPHA_DURATION : 0, + mIsOpening ? mDuration - FOLDER_NAME_ALPHA_DURATION : FOLDER_NAME_ALPHA_DURATION); + + // Translate the footer so that it tracks the bottom of the content. + float normalHeight = mFolder.getContentAreaHeight(); + float scaledHeight = normalHeight * initialScale; + float diff = normalHeight - scaledHeight; + play(a, getAnimator(mFolder.mFooter, View.TRANSLATION_Y, -diff, 0f)); // Animate the elevation midway so that the shadow is not noticeable in the background. int midDuration = mDuration / 2; Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0); play(a, z, mIsOpening ? midDuration : 0, midDuration); + + // Store clip variables + CellLayout cellLayout = mContent.getCurrentCellLayout(); + boolean folderClipChildren = mFolder.getClipChildren(); + boolean folderClipToPadding = mFolder.getClipToPadding(); + boolean contentClipChildren = mContent.getClipChildren(); + boolean contentClipToPadding = mContent.getClipToPadding(); + boolean cellLayoutClipChildren = cellLayout.getClipChildren(); + boolean cellLayoutClipPadding = cellLayout.getClipToPadding(); + + mFolder.setClipChildren(false); + mFolder.setClipToPadding(false); + mContent.setClipChildren(false); + mContent.setClipToPadding(false); + cellLayout.setClipChildren(false); + cellLayout.setClipToPadding(false); + a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -207,8 +244,20 @@ public class FolderAnimationManager { mFolder.setTranslationX(0.0f); mFolder.setTranslationY(0.0f); mFolder.setTranslationZ(0.0f); - mFolder.setScaleX(1f); - mFolder.setScaleY(1f); + mFolder.mContent.setScaleX(1f); + mFolder.mContent.setScaleY(1f); + mFolder.mFooter.setScaleX(1f); + mFolder.mFooter.setScaleY(1f); + mFolder.mFooter.setTranslationX(0f); + mFolder.mFolderName.setAlpha(1f); + + mFolder.setClipChildren(folderClipChildren); + mFolder.setClipToPadding(folderClipToPadding); + mContent.setClipChildren(contentClipChildren); + mContent.setClipToPadding(contentClipToPadding); + cellLayout.setClipChildren(cellLayoutClipChildren); + cellLayout.setClipToPadding(cellLayoutClipPadding); + } }); diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index fd6d1e38a9..7bbd45dac3 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -371,22 +371,31 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true); final int finalIndex = index; - String[] suggestedNameOut = new String[1]; + String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX]; if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { - Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider() - .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut)); + Executors.UI_HELPER_EXECUTOR.post(() -> { + mLauncher.getFolderNameProvider().getSuggestedFolderName( + getContext(), mInfo.contents, suggestedNameOut); + showFinalView(finalIndex, item, suggestedNameOut); + }); + } else { + showFinalView(finalIndex, item, suggestedNameOut); } - postDelayed(() -> { - mPreviewItemManager.hidePreviewItem(finalIndex, false); - mFolder.showItem(item); - invalidate(); - mFolder.showSuggestedTitle(suggestedNameOut[0]); - }, DROP_IN_ANIMATION_DURATION); } else { addItem(item); } } + private void showFinalView(int finalIndex, final WorkspaceItemInfo item, + String[] suggestedNameOut) { + postDelayed(() -> { + mPreviewItemManager.hidePreviewItem(finalIndex, false); + mFolder.showItem(item); + invalidate(); + mFolder.showSuggestedTitle(suggestedNameOut); + }, DROP_IN_ANIMATION_DURATION); + } + public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) { WorkspaceItemInfo item; if (d.dragInfo instanceof AppInfo) { diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java index 0a1221e696..37aa815771 100644 --- a/src/com/android/launcher3/folder/FolderNameProvider.java +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -28,6 +28,12 @@ import java.util.ArrayList; */ public class FolderNameProvider { + /** + * IME usually has up to 3 suggest slots. Adding one as in Launcher, there are folder + * name edit box that we can also provide suggestion. + */ + public static final int SUGGEST_MAX = 4; + /** * Returns suggested folder name. */ diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index b2c0ca7213..2d177d2f2d 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -48,7 +48,7 @@ import com.android.launcher3.views.ActivityContext; * This object represents a FolderIcon preview background. It stores drawing / measurement * information, handles drawing, and animation (accept state <--> rest state). */ -public class PreviewBackground { +public class PreviewBackground extends CellLayout.DelegatedCellDrawing { private static final int CONSUMPTION_ANIMATION_DURATION = 100; @@ -76,8 +76,6 @@ public class PreviewBackground { int basePreviewOffsetY; private CellLayout mDrawingDelegate; - public int delegateCellX; - public int delegateCellY; // When the PreviewBackground is drawn under an icon (for creating a folder) the border // should not occlude the icon @@ -124,6 +122,27 @@ public class PreviewBackground { } }; + /** + * Draws folder background under cell layout + */ + @Override + public void drawUnderItem(Canvas canvas) { + drawBackground(canvas); + if (!isClipping) { + drawBackgroundStroke(canvas); + } + } + + /** + * Draws folder background on cell layout + */ + @Override + public void drawOverItem(Canvas canvas) { + if (isClipping) { + drawBackgroundStroke(canvas); + } + } + public void setup(Context context, ActivityContext activity, View invalidateDelegate, int availableSpaceX, int topPadding) { mInvalidateDelegate = invalidateDelegate; @@ -317,19 +336,19 @@ public class PreviewBackground { private void delegateDrawing(CellLayout delegate, int cellX, int cellY) { if (mDrawingDelegate != delegate) { - delegate.addFolderBackground(this); + delegate.addDelegatedCellDrawing(this); } mDrawingDelegate = delegate; - delegateCellX = cellX; - delegateCellY = cellY; + mDelegateCellX = cellX; + mDelegateCellY = cellY; invalidate(); } private void clearDrawingDelegate() { if (mDrawingDelegate != null) { - mDrawingDelegate.removeFolderBackground(this); + mDrawingDelegate.removeDelegatedCellDrawing(this); } mDrawingDelegate = null; @@ -395,8 +414,8 @@ public class PreviewBackground { // is saved and restored at the beginning of the animation, since cancelling the // existing animation can clear the delgate. CellLayout cl = mDrawingDelegate; - int cellX = delegateCellX; - int cellY = delegateCellY; + int cellX = mDelegateCellX; + int cellY = mDelegateCellY; animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate); } diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index a9242f94e0..3668313bdc 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.settings; +import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; + import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED; import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey; @@ -24,28 +26,21 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.util.ArrayMap; -import android.util.ArraySet; +import android.util.Pair; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.FlagTogglerPrefUi; -import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; - -import java.util.List; -import java.util.Set; - import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceDataStore; @@ -54,6 +49,16 @@ import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; +import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.FlagTogglerPrefUi; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + /** * Dev-build only UI allowing developers to toggle flag settings and plugins. * See {@link FeatureFlags}. @@ -154,44 +159,53 @@ public class DeveloperOptionsFragment extends PreferenceFragment { PackageManager pm = getContext().getPackageManager(); Set pluginActions = manager.getPluginActions(); - ArrayMap> plugins = new ArrayMap<>(); + + ArrayMap, ArrayList>> plugins = + new ArrayMap<>(); + + Set pluginPermissionApps = pm.getPackagesHoldingPermissions( + new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS) + .stream() + .map(pi -> pi.packageName) + .collect(Collectors.toSet()); + for (String action : pluginActions) { String name = toName(action); List result = pm.queryIntentServices( - new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS); + new Intent(action), MATCH_DISABLED_COMPONENTS); for (ResolveInfo info : result) { String packageName = info.serviceInfo.packageName; - if (!plugins.containsKey(packageName)) { - plugins.put(packageName, new ArraySet<>()); + if (!pluginPermissionApps.contains(packageName)) { + continue; } - plugins.get(packageName).add(name); + + Pair key = Pair.create(packageName, info.serviceInfo.processName); + if (!plugins.containsKey(key)) { + plugins.put(key, new ArrayList<>()); + } + plugins.get(key).add(Pair.create(name, info.serviceInfo)); } } - List apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION}, - PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES); - PreferenceDataStore enabled = manager.getPluginEnabler(); - apps.forEach(app -> { - if (!plugins.containsKey(app.packageName)) return; - SwitchPreference pref = new PluginPreference(prefContext, app, enabled); - pref.setSummary("Plugins: " + toString(plugins.get(app.packageName))); - mPluginsCategory.addPreference(pref); + PreferenceDataStore enabler = manager.getPluginEnabler(); + plugins.forEach((key, si) -> { + String packageName = key.first; + List componentNames = si.stream() + .map(p -> new ComponentName(packageName, p.second.name)) + .collect(Collectors.toList()); + if (!componentNames.isEmpty()) { + SwitchPreference pref = new PluginPreference( + prefContext, si.get(0).second.applicationInfo, enabler, componentNames); + pref.setSummary("Plugins: " + + si.stream().map(p -> p.first).collect(Collectors.joining(", "))); + mPluginsCategory.addPreference(pref); + } }); } - private String toString(ArraySet plugins) { - StringBuilder b = new StringBuilder(); - for (String string : plugins) { - if (b.length() != 0) { - b.append(", "); - } - b.append(string); - } - return b.toString(); - } - private String toName(String action) { - String str = action.replace("com.android.systemui.action.PLUGIN_", ""); + String str = action.replace("com.android.systemui.action.PLUGIN_", "") + .replace("com.android.launcher3.action.PLUGIN_", ""); StringBuilder b = new StringBuilder(); for (String s : str.split("_")) { if (b.length() != 0) { @@ -205,18 +219,20 @@ public class DeveloperOptionsFragment extends PreferenceFragment { private static class PluginPreference extends SwitchPreference { private final boolean mHasSettings; - private final PackageInfo mInfo; private final PreferenceDataStore mPluginEnabler; + private final String mPackageName; + private final List mComponentNames; - public PluginPreference(Context prefContext, PackageInfo info, - PreferenceDataStore pluginEnabler) { + PluginPreference(Context prefContext, ApplicationInfo info, + PreferenceDataStore pluginEnabler, List componentNames) { super(prefContext); PackageManager pm = prefContext.getPackageManager(); mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) .setPackage(info.packageName), 0) != null; - mInfo = info; + mPackageName = info.packageName; + mComponentNames = componentNames; mPluginEnabler = pluginEnabler; - setTitle(info.applicationInfo.loadLabel(pm)); + setTitle(info.loadLabel(pm)); setChecked(isPluginEnabled()); setWidgetLayoutResource(R.layout.switch_preference_with_settings); } @@ -227,9 +243,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { } private boolean isPluginEnabled() { - for (int i = 0; i < mInfo.services.length; i++) { - ComponentName componentName = new ComponentName(mInfo.packageName, - mInfo.services[i].name); + for (ComponentName componentName : mComponentNames) { if (!isEnabled(componentName)) { return false; } @@ -240,17 +254,14 @@ public class DeveloperOptionsFragment extends PreferenceFragment { @Override protected boolean persistBoolean(boolean isEnabled) { boolean shouldSendBroadcast = false; - for (int i = 0; i < mInfo.services.length; i++) { - ComponentName componentName = new ComponentName(mInfo.packageName, - mInfo.services[i].name); - + for (ComponentName componentName : mComponentNames) { if (isEnabled(componentName) != isEnabled) { mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled); shouldSendBroadcast = true; } } if (shouldSendBroadcast) { - final String pkg = mInfo.packageName; + final String pkg = mPackageName; final Intent intent = new Intent(PLUGIN_CHANGED, pkg != null ? Uri.fromParts("package", pkg, null) : null); getContext().sendBroadcast(intent); @@ -268,8 +279,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { : View.GONE); holder.findViewById(R.id.settings).setOnClickListener(v -> { ResolveInfo result = v.getContext().getPackageManager().resolveActivity( - new Intent(ACTION_PLUGIN_SETTINGS).setPackage( - mInfo.packageName), 0); + new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0); if (result != null) { v.getContext().startActivity(new Intent().setComponent( new ComponentName(result.activityInfo.packageName, @@ -278,7 +288,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { }); holder.itemView.setOnLongClickListener(v -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", mInfo.packageName, null)); + intent.setData(Uri.fromParts("package", mPackageName, null)); getContext().startActivity(intent); return true; }); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 60f6ee9c52..f40f9762d0 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -413,10 +413,7 @@ public abstract class AbstractStateChangeTouchController } else { // Let the state manager know that the animation didn't go to the target state, // but don't cancel ourselves (we already clean up when the animation completes). - Runnable onCancel = mCurrentAnimation.getOnCancelRunnable(); - mCurrentAnimation.setOnCancelRunnable(null); - mCurrentAnimation.dispatchOnCancel(); - mCurrentAnimation.setOnCancelRunnable(onCancel); + mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(); endProgress = 0; if (progress <= 0) { diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java new file mode 100644 index 0000000000..8a75767f42 --- /dev/null +++ b/src/com/android/launcher3/util/DynamicResource.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import android.content.Context; + +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.FractionRes; +import androidx.annotation.IntegerRes; + +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.ResourceProvider; + +/** + * Utility class to support customizing resource values using plugins + * + * To load resources, call + * DynamicResource.provider(context).getInt(resId) or any other supported methods + * + * To allow customization for a particular resource, add them to dynamic_resources.xml + */ +public class DynamicResource implements ResourceProvider, PluginListener { + + private static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(DynamicResource::new); + + private final Context mContext; + private ResourceProvider mPlugin; + + private DynamicResource(Context context) { + mContext = context; + PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this, + ResourceProvider.class, false /* allowedMultiple */); + } + + @Override + public int getInt(@IntegerRes int resId) { + return mContext.getResources().getInteger(resId); + } + + @Override + public float getFraction(@FractionRes int resId) { + return mContext.getResources().getFraction(resId, 1, 1); + } + + @Override + public float getDimension(@DimenRes int resId) { + return mContext.getResources().getDimension(resId); + } + + @Override + public int getColor(@ColorRes int resId) { + return mContext.getResources().getColor(resId, null); + } + + @Override + public void onPluginConnected(ResourceProvider plugin, Context context) { + mPlugin = plugin; + } + + @Override + public void onPluginDisconnected(ResourceProvider plugin) { + mPlugin = null; + } + + /** + * Returns the currently active or default provider + */ + public static ResourceProvider provider(Context context) { + DynamicResource dr = DynamicResource.INSTANCE.get(context); + ResourceProvider plugin = dr.mPlugin; + return plugin == null ? dr : plugin; + } +} diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index c63d745e0d..0c9a28bba0 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -560,7 +560,7 @@ public class FloatingIconView extends View implements * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a * callback to set the icon once the icon result is loaded. */ - private void checkIconResult(View originalView, boolean isOpening) { + private void checkIconResult(View originalView) { CancellationSignal cancellationSignal = new CancellationSignal(); if (mIconLoadResult == null) { @@ -572,9 +572,7 @@ public class FloatingIconView extends View implements if (mIconLoadResult.isIconLoaded) { setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); - if (isOpening) { - hideOriginalView(originalView); - } + hideOriginalView(originalView); } else { mIconLoadResult.onIconLoaded = () -> { if (cancellationSignal.isCanceled()) { @@ -585,10 +583,7 @@ public class FloatingIconView extends View implements mIconLoadResult.iconOffset); setVisibility(VISIBLE); - if (isOpening) { - // Delay swapping views until the icon is loaded to prevent a flash. - hideOriginalView(originalView); - } + hideOriginalView(originalView); }; mLoadIconSignal = cancellationSignal; } @@ -596,9 +591,9 @@ public class FloatingIconView extends View implements } private void hideOriginalView(View originalView) { - if (originalView instanceof BubbleTextView) { - ((BubbleTextView) originalView).setIconVisible(false); - ((BubbleTextView) originalView).setForceHideDot(true); + if (originalView instanceof IconLabelDotView) { + ((IconLabelDotView) originalView).setIconVisible(false); + ((IconLabelDotView) originalView).setForceHideDot(true); } else { originalView.setVisibility(INVISIBLE); } @@ -674,6 +669,9 @@ public class FloatingIconView extends View implements } public void fastFinish() { + if (mLoadIconSignal != null) { + mLoadIconSignal.cancel(); + } if (mEndRunnable != null) { mEndRunnable.run(); mEndRunnable = null; @@ -689,6 +687,10 @@ public class FloatingIconView extends View implements if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) { setVisibility(View.VISIBLE); } + if (!mIsOpening) { + // When closing an app, we want the item on the workspace to be invisible immediately + hideOriginalView(mOriginalIcon); + } } @Override @@ -798,7 +800,7 @@ public class FloatingIconView extends View implements // Must be called after the fastFinish listener and end runnable is created so that // the icon is not left in a hidden state. if (shouldLoadIcon) { - view.checkIconResult(originalView, isOpening); + view.checkIconResult(originalView); } return view; @@ -842,6 +844,7 @@ public class FloatingIconView extends View implements @Override public void onAnimationStart(Animator animation) { btv.setIconVisible(true); + btv.setForceHideDot(true); } }); fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255)); diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 9f59d78217..e64b2fb483 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -77,7 +77,7 @@ import java.util.List; /** * Simple scrim which draws a flat color */ -public class ScrimView extends View implements Insettable, OnChangeListener, +public class ScrimView extends View implements Insettable, OnChangeListener, AccessibilityStateChangeListener, StateListener { public static final Property DRAG_HANDLE_ALPHA = @@ -101,7 +101,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; - protected final Launcher mLauncher; + protected final T mLauncher; private final WallpaperColorInfo mWallpaperColorInfo; private final AccessibilityManager mAM; protected final int mEndScrim; @@ -130,7 +130,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); - mLauncher = Launcher.getLauncher(context); + mLauncher = Launcher.cast(Launcher.getLauncher(context)); mWallpaperColorInfo = WallpaperColorInfo.getInstance(context); mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor); diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java new file mode 100644 index 0000000000..eaed9e7f1c --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.plugins; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Plugin to support customizing resource + */ +@ProvidesInterface(action = ResourceProvider.ACTION, version = ResourceProvider.VERSION) +public interface ResourceProvider extends Plugin { + String ACTION = "com.android.launcher3.action.PLUGIN_DYNAMIC_RESOURCE"; + int VERSION = 1; + + /** + * @see android.content.res.Resources#getInteger(int) + */ + int getInt(int resId); + + /** + * @see android.content.res.Resources#getFraction(int, int, int) + */ + float getFraction(int resId); + + /** + * @see android.content.res.Resources#getDimension(int) + */ + float getDimension(int resId); + + /** + * @see android.content.res.Resources#getColor(int) + */ + int getColor(int resId); +} diff --git a/tests/Android.mk b/tests/Android.mk index 31a996084c..83fdddce28 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -50,7 +50,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ androidx.test.runner \ androidx.test.rules \ androidx.test.uiautomator_uiautomator \ - mockito-target-minus-junit4 + mockito-target-minus-junit4 \ + launcher-log-protos-lite ifneq (,$(wildcard frameworks/base)) LOCAL_PRIVATE_PLATFORM_APIS := true diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index bb19515dd0..4243ed041f 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -122,10 +122,6 @@ public abstract class AbstractLauncherUiTest { protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule(); - @Rule - public ShellCommandRule mDefaultLauncherRule = - TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null; - @Rule public ShellCommandRule mDisableHeadsUpNotification = ShellCommandRule.disableHeadsUpNotification(); @@ -154,9 +150,13 @@ public abstract class AbstractLauncherUiTest { } protected TestRule getRulesInsideActivityMonitor() { - return RuleChain. - outerRule(new PortraitLandscapeRunner(this)). - around(new FailureWatcher(mDevice)); + final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this)) + .around(new FailureWatcher(mDevice)); + + return TestHelpers.isInLauncherProcess() + ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()) + .around(inner) : + inner; } @Rule diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index d26fac94a6..366be1dd7d 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -560,10 +560,12 @@ public final class LauncherInstrumentation { final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName())) ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL; - swipeToState( - displaySize.x / 2, displaySize.y - 1, - displaySize.x / 2, 0, - ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState); + try (LauncherInstrumentation.Closable c = addContextLayer(action)) { + swipeToState( + displaySize.x / 2, displaySize.y - 1, + displaySize.x / 2, 0, + ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState); + } } } else { log(action = "clicking home button"); @@ -873,12 +875,17 @@ public final class LauncherInstrumentation { final Point start = new Point(startX, startY); final Point end = new Point(endX, endY); sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start); + final long endTime = movePointer(start, end, steps, downTime, slowDown); + sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end); + } + + long movePointer(Point start, Point end, int steps, long downTime, boolean slowDown) { long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end); if (slowDown) { endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end, end); } - sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end); + return endTime; } void waitForIdle() { diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 3301dd87d8..81d343d38b 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -39,7 +39,6 @@ import com.android.launcher3.testing.TestProtocol; * Operations on the workspace screen. */ public final class Workspace extends Home { - private static final int DRAG_DURATION = 500; private static final int FLING_STEPS = 10; private final UiObject2 mHotseat; @@ -170,8 +169,7 @@ public final class Workspace extends Home { LauncherInstrumentation.log("dragIconToWorkspace: sent down"); launcher.waitForLauncherObject(longPressIndicator); LauncherInstrumentation.log("dragIconToWorkspace: indicator"); - launcher.movePointer( - downTime, SystemClock.uptimeMillis(), DRAG_DURATION, launchableCenter, dest); + launcher.movePointer(launchableCenter, dest, 10, downTime, true); LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); launcher.sendPointer( downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);