Enable Dragging on predicted hybrid hotseat items

Bug: 142753423
Test: Manual
Change-Id: I2d54f82129d30bd289d05039e009a10543d55f34
This commit is contained in:
Samuel Fufa 2019-12-04 12:11:57 -08:00
parent 26b0938f09
commit 2c96dbc46c
4 changed files with 111 additions and 52 deletions

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:inset="@dimen/predicted_icon_background_inset">
<shape>
<solid android:color="?attr/folderFillColor" />
<corners android:radius="@dimen/predicted_icon_background_corner_radius" />
</shape>
</inset>

View File

@ -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<QuickstepLauncher>,
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<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
@ -87,10 +91,18 @@ public class HotseatPredictionController implements DragController.DragListener,
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
private AnimatorSet mIconRemoveAnimators;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> 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<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
int predictionIndex = 0;
ArrayList<WorkspaceItemInfo> 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<PredictedAppIcon.PredictedIconOutlineDrawing> outlines) {
private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> 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<QuickstepLauncher> {
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<ComponentKeyMapper> predictedApps = sInstance.mComponentKeyMappers;
final List<ComponentKeyMapper> 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) {

View File

@ -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();

View File

@ -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.
*/