diff --git a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml deleted file mode 100644 index cfc6d4801d..0000000000 --- a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - 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 f7e71f3731..b94142ab62 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -44,6 +44,7 @@ import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; 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.userevent.nano.LauncherLogProto; @@ -61,7 +62,7 @@ import java.util.stream.IntStream; public class HotseatPredictionController implements DragController.DragListener, View.OnAttachStateChangeListener, SystemShortcut.Factory, InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener, - IconCache.ItemInfoUpdateReceiver { + IconCache.ItemInfoUpdateReceiver, DragSource { private static final String TAG = "PredictiveHotseat"; private static final boolean DEBUG = false; @@ -72,6 +73,9 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String APP_LOCATION_HOTSEAT = "hotseat"; private static final String APP_LOCATION_WORKSPACE = "workspace"; + private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps"; + private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps"; + private static final String PREDICTION_CLIENT = "hotseat"; private DropTarget.DragObject mDragObject; @@ -79,7 +83,7 @@ public class HotseatPredictionController implements DragController.DragListener, private int mPredictedSpotsCount = 0; private Launcher mLauncher; - private Hotseat mHotseat; + private final Hotseat mHotseat; private List mComponentKeyMappers = new ArrayList<>(); @@ -87,10 +91,18 @@ public class HotseatPredictionController implements DragController.DragListener, private AppPredictor mAppPredictor; private AllAppsStore mAllAppsStore; + private AnimatorSet mIconRemoveAnimators; + private List mOutlineDrawings = new ArrayList<>(); - private static HotseatPredictionController sInstance; + private final View.OnLongClickListener mPredictionLongClickListener = v -> { + if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; + if (mLauncher.getWorkspace().isSwitchingState()) return false; + // Start the drag + mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions()); + return false; + }; public HotseatPredictionController(Launcher launcher) { mLauncher = launcher; @@ -101,7 +113,9 @@ public class HotseatPredictionController implements DragController.DragListener, mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); - sInstance = this; + if (mHotseat.isAttachedToWindow()) { + onViewAttachedToWindow(mHotseat); + } } @Override @@ -125,6 +139,17 @@ public class HotseatPredictionController implements DragController.DragListener, List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers); int predictionIndex = 0; ArrayList newItems = new ArrayList<>(); + // make sure predicted icon removal and filling predictions don't step on each other + if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) { + mIconRemoveAnimators.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + fillGapsWithPrediction(animate, callback); + mIconRemoveAnimators.removeListener(this); + } + }); + return; + } for (int rank = 0; rank < mHotSeatItemsCount; rank++) { View child = mHotseat.getChildAt( mHotseat.getCellXFromOrder(rank), @@ -140,12 +165,11 @@ public class HotseatPredictionController implements DragController.DragListener, } continue; } - WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); if (isPredictedIcon(child) && child.isEnabled()) { PredictedAppIcon icon = (PredictedAppIcon) child; icon.applyFromWorkspaceItem(predictedItem); - icon.finishBinding(); + icon.finishBinding(mPredictionLongClickListener); } else { newItems.add(predictedItem); } @@ -160,7 +184,7 @@ public class HotseatPredictionController implements DragController.DragListener, for (WorkspaceItemInfo item : itemsToAdd) { PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item); mLauncher.getWorkspace().addInScreenFromBind(icon, item); - icon.finishBinding(); + icon.finishBinding(mPredictionLongClickListener); if (animate) { animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1)); } @@ -215,9 +239,9 @@ public class HotseatPredictionController implements DragController.DragListener, private Bundle getAppPredictionContextExtra() { Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(APP_LOCATION_HOTSEAT, + bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT, getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets()))); - bundle.putParcelableArrayList(APP_LOCATION_WORKSPACE, getPinnedAppTargetsInViewGroup( + bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup( mLauncher.getWorkspace().getScreenWithId( Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets())); return bundle; @@ -285,9 +309,12 @@ public class HotseatPredictionController implements DragController.DragListener, ItemInfoWithIcon info = mapper.getApp(allAppsStore); if (info instanceof AppInfo) { WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info); + predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; predictedApps.add(predictedApp); } else if (info instanceof WorkspaceItemInfo) { - predictedApps.add(new WorkspaceItemInfo((WorkspaceItemInfo) info)); + WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info); + predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; + predictedApps.add(predictedApp); } else { if (DEBUG) { Log.e(TAG, "Predicted app not found: " + mapper); @@ -313,13 +340,27 @@ public class HotseatPredictionController implements DragController.DragListener, return icons; } - private void removePredictedApps(List outlines) { + private void removePredictedApps(List outlines, + ItemInfo draggedInfo) { + if (mIconRemoveAnimators != null) { + mIconRemoveAnimators.end(); + } + mIconRemoveAnimators = new AnimatorSet(); + removeOutlineDrawings(); for (PredictedAppIcon icon : getPredictedIcons()) { + if (!icon.isEnabled()) { + continue; + } + if (icon.getTag().equals(draggedInfo)) { + mHotseat.removeView(icon); + continue; + } int rank = ((WorkspaceItemInfo) icon.getTag()).rank; outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing( mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon)); icon.setEnabled(false); - icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { + ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0); + animator.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { if (icon.getParent() != null) { @@ -327,10 +368,11 @@ public class HotseatPredictionController implements DragController.DragListener, } } }); + mIconRemoveAnimators.play(animator); } + mIconRemoveAnimators.start(); } - private void notifyItemAction(AppTarget target, String location, int action) { if (mAppPredictor != null) { mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, @@ -340,7 +382,7 @@ public class HotseatPredictionController implements DragController.DragListener, @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - removePredictedApps(mOutlineDrawings); + removePredictedApps(mOutlineDrawings, dragObject.dragInfo); mDragObject = dragObject; if (mOutlineDrawings.isEmpty()) return; for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { @@ -354,14 +396,25 @@ public class HotseatPredictionController implements DragController.DragListener, if (mDragObject == null) { return; } + ItemInfo dragInfo = mDragObject.dragInfo; - if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) { + ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); + ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( + Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); + + if (dragInfo instanceof WorkspaceItemInfo + && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && dragInfo.getTargetComponent() != null) { AppTarget appTarget = getAppTargetFromItemInfo(dragInfo); if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); + if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) { + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); + } } if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) { + notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + } } if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); @@ -371,14 +424,7 @@ public class HotseatPredictionController implements DragController.DragListener, } } mDragObject = null; - fillGapsWithPrediction(true, () -> { - if (mOutlineDrawings.isEmpty()) return; - for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { - mHotseat.removeDelegatedCellDrawing(outlineDrawing); - } - mHotseat.invalidate(); - mOutlineDrawings.clear(); - }); + fillGapsWithPrediction(true, this::removeOutlineDrawings); } @Nullable @@ -394,11 +440,20 @@ public class HotseatPredictionController implements DragController.DragListener, private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) { itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; itemInfo.rank = rank; - itemInfo.cellX = rank; - itemInfo.cellY = mHotSeatItemsCount - rank - 1; + itemInfo.cellX = mHotseat.getCellXFromOrder(rank); + itemInfo.cellY = mHotseat.getCellYFromOrder(rank); itemInfo.screenId = rank; } + private void removeOutlineDrawings() { + if (mOutlineDrawings.isEmpty()) return; + for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { + mHotseat.removeDelegatedCellDrawing(outlineDrawing); + } + mHotseat.invalidate(); + mOutlineDrawings.clear(); + } + @Override public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { this.mHotSeatItemsCount = profile.numHotseatIcons; @@ -415,6 +470,17 @@ public class HotseatPredictionController implements DragController.DragListener, } + @Override + public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) { + //Does nothing + } + + @Override + public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target, + LauncherLogProto.Target targetParent) { + mHotseat.fillInLogContainerData(v, info, target, targetParent); + } + private class PinPrediction extends SystemShortcut { private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) { @@ -435,18 +501,22 @@ public class HotseatPredictionController implements DragController.DragListener, */ public static void fillInHybridHotseatRank( @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { - if (sInstance == null || itemInfo.getTargetComponent() == null + QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); + if (launcher == null || launcher.getHotseatPredictionController() == null + || itemInfo.getTargetComponent() == null || itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { return; } + HotseatPredictionController controller = launcher.getHotseatPredictionController(); + final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); - final List predictedApps = sInstance.mComponentKeyMappers; + final List predictedApps = controller.mComponentKeyMappers; IntStream.range(0, predictedApps.size()) .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) .findFirst() .ifPresent((rank) -> target.predictedRank = - Integer.parseInt(sInstance.mPredictedSpotsCount + "0" + rank)); + Integer.parseInt(controller.mPredictedSpotsCount + "0" + rank)); } private static boolean isPredictedIcon(View view) { @@ -461,8 +531,7 @@ public class HotseatPredictionController implements DragController.DragListener, } ItemInfo info = (ItemInfo) view.getTag(); return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && ( - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); + info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION); } private static boolean isInHotseat(ItemInfo itemInfo) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index 1dcbffb38f..27ac284037 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -29,7 +29,6 @@ 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; @@ -37,7 +36,6 @@ 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; @@ -47,14 +45,13 @@ import com.android.launcher3.views.DoubleShadowBubbleTextView; */ public class PredictedAppIcon extends DoubleShadowBubbleTextView { - private static final float RING_EFFECT_RATIO = 0.12f; + private static final float RING_EFFECT_RATIO = 0.11f; 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); } @@ -105,14 +102,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView { /** * prepares prediction icon for usage after bind */ - public void finishBinding() { - setOnLongClickListener((v) -> { - PopupContainerWithArrow.showForIcon((BubbleTextView) v); - if (getParent() != null) { - getParent().requestDisallowInterceptTouchEvent(true); - } - return true; - }); + public void finishBinding(OnLongClickListener longClickListener) { + setOnLongClickListener(longClickListener); ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false; setTextVisibility(false); verifyHighRes(); 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 37a3929818..cae01aefba 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 @@ -177,6 +177,13 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + /** + * Returns Prediction controller for hybrid hotseat + */ + public HotseatPredictionController getHotseatPredictionController() { + return mHotseatPredictionController; + } + /** * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. */