blob: 9bc097527ca501863d44147dc319afee2e7941ee [file] [log] [blame]
/*
* 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.hybridhotseat;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.logging.LoggerUtils.newAction;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.appprediction.ComponentKeyMapper;
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.logging.FileLog;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.DeviceFlag;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
/**
* Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
* pinning of predicted apps and manages replacement of predicted apps with user drag.
*/
public class HotseatPredictionController implements DragController.DragListener,
View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
IconCache.ItemInfoUpdateReceiver, DragSource {
private static final String TAG = "PredictiveHotseat";
private static final boolean DEBUG = false;
public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt(
DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5);
//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 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;
private int mHotSeatItemsCount;
private int mPredictedSpotsCount = 0;
private Launcher mLauncher;
private final Hotseat mHotseat;
private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
private DynamicItemCache mDynamicItemCache;
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
private AnimatorSet mIconRemoveAnimators;
private boolean mUIUpdatePaused = false;
private HotseatEduController mHotseatEduController;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
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;
mHotseat = launcher.getHotseat();
mAllAppsStore = mLauncher.getAppsView().getAppsStore();
mAllAppsStore.addUpdateListener(this);
mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
if (mHotseat.isAttachedToWindow()) {
onViewAttachedToWindow(mHotseat);
}
}
/**
* Returns whether or not the prediction controller is ready to show predictions
*/
public boolean isReady() {
return mLauncher.getSharedPrefs().getBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
false);
}
/**
* Transitions to NORMAL workspace mode and shows edu
*/
public void showEdu() {
if (mHotseatEduController == null) return;
mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
() -> mHotseatEduController.showEdu());
}
@Override
public void onViewAttachedToWindow(View view) {
mLauncher.getDragController().addDragListener(this);
}
@Override
public void onViewDetachedFromWindow(View view) {
mLauncher.getDragController().removeDragListener(this);
}
private void fillGapsWithPrediction() {
fillGapsWithPrediction(false, null);
}
private void fillGapsWithPrediction(boolean animate, Runnable callback) {
if (!isReady() || mUIUpdatePaused || mDragObject != null) {
return;
}
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),
mHotseat.getCellYFromOrder(rank));
if (child != null && !isPredictedIcon(child)) {
continue;
}
if (predictedApps.size() <= predictionIndex) {
// Remove predicted apps from the past
if (isPredictedIcon(child)) {
mHotseat.removeView(child);
}
continue;
}
WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
icon.applyFromWorkspaceItem(predictedItem);
icon.finishBinding(mPredictionLongClickListener);
} else {
newItems.add(predictedItem);
}
preparePredictionInfo(predictedItem, rank);
}
mPredictedSpotsCount = predictionIndex;
bindItems(newItems, animate, callback);
}
private void bindItems(List<WorkspaceItemInfo> 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(mPredictionLongClickListener);
if (animate) {
animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
}
}
if (animate) {
if (callback != null) {
animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
}
animationSet.start();
} else {
if (callback != null) callback.run();
}
}
/**
* Unregisters callbacks and frees resources
*/
public void destroy() {
mAllAppsStore.removeUpdateListener(this);
mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
mHotseat.removeOnAttachStateChangeListener(this);
if (mAppPredictor != null) {
mAppPredictor.destroy();
}
}
/**
* start and pauses predicted apps update on the hotseat
*/
public void setPauseUIUpdate(boolean paused) {
mUIUpdatePaused = paused;
if (!paused) {
fillGapsWithPrediction();
}
}
/**
* Creates App Predictor with all the current apps pinned on the hotseat
*/
public void createPredictor() {
AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
if (mAppPredictor != null) {
mAppPredictor.destroy();
}
mAppPredictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(mLauncher)
.setUiSurface(PREDICTION_CLIENT)
.setPredictedTargetCount(mHotSeatItemsCount)
.setExtras(getAppPredictionContextExtra())
.build());
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
setPauseUIUpdate(false);
performBetaCheck();
if (!isReady()) {
mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
}
mAppPredictor.requestPredictionUpdate();
}
private Bundle getAppPredictionContextExtra() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT,
getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets())));
bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup(
mLauncher.getWorkspace().getScreenWithId(
Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
return bundle;
}
private ArrayList<AppTarget> getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) {
ArrayList<AppTarget> pinnedApps = new ArrayList<>();
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));
}
}
return pinnedApps;
}
private void setPredictedApps(List<AppTarget> appTargets) {
mComponentKeyMappers.clear();
StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
for (AppTarget appTarget : appTargets) {
ComponentKey key;
if (appTarget.getShortcutInfo() != null) {
key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
} else {
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
predictionLog.append(key.toString());
predictionLog.append(",rank:");
predictionLog.append(appTarget.getRank());
predictionLog.append("\n");
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
predictionLog.append("]");
if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
updateDependencies();
if (isReady()) {
fillGapsWithPrediction();
} else if (mHotseatEduController != null) {
mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
}
}
private void updateDependencies() {
mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
mHotSeatItemsCount);
}
/**
* Pins a predicted app icon into place.
*/
public void pinPrediction(ItemInfo info) {
PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
mHotseat.getCellXFromOrder(info.rank),
mHotseat.getCellYFromOrder(info.rank));
if (icon == null) {
return;
}
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
icon.pin(workspaceItemInfo);
AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
}
private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
List<ComponentKeyMapper> components) {
AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
if (allAppsStore.getApps().length == 0) {
return Collections.emptyList();
}
List<WorkspaceItemInfo> predictedApps = new ArrayList<>();
for (ComponentKeyMapper mapper : components) {
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) {
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);
}
}
// Stop at the number of hotseat items
if (predictedApps.size() == mHotSeatItemsCount) {
break;
}
}
return predictedApps;
}
private List<PredictedAppIcon> getPredictedIcons() {
List<PredictedAppIcon> 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((PredictedAppIcon) child);
}
}
return icons;
}
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);
ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
animator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (icon.getParent() != null) {
mHotseat.removeView(icon);
}
}
});
mIconRemoveAnimators.play(animator);
}
mIconRemoveAnimators.start();
}
private void notifyItemAction(AppTarget target, String location, int action) {
if (mAppPredictor != null) {
mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target,
action).setLaunchLocation(location).build());
}
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
mDragObject = dragObject;
if (mOutlineDrawings.isEmpty()) return;
for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
mHotseat.addDelegatedCellDrawing(outlineDrawing);
}
mHotseat.invalidate();
}
/**
* Unpins pinned app when it's converted into a folder
*/
public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
return;
}
AppTarget target = getAppTargetFromItemInfo(info);
ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains(
target)) {
notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
} else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup(
firstScreenVG).contains(target)) {
notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
}
}
/**
* Pins workspace item created when all folder items are removed but one
*/
public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
return;
}
AppTarget target = getAppTargetFromItemInfo(info);
if (isInHotseat(info)) {
notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
} else if (isInFirstPage(info)) {
notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
}
}
@Override
public void onDragEnd() {
if (mDragObject == null) {
return;
}
ItemInfo dragInfo = mDragObject.dragInfo;
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)) {
if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) {
notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
}
}
if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) {
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);
}
if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) {
notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
}
}
mDragObject = null;
fillGapsWithPrediction(true, this::removeOutlineDrawings);
}
@Nullable
@Override
public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
ItemInfo itemInfo) {
if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
return null;
}
return new PinPrediction(activity, itemInfo);
}
private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
itemInfo.rank = rank;
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;
createPredictor();
}
@Override
public void onAppsUpdated() {
fillGapsWithPrediction();
}
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {
}
@Override
public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
//Does nothing
}
@Override
public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
ArrayList<LauncherLogProto.Target> parents) {
mHotseat.fillInLogContainerData(childInfo, child, parents);
}
private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
super(R.drawable.ic_pin, R.string.pin_prediction, target,
itemInfo);
}
@Override
public void onClick(View view) {
dismissTaskMenuView(mTarget);
pinPrediction(mItemInfo);
}
}
private void performBetaCheck() {
if (isReady()) return;
int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount();
// -1 to exclude smart space
int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
// opt user into the feature without onboarding tip or migration if they don't have any
// open spots in their hotseat and have more than maxItems in their hotseat + workspace
if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount
> MAX_ITEMS_FOR_MIGRATION) {
mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
true).apply();
LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH);
LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP);
action.touch = LauncherLogProto.Action.Touch.TAP;
target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
// temporarily encode details in log target (go/hotseat_migration)
target.rank = 2;
target.cardinality = MAX_ITEMS_FOR_MIGRATION;
target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount;
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null);
}
}
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
*/
public static void encodeHotseatLayoutIntoPredictionRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
if (launcher == null || launcher.getHotseatPredictionController() == null
|| itemInfo.getTargetComponent() == null) {
return;
}
HotseatPredictionController controller = launcher.getHotseatPredictionController();
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
OptionalInt rank = IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst();
target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
+ (rank.isPresent() ? rank.getAsInt() + 1 : 0);
}
private static boolean isPredictedIcon(View view) {
return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
private static boolean isPinnedIcon(View view) {
if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) {
return false;
}
ItemInfo info = (ItemInfo) view.getTag();
return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && (
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION);
}
private static boolean isInHotseat(ItemInfo itemInfo) {
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;
ComponentName cn = info.getTargetComponent();
return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
}
}