diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml index 397ea82489..8f4ce4343b 100644 --- a/quickstep/res/values/override.xml +++ b/quickstep/res/values/override.xml @@ -27,8 +27,6 @@ com.android.quickstep.logging.UserEventDispatcherExtension - com.android.launcher3.hybridhotseat.HotseatPredictionModel - com.android.launcher3.model.QuickstepModelDelegate diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index a0016cb905..68111d2632 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -26,12 +26,10 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Intent; import android.content.IntentSender; -import android.content.SharedPreferences; import android.os.Bundle; import android.os.CancellationSignal; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.proxy.ProxyActivityStarter; @@ -40,14 +38,12 @@ import com.android.launcher3.statehandlers.BackButtonAlphaHandler; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; -import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.RecentsModel; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.util.QuickstepOnboardingPrefs; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.views.OverviewActionsView; @@ -73,7 +69,6 @@ public abstract class BaseQuickstepLauncher extends Launcher Float.intBitsToFloat(arg1), arg2 != 0); private OverviewActionsView mActionsView; - protected HotseatPredictionController mHotseatPredictionController; @Override protected void onCreate(Bundle savedInstanceState) { @@ -221,11 +216,6 @@ public abstract class BaseQuickstepLauncher extends Launcher return mDepthController; } - @Override - protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) { - return new QuickstepOnboardingPrefs(this, sharedPrefs); - } - @Override public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { QuickstepAppTransitionManagerImpl appTransitionManager = @@ -314,13 +304,6 @@ public abstract class BaseQuickstepLauncher extends Launcher Stream.of(WellbeingModel.SHORTCUT_FACTORY)); } - /** - * Returns Prediction controller for hybrid hotseat - */ - public HotseatPredictionController getHotseatPredictionController() { - return mHotseatPredictionController; - } - public void setHintUserWillBeActive() { addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); } diff --git a/quickstep/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/src/com/android/launcher3/appprediction/ComponentKeyMapper.java deleted file mode 100644 index d200868899..0000000000 --- a/quickstep/src/com/android/launcher3/appprediction/ComponentKeyMapper.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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.appprediction; - -import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; - -import com.android.launcher3.allapps.AllAppsStore; -import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.util.ComponentKey; - -public class ComponentKeyMapper { - - protected final ComponentKey componentKey; - private final DynamicItemCache mCache; - - public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) { - componentKey = key; - mCache = cache; - } - - public String getPackage() { - return componentKey.componentName.getPackageName(); - } - - public String getComponentClass() { - return componentKey.componentName.getClassName(); - } - - public ComponentKey getComponentKey() { - return componentKey; - } - - @Override - public String toString() { - return componentKey.toString(); - } - - public ItemInfoWithIcon getApp(AllAppsStore store) { - AppInfo item = store.getApp(componentKey); - if (item != null) { - return item; - } else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) { - return mCache.getInstantApp(componentKey.componentName.getPackageName()); - } else { - return mCache.getShortcutInfo(componentKey); - } - } -} diff --git a/quickstep/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/src/com/android/launcher3/appprediction/DynamicItemCache.java deleted file mode 100644 index ab96b1340a..0000000000 --- a/quickstep/src/com/android/launcher3/appprediction/DynamicItemCache.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * 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.appprediction; - -import static android.content.pm.PackageManager.MATCH_INSTANT; - -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.ArrayMap; -import android.util.Log; - -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.allapps.AllAppsStore; -import com.android.launcher3.icons.IconCache; -import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.shortcuts.ShortcutRequest; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.InstantAppResolver; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Utility class which loads and caches predicted items like instant apps and shortcuts, before - * they can be displayed on the UI - */ -public class DynamicItemCache { - - private static final String TAG = "DynamicItemCache"; - private static final boolean DEBUG = false; - private static final String DEFAULT_URL = "default-url"; - - private static final int BG_MSG_LOAD_SHORTCUTS = 1; - private static final int BG_MSG_LOAD_INSTANT_APPS = 2; - - private static final int UI_MSG_UPDATE_SHORTCUTS = 1; - private static final int UI_MSG_UPDATE_INSTANT_APPS = 2; - - private final Context mContext; - private final Handler mWorker; - private final Handler mUiHandler; - private final InstantAppResolver mInstantAppResolver; - private final Runnable mOnUpdateCallback; - private final IconCache mIconCache; - - private final Map mShortcuts; - private final Map mInstantApps; - - public DynamicItemCache(Context context, Runnable onUpdateCallback) { - mContext = context; - mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage); - mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); - mInstantAppResolver = InstantAppResolver.newInstance(context); - mOnUpdateCallback = onUpdateCallback; - mIconCache = LauncherAppState.getInstance(mContext).getIconCache(); - - mShortcuts = new HashMap<>(); - mInstantApps = new HashMap<>(); - } - - public void cacheItems(List shortcutKeys, List pkgNames) { - if (!shortcutKeys.isEmpty()) { - mWorker.removeMessages(BG_MSG_LOAD_SHORTCUTS); - Message.obtain(mWorker, BG_MSG_LOAD_SHORTCUTS, shortcutKeys).sendToTarget(); - } - if (!pkgNames.isEmpty()) { - mWorker.removeMessages(BG_MSG_LOAD_INSTANT_APPS); - Message.obtain(mWorker, BG_MSG_LOAD_INSTANT_APPS, pkgNames).sendToTarget(); - } - } - - private boolean handleWorkerMessage(Message msg) { - switch (msg.what) { - case BG_MSG_LOAD_SHORTCUTS: { - List shortcutKeys = msg.obj != null ? - (List) msg.obj : Collections.EMPTY_LIST; - Map shortcutKeyAndInfos = new ArrayMap<>(); - for (ShortcutKey shortcutKey : shortcutKeys) { - WorkspaceItemInfo workspaceItemInfo = loadShortcutWorker(shortcutKey); - if (workspaceItemInfo != null) { - shortcutKeyAndInfos.put(shortcutKey, workspaceItemInfo); - } - } - Message.obtain(mUiHandler, UI_MSG_UPDATE_SHORTCUTS, shortcutKeyAndInfos) - .sendToTarget(); - return true; - } - case BG_MSG_LOAD_INSTANT_APPS: { - List pkgNames = msg.obj != null ? - (List) msg.obj : Collections.EMPTY_LIST; - List instantAppItemInfos = new ArrayList<>(); - for (String pkgName : pkgNames) { - InstantAppItemInfo instantAppItemInfo = loadInstantApp(pkgName); - if (instantAppItemInfo != null) { - instantAppItemInfos.add(instantAppItemInfo); - } - } - Message.obtain(mUiHandler, UI_MSG_UPDATE_INSTANT_APPS, instantAppItemInfos) - .sendToTarget(); - return true; - } - } - - return false; - } - - private boolean handleUiMessage(Message msg) { - switch (msg.what) { - case UI_MSG_UPDATE_SHORTCUTS: { - mShortcuts.clear(); - mShortcuts.putAll((Map) msg.obj); - mOnUpdateCallback.run(); - return true; - } - case UI_MSG_UPDATE_INSTANT_APPS: { - List instantAppItemInfos = (List) msg.obj; - mInstantApps.clear(); - for (InstantAppItemInfo instantAppItemInfo : instantAppItemInfos) { - mInstantApps.put(instantAppItemInfo.getTargetComponent().getPackageName(), - instantAppItemInfo); - } - mOnUpdateCallback.run(); - if (DEBUG) { - Log.d(TAG, String.format("Cache size: %d, Cache: %s", - mInstantApps.size(), mInstantApps.toString())); - } - return true; - } - } - - return false; - } - - @WorkerThread - private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) { - List details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL); - if (!details.isEmpty()) { - WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext); - mIconCache.getShortcutIcon(si, details.get(0)); - return si; - } - if (DEBUG) { - Log.d(TAG, "No shortcut found: " + shortcutKey.toString()); - } - return null; - } - - private InstantAppItemInfo loadInstantApp(String pkgName) { - PackageManager pm = mContext.getPackageManager(); - - try { - ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0); - if (!mInstantAppResolver.isInstantApp(ai)) { - return null; - } - } catch (PackageManager.NameNotFoundException e) { - return null; - } - - String url = retrieveDefaultUrl(pkgName, pm); - if (url == null) { - Log.w(TAG, "no default-url available for pkg " + pkgName); - return null; - } - - Intent intent = new Intent(Intent.ACTION_VIEW) - .addCategory(Intent.CATEGORY_BROWSABLE) - .setData(Uri.parse(url)); - InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName); - IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache(); - iconCache.getTitleAndIcon(info, false); - if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) { - return null; - } - return info; - } - - @Nullable - public static String retrieveDefaultUrl(String pkgName, PackageManager pm) { - Intent mainIntent = new Intent().setAction(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_LAUNCHER).setPackage(pkgName); - List resolveInfos = pm.queryIntentActivities( - mainIntent, MATCH_INSTANT | PackageManager.GET_META_DATA); - String url = null; - for (ResolveInfo resolveInfo : resolveInfos) { - if (resolveInfo.activityInfo.metaData != null - && resolveInfo.activityInfo.metaData.containsKey(DEFAULT_URL)) { - url = resolveInfo.activityInfo.metaData.getString(DEFAULT_URL); - } - } - return url; - } - - @UiThread - public InstantAppItemInfo getInstantApp(String pkgName) { - return mInstantApps.get(pkgName); - } - - @MainThread - public WorkspaceItemInfo getShortcutInfo(ComponentKey key) { - return mShortcuts.get(key); - } - - /** - * requests and caches icons for app targets - */ - public void updateDependencies(List componentKeyMappers, - AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) { - List instantAppsToLoad = new ArrayList<>(); - List shortcutsToLoad = new ArrayList<>(); - int total = componentKeyMappers.size(); - for (int i = 0, count = 0; i < total && count < itemCount; i++) { - ComponentKeyMapper mapper = componentKeyMappers.get(i); - // Update instant apps - if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) { - instantAppsToLoad.add(mapper.getPackage()); - count++; - } else if (mapper.getComponentKey() instanceof ShortcutKey) { - shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey()); - count++; - } else { - // Reload high res icon - AppInfo info = (AppInfo) mapper.getApp(appsStore); - if (info != null) { - if (info.usingLowResIcon()) { - mIconCache.updateIconInBackground(callback, info); - } - count++; - } - } - } - cacheItems(shortcutsToLoad, instantAppsToLoad); - } -} diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index 8ebf1251ec..4451e7a6c1 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -47,35 +47,27 @@ import java.util.stream.IntStream; */ public class HotseatEduController { - public static final String HOTSEAT_EDU_ACTION = - "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"; public static final String SETTINGS_ACTION = "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; private final Launcher mLauncher; private final Hotseat mHotseat; - private HotseatRestoreHelper mRestoreHelper; private List mPredictedApps; private HotseatEduDialog mActiveDialog; private ArrayList mNewItems = new ArrayList<>(); private IntArray mNewScreens = null; - private Runnable mOnOnboardingComplete; - HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) { + HotseatEduController(Launcher launcher) { mLauncher = launcher; mHotseat = launcher.getHotseat(); - mRestoreHelper = restoreHelper; - mOnOnboardingComplete = runnable; } /** * Checks what type of migration should be used and migrates hotseat */ void migrate() { - if (mRestoreHelper != null) { - mRestoreHelper.createBackup(); - } + HotseatRestoreHelper.createBackup(mLauncher); if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { migrateToFolder(); } else { @@ -227,7 +219,7 @@ public class HotseatEduController { } void finishOnboarding() { - mOnOnboardingComplete.run(); + mLauncher.getModel().onWorkspaceUiChanged(); } void showDimissTip() { diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index b94e6337d0..6fe16c9faa 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -15,23 +15,17 @@ */ package com.android.launcher3.hybridhotseat; -import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED; 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.content.ComponentName; import android.os.Process; -import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.View; import android.view.ViewGroup; @@ -44,77 +38,51 @@ import com.android.launcher3.DropTarget; import com.android.launcher3.Hotseat; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.Utilities; -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.graphics.DragPreviewProvider; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer; import com.android.launcher3.logging.InstanceId; -import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; 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; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.IntArray; import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.views.ArrowTipView; import com.android.launcher3.views.Snackbar; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.OptionalInt; -import java.util.stream.IntStream; +import java.util.stream.Collectors; /** * 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, - InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener, - IconCache.ItemInfoUpdateReceiver, DragSource { + SystemShortcut.Factory, InvariantDeviceProfile.OnIDPChangeListener, + DragSource { - private static final String TAG = "PredictiveHotseat"; - private static final boolean DEBUG = false; - - 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 final HotseatRestoreHelper mRestoreHelper; + private List mPredictedItems = Collections.emptyList(); - private List mComponentKeyMappers = new ArrayList<>(); - - private DynamicItemCache mDynamicItemCache; - - private final HotseatPredictionModel mPredictionModel; - private AppPredictor mAppPredictor; - private AllAppsStore mAllAppsStore; private AnimatorSet mIconRemoveAnimators; private boolean mUIUpdatePaused = false; - private boolean mIsDestroyed = false; - + private boolean mDragInProgress = false; private List mOutlineDrawings = new ArrayList<>(); @@ -130,26 +98,23 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); return true; } + // Start the drag - mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions()); + // Use a new itemInfo so that the original predicted item is stable + WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag()); + v.setVisibility(View.INVISIBLE); + mLauncher.getWorkspace().beginDragShared( + v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions()); return true; }; public HotseatPredictionController(Launcher launcher) { mLauncher = launcher; mHotseat = launcher.getHotseat(); - mAllAppsStore = mLauncher.getAppsView().getAppsStore(); - LauncherAppState appState = LauncherAppState.getInstance(launcher); - mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel(); - mAllAppsStore.addUpdateListener(this); - mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; + mLauncher.getDragController().addDragListener(this); + launcher.getDeviceProfile().inv.addOnChangeListener(this); - mHotseat.addOnAttachStateChangeListener(this); - mRestoreHelper = new HotseatRestoreHelper(mLauncher); - if (mHotseat.isAttachedToWindow()) { - onViewAttachedToWindow(mHotseat); - } } /** @@ -157,7 +122,7 @@ public class HotseatPredictionController implements DragController.DragListener, */ public void showEdu() { mLauncher.getStateManager().goToState(NORMAL, true, () -> { - if (mComponentKeyMappers.isEmpty()) { + if (mPredictedItems.isEmpty()) { // launcher has empty predictions set Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled, R.string.hotseat_prediction_settings, null, @@ -165,10 +130,10 @@ public class HotseatPredictionController implements DragController.DragListener, } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) { showDiscoveryTip(); } else { - HotseatEduController eduController = new HotseatEduController(mLauncher, - mRestoreHelper, - this::createPredictor); - eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers)); + HotseatEduController eduController = new HotseatEduController(mLauncher); + eduController.setPredictedApps(mPredictedItems.stream() + .map(i -> (WorkspaceItemInfo) i) + .collect(Collectors.toList())); eduController.showEdu(); } }); @@ -192,17 +157,7 @@ public class HotseatPredictionController implements DragController.DragListener, * Returns if hotseat client has predictions */ public boolean hasPredictions() { - return !mComponentKeyMappers.isEmpty(); - } - - @Override - public void onViewAttachedToWindow(View view) { - mLauncher.getDragController().addDragListener(this); - } - - @Override - public void onViewDetachedFromWindow(View view) { - mLauncher.getDragController().removeDragListener(this); + return !mPredictedItems.isEmpty(); } private void fillGapsWithPrediction() { @@ -210,15 +165,10 @@ public class HotseatPredictionController implements DragController.DragListener, } private void fillGapsWithPrediction(boolean animate, Runnable callback) { - if (mUIUpdatePaused || mDragObject != null) { - return; - } - List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers); - if (mComponentKeyMappers.isEmpty() != predictedApps.isEmpty()) { - // Safely ignore update as AppsList is not ready yet. This will called again once - // apps are ready (HotseatPredictionController#onAppsUpdated) + if (mUIUpdatePaused || mDragInProgress) { return; } + int predictionIndex = 0; ArrayList newItems = new ArrayList<>(); // make sure predicted icon removal and filling predictions don't step on each other @@ -240,14 +190,15 @@ public class HotseatPredictionController implements DragController.DragListener, if (child != null && !isPredictedIcon(child)) { continue; } - if (predictedApps.size() <= predictionIndex) { + if (mPredictedItems.size() <= predictionIndex) { // Remove predicted apps from the past if (isPredictedIcon(child)) { mHotseat.removeView(child); } continue; } - WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); + WorkspaceItemInfo predictedItem = + (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++); if (isPredictedIcon(child) && child.isEnabled()) { PredictedAppIcon icon = (PredictedAppIcon) child; icon.applyFromWorkspaceItem(predictedItem); @@ -257,7 +208,6 @@ public class HotseatPredictionController implements DragController.DragListener, } preparePredictionInfo(predictedItem, rank); } - mPredictedSpotsCount = predictionIndex; bindItems(newItems, animate, callback); } @@ -285,13 +235,7 @@ public class HotseatPredictionController implements DragController.DragListener, * Unregisters callbacks and frees resources */ public void destroy() { - mIsDestroyed = true; - mAllAppsStore.removeUpdateListener(this); mLauncher.getDeviceProfile().inv.removeOnChangeListener(this); - mHotseat.removeOnAttachStateChangeListener(this); - if (mAppPredictor != null) { - mAppPredictor.destroy(); - } } /** @@ -305,97 +249,14 @@ public class HotseatPredictionController implements DragController.DragListener, } /** - * Creates App Predictor with all the current apps pinned on the hotseat + * Sets or updates the predicted items */ - public void createPredictor() { - AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class); - if (apm == null) { - return; + public void setPredictedItems(FixedContainerItems items) { + mPredictedItems = items.items; + if (mPredictedItems.isEmpty()) { + HotseatRestoreHelper.restoreBackup(mLauncher); } - if (mAppPredictor != null) { - mAppPredictor.destroy(); - mAppPredictor = null; - } - WeakReference controllerRef = new WeakReference<>(this); - - - mPredictionModel.createBundle(bundle -> { - if (mIsDestroyed) return; - mAppPredictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mLauncher) - .setUiSurface(PREDICTION_CLIENT) - .setPredictedTargetCount(mHotSeatItemsCount) - .setExtras(bundle) - .build()); - mAppPredictor.registerPredictionUpdates( - mLauncher.getApplicationContext().getMainExecutor(), - list -> { - if (controllerRef.get() != null) { - controllerRef.get().setPredictedApps(list); - } - }); - mAppPredictor.requestPredictionUpdate(); - }); - setPauseUIUpdate(false); - } - - /** - * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items. - */ - public void showCachedItems(List apps, IntArray ranks) { - if (hasPredictions() && mAppPredictor != null) { - mAppPredictor.requestPredictionUpdate(); - fillGapsWithPrediction(); - return; - } - int count = Math.min(ranks.size(), apps.size()); - List items = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i)); - ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user); - preparePredictionInfo(item, ranks.get(i)); - items.add(item); - - mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache)); - } - updateDependencies(); - bindItems(items, false, null); - } - - private void setPredictedApps(List appTargets) { - mComponentKeyMappers.clear(); - if (appTargets.isEmpty()) { - mRestoreHelper.restoreBackup(); - } - StringBuilder predictionLog = new StringBuilder("predictedApps: [\n"); - ArrayList componentKeys = new ArrayList<>(); - 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()); - } - componentKeys.add(key); - 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) { - HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString()); - } - updateDependencies(); fillGapsWithPrediction(); - mPredictionModel.cachePredictionComponentKeys(componentKeys); - } - - private void updateDependencies() { - mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this, - mHotSeatItemsCount); } /** @@ -414,42 +275,9 @@ public class HotseatPredictionController implements DragController.DragListener, workspaceItemInfo.cellX, workspaceItemInfo.cellY); ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); icon.pin(workspaceItemInfo); - AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo); - if (appTarget != null) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, - AppTargetEvent.ACTION_PIN, workspaceItemInfo)); - } - } - - private List mapToWorkspaceItemInfo( - List components) { - AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore(); - if (allAppsStore.getApps().length == 0) { - return Collections.emptyList(); - } - - List 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; + mLauncher.getStatsLogManager().logger() + .withItemInfo(workspaceItemInfo) + .log(LAUNCHER_HOTSEAT_PREDICTION_PINNED); } private List getPredictedIcons() { @@ -465,7 +293,7 @@ public class HotseatPredictionController implements DragController.DragListener, } private void removePredictedApps(List outlines, - ItemInfo draggedInfo) { + DropTarget.DragObject dragObject) { if (mIconRemoveAnimators != null) { mIconRemoveAnimators.end(); } @@ -475,7 +303,7 @@ public class HotseatPredictionController implements DragController.DragListener, if (!icon.isEnabled()) { continue; } - if (icon.getTag().equals(draggedInfo)) { + if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) { mHotseat.removeView(icon); continue; } @@ -497,84 +325,23 @@ public class HotseatPredictionController implements DragController.DragListener, mIconRemoveAnimators.start(); } - private void notifyItemAction(AppTargetEvent event) { - if (mAppPredictor != null) { - mAppPredictor.notifyAppTargetEvent(event); - } - } - @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - removePredictedApps(mOutlineDrawings, dragObject.dragInfo); - mDragObject = dragObject; + removePredictedApps(mOutlineDrawings, dragObject); if (mOutlineDrawings.isEmpty()) return; for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { mHotseat.addDelegatedCellDrawing(outlineDrawing); } + mDragInProgress = true; mHotseat.invalidate(); } - /** - * Unpins pinned app when it's converted into a folder - */ - public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { - AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); - AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); - if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, - AppTargetEvent.ACTION_PIN, folderInfo)); - } - // using folder info with isTrackedForPrediction as itemInfo.container is already changed - // to folder by this point - if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, - AppTargetEvent.ACTION_UNPIN, folderInfo - )); - } - } - - /** - * Pins workspace item created when all folder items are removed but one - */ - public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { - AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); - AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); - if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, - AppTargetEvent.ACTION_UNPIN, folderInfo)); - } - if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, - AppTargetEvent.ACTION_PIN, itemInfo)); - } - } - @Override public void onDragEnd() { - if (mDragObject == null) { - return; - } - - ItemInfo dragInfo = mDragObject.dragInfo; - if (mDragObject.isMoved()) { - AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo); - //always send pin event first to prevent AiAi from predicting an item moved within - // the same page - if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, - AppTargetEvent.ACTION_PIN, dragInfo)); - } - if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction( - mDragObject.originalDragInfo)) { - notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, - AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo)); - } - } - mDragObject = null; + mDragInProgress = false; fillGapsWithPrediction(true, this::removeOutlineDrawings); } - @Nullable @Override public SystemShortcut getShortcut(QuickstepLauncher activity, @@ -604,19 +371,7 @@ public class HotseatPredictionController implements DragController.DragListener, @Override public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - if ((changeFlags & CHANGE_FLAG_GRID) != 0) { - this.mHotSeatItemsCount = profile.numHotseatIcons; - createPredictor(); - } - } - - @Override - public void onAppsUpdated() { - fillGapsWithPrediction(); - } - - @Override - public void reapplyItemInfo(ItemInfoWithIcon info) { + this.mHotSeatItemsCount = profile.numHotseatIcons; } @Override @@ -643,17 +398,20 @@ public class HotseatPredictionController implements DragController.DragListener, + ",launchLocation:" + itemInfo.container); } - if (itemInfo.getTargetComponent() == null || itemInfo.user == null) { + + ComponentName targetCN = itemInfo.getTargetComponent(); + if (targetCN == null) { return; } - - final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); - - final List predictedApps = new ArrayList<>(mComponentKeyMappers); - OptionalInt rank = IntStream.range(0, predictedApps.size()) - .filter(index -> key.equals(predictedApps.get(index).getComponentKey())) - .findFirst(); - if (!rank.isPresent()) { + int rank = -1; + for (int i = mPredictedItems.size() - 1; i >= 0; i--) { + ItemInfo info = mPredictedItems.get(i); + if (targetCN.equals(info.getTargetComponent()) && itemInfo.user.equals(info.user)) { + rank = i; + break; + } + } + if (rank < 0) { return; } @@ -666,11 +424,11 @@ public class HotseatPredictionController implements DragController.DragListener, PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder(); containerBuilder.setCardinality(cardinality); if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { - containerBuilder.setIndex(rank.getAsInt()); + containerBuilder.setIndex(rank); } mLauncher.getStatsLogManager().logger() .withInstanceId(instanceId) - .withRank(rank.getAsInt()) + .withRank(rank) .withContainerInfo(ContainerInfo.newBuilder() .setPredictedHotseatContainer(containerBuilder) .build()) @@ -691,30 +449,6 @@ public class HotseatPredictionController implements DragController.DragListener, } } - /** - * 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 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 diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java index 5a038d27af..8f31c22d15 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java @@ -15,7 +15,7 @@ */ package com.android.launcher3.hybridhotseat; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; @@ -24,13 +24,10 @@ import android.content.ComponentName; import android.content.Context; import android.os.Bundle; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.Workspace; -import com.android.launcher3.model.AllAppsList; -import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.model.PredictionModel; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -38,55 +35,48 @@ import com.android.launcher3.shortcuts.ShortcutKey; import java.util.ArrayList; import java.util.Locale; -import java.util.function.Consumer; /** * Model helper for app predictions in workspace */ -public class HotseatPredictionModel extends PredictionModel { +public class HotseatPredictionModel { private static final String APP_LOCATION_HOTSEAT = "hotseat"; private static final String APP_LOCATION_WORKSPACE = "workspace"; private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items"; - - public HotseatPredictionModel(Context context) { } - /** - * Creates and returns bundle using workspace items and cached items + * Creates and returns bundle using workspace items */ - public void createBundle(Consumer cb) { - LauncherAppState appState = LauncherAppState.getInstance(mContext); - appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { - @Override - public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - Bundle bundle = new Bundle(); - ArrayList events = new ArrayList<>(); - ArrayList workspaceItems = new ArrayList<>(dataModel.workspaceItems); - workspaceItems.addAll(dataModel.appWidgets); - for (ItemInfo item : workspaceItems) { - AppTarget target = getAppTargetFromInfo(item); - if (target != null && !isTrackedForPrediction(item)) continue; - events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item)); - } - ArrayList currentTargets = new ArrayList<>(); - for (ItemInfo itemInfo : dataModel.cachedPredictedItems) { - AppTarget target = getAppTargetFromInfo(itemInfo); - if (target != null) currentTargets.add(target); - } - bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events); - bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets); - MAIN_EXECUTOR.execute(() -> cb.accept(bundle)); + public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) { + Bundle bundle = new Bundle(); + ArrayList events = new ArrayList<>(); + ArrayList workspaceItems = new ArrayList<>(dataModel.workspaceItems); + workspaceItems.addAll(dataModel.appWidgets); + for (ItemInfo item : workspaceItems) { + AppTarget target = getAppTargetFromInfo(context, item); + if (target != null && !isTrackedForPrediction(item)) continue; + events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item)); + } + ArrayList currentTargets = new ArrayList<>(); + FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); + if (hotseatItems != null) { + for (ItemInfo itemInfo : hotseatItems.items) { + AppTarget target = getAppTargetFromInfo(context, itemInfo); + if (target != null) currentTargets.add(target); } - }); + } + bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events); + bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets); + return bundle; } /** * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null * if item is not supported prediction */ - public AppTarget getAppTargetFromInfo(ItemInfo info) { + public static AppTarget getAppTargetFromInfo(Context context, ItemInfo info) { if (info == null) return null; if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && info instanceof LauncherAppWidgetInfo @@ -107,17 +97,17 @@ public class HotseatPredictionModel extends PredictionModel { shortcutKey.componentName.getPackageName(), shortcutKey.user).build(); } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { return new AppTarget.Builder(new AppTargetId("folder:" + info.id), - mContext.getPackageName(), info.user).build(); + context.getPackageName(), info.user).build(); } return null; } - /** * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item * location using {@link ItemInfo} */ - public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) { + public static AppTargetEvent wrapAppTargetWithLocation( + AppTarget target, int action, ItemInfo info) { String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java index 9e7c9fba6d..90f762e24c 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java @@ -19,8 +19,10 @@ import static com.android.launcher3.LauncherSettings.Favorites.HYBRID_HOTSEAT_BA import static com.android.launcher3.provider.LauncherDbUtils.tableExists; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.Context; + import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.GridBackupTable; import com.android.launcher3.provider.LauncherDbUtils; @@ -29,29 +31,24 @@ import com.android.launcher3.provider.LauncherDbUtils; * A helper class to manage migration revert restoration for hybrid hotseat */ public class HotseatRestoreHelper { - private final Launcher mLauncher; - - HotseatRestoreHelper(Launcher context) { - mLauncher = context; - } /** * Creates a snapshot backup of Favorite table for future restoration use. */ - public void createBackup() { + public static void createBackup(Context context) { MODEL_EXECUTOR.execute(() -> { try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction) LauncherSettings.Settings.call( - mLauncher.getContentResolver(), + context.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_TRANSACTION) .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) { - InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv; - GridBackupTable backupTable = new GridBackupTable(mLauncher, + InvariantDeviceProfile idp = LauncherAppState.getIDP(context); + GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(), idp.numHotseatIcons, idp.numColumns, idp.numRows); backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE); transaction.commit(); - LauncherSettings.Settings.call(mLauncher.getContentResolver(), + LauncherSettings.Settings.call(context.getContentResolver(), LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE); } }); @@ -60,23 +57,23 @@ public class HotseatRestoreHelper { /** * Finds and restores a previously saved snapshow of Favorites table */ - public void restoreBackup() { + public static void restoreBackup(Context context) { MODEL_EXECUTOR.execute(() -> { try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction) LauncherSettings.Settings.call( - mLauncher.getContentResolver(), + context.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_TRANSACTION) .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) { if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) { return; } - InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv; - GridBackupTable backupTable = new GridBackupTable(mLauncher, + InvariantDeviceProfile idp = LauncherAppState.getIDP(context); + GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(), idp.numHotseatIcons, idp.numColumns, idp.numRows); backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true); transaction.commit(); - mLauncher.getModel().forceReload(); + LauncherAppState.getInstance(context).getModel().forceReload(); } }); } diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java index 8e4c43f464..364a321f75 100644 --- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java +++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java @@ -15,8 +15,21 @@ */ package com.android.launcher3.model; +import static android.app.prediction.AppTargetEvent.ACTION_DISMISS; +import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH; +import static android.app.prediction.AppTargetEvent.ACTION_PIN; +import static android.app.prediction.AppTargetEvent.ACTION_UNPIN; + +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; @@ -47,11 +60,12 @@ import com.android.launcher3.logger.LauncherAtom.FolderContainer; import com.android.launcher3.logger.LauncherAtom.HotseatContainer; import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer; import com.android.launcher3.logging.StatsLogManager.EventEnum; +import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.pm.UserCache; import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer; import java.util.Locale; -import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; import java.util.function.Predicate; /** @@ -64,9 +78,11 @@ public class AppEventProducer implements StatsLogConsumer { private final Context mContext; private final Handler mMessageHandler; - private final Consumer mCallback; + private final ObjIntConsumer mCallback; - public AppEventProducer(Context context, Consumer callback) { + private LauncherAtom.ItemInfo mLastDragItem; + + public AppEventProducer(Context context, ObjIntConsumer callback) { mContext = context; mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage); mCallback = callback; @@ -76,7 +92,7 @@ public class AppEventProducer implements StatsLogConsumer { private boolean handleMessage(Message msg) { switch (msg.what) { case MSG_LAUNCH: { - mCallback.accept((AppTargetEvent) msg.obj); + mCallback.accept((AppTargetEvent) msg.obj, msg.arg1); return true; } } @@ -84,13 +100,18 @@ public class AppEventProducer implements StatsLogConsumer { } @AnyThread - private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) { - AppTarget target = toAppTarget(atomInfo); + private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId, int targetPredictor) { + sendEvent(toAppTarget(atomInfo), atomInfo, eventId, targetPredictor); + } + + @AnyThread + private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId, + int targetPredictor) { if (target != null) { AppTargetEvent event = new AppTargetEvent.Builder(target, eventId) - .setLaunchLocation(getContainer(atomInfo)) + .setLaunchLocation(getContainer(locationInfo)) .build(); - Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget(); + mMessageHandler.obtainMessage(MSG_LAUNCH, targetPredictor, 0, event).sendToTarget(); } } @@ -101,9 +122,42 @@ public class AppEventProducer implements StatsLogConsumer { || event == LAUNCHER_TASK_LAUNCH_TAP || event == LAUNCHER_QUICKSWITCH_RIGHT || event == LAUNCHER_QUICKSWITCH_LEFT) { - sendEvent(atomInfo, AppTargetEvent.ACTION_LAUNCH); + sendEvent(atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION); } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) { - sendEvent(atomInfo, AppTargetEvent.ACTION_DISMISS); + sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION); + } else if (event == LAUNCHER_ITEM_DRAG_STARTED) { + mLastDragItem = atomInfo; + } else if (event == LAUNCHER_ITEM_DROP_COMPLETED) { + if (mLastDragItem == null) { + return; + } + if (isTrackedForHotseatPrediction(atomInfo)) { + sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION); + } + if (isTrackedForHotseatPrediction(mLastDragItem)) { + sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION); + } + mLastDragItem = null; + } else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) { + if (isTrackedForHotseatPrediction(atomInfo)) { + sendEvent(createTempFolderTarget(), atomInfo, ACTION_PIN, + CONTAINER_HOTSEAT_PREDICTION); + sendEvent(atomInfo, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION); + } + } else if (event == LAUNCHER_FOLDER_CONVERTED_TO_ICON) { + if (isTrackedForHotseatPrediction(atomInfo)) { + sendEvent(createTempFolderTarget(), atomInfo, ACTION_UNPIN, + CONTAINER_HOTSEAT_PREDICTION); + sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION); + } + } else if (event == LAUNCHER_ITEM_DROPPED_ON_REMOVE) { + if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) { + sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION); + } + } else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) { + if (isTrackedForHotseatPrediction(atomInfo)) { + sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION); + } } } @@ -152,10 +206,8 @@ public class AppEventProducer implements StatsLogConsumer { } break; } - case FOLDER_ICON: { - id = "folder:" + SystemClock.uptimeMillis(); - cn = new ComponentName(mContext.getPackageName(), "#folder"); - } + case FOLDER_ICON: + return createTempFolderTarget(); } if (id != null && cn != null) { return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle) @@ -165,6 +217,12 @@ public class AppEventProducer implements StatsLogConsumer { return null; } + private AppTarget createTempFolderTarget() { + return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()), + mContext.getPackageName(), Process.myUserHandle()) + .build(); + } + private String getContainer(LauncherAtom.ItemInfo info) { ContainerInfo ci = info.getContainerInfo(); switch (ci.getContainerCase()) { @@ -212,11 +270,26 @@ public class AppEventProducer implements StatsLogConsumer { } private static String getHotseatContainerString(HotseatContainer hc) { - return String.format(Locale.ENGLISH, "hotseat/%d", hc.getIndex()); + return String.format(Locale.ENGLISH, "hotseat/%1$d/[%1$d,0]/[1,1]", hc.getIndex()); } private static ComponentName parseNullable(String componentNameString) { return TextUtils.isEmpty(componentNameString) ? null : ComponentName.unflattenFromString(componentNameString); } + + /** + * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors + */ + private static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) { + ContainerInfo ci = info.getContainerInfo(); + switch (ci.getContainerCase()) { + case HOTSEAT: + return true; + case WORKSPACE: + return ci.getWorkspace().getPageIndex() == 0; + default: + return false; + } + } } diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 166cb6cc4d..be57dece79 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -16,9 +16,11 @@ package com.android.launcher3.model; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; @@ -62,6 +64,8 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange private final PredictorState mAllAppsState = new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions"); + private final PredictorState mHotseatState = + new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions"); private final InvariantDeviceProfile mIDP; private final AppEventProducer mAppEventProducer; @@ -81,13 +85,23 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange // TODO: Implement caching and preloading super.loadItems(ums, pinnedShortcuts); - WorkspaceItemFactory factory = + WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numAllAppsColumns); mAllAppsState.items.setItems( - mAllAppsState.storage.read(mApp.getContext(), factory, ums.allUsers::get)); + mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get)); mDataModel.extraItems.put(CONTAINER_PREDICTION, mAllAppsState.items); + WorkspaceItemFactory hotseatFactory = + new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numHotseatIcons); + mHotseatState.items.setItems( + mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get)); + mDataModel.extraItems.put(CONTAINER_HOTSEAT_PREDICTION, mHotseatState.items); mActive = true; + } + + @Override + public void workspaceLoadComplete() { + super.workspaceLoadComplete(); recreatePredictors(); } @@ -111,6 +125,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange private void destroyPredictors() { mAllAppsState.destroyPredictor(); + mHotseatState.destroyPredictor(); } @WorkerThread @@ -125,18 +140,28 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange return; } - int count = mIDP.numAllAppsColumns; - - mAllAppsState.predictor = apm.createAppPredictionSession( + registerPredictor(mAllAppsState, apm.createAppPredictionSession( new AppPredictionContext.Builder(context) .setUiSurface("home") - .setPredictedTargetCount(count) - .build()); - mAllAppsState.predictor.registerPredictionUpdates( - Executors.MODEL_EXECUTOR, t -> handleUpdate(mAllAppsState, t)); - mAllAppsState.predictor.requestPredictionUpdate(); + .setPredictedTargetCount(mIDP.numAllAppsColumns) + .build())); + + // TODO: get bundle + registerPredictor(mHotseatState, apm.createAppPredictionSession( + new AppPredictionContext.Builder(context) + .setUiSurface("hotseat") + .setPredictedTargetCount(mIDP.numHotseatIcons) + .setExtras(convertDataModelToAppTargetBundle(context, mDataModel)) + .build())); + } + private void registerPredictor(PredictorState state, AppPredictor predictor) { + state.predictor = predictor; + state.predictor.registerPredictionUpdates( + Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t)); + state.predictor.requestPredictionUpdate(); + } private void handleUpdate(PredictorState state, List targets) { if (state.setTargets(targets)) { @@ -154,9 +179,10 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange } } - private void onAppTargetEvent(AppTargetEvent event) { - if (mAllAppsState.predictor != null) { - mAllAppsState.predictor.notifyAppTargetEvent(event); + private void onAppTargetEvent(AppTargetEvent event, int client) { + PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState; + if (state.predictor != null) { + state.predictor.notifyAppTargetEvent(event); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index f7bc0b32d4..e808f8cb78 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -33,8 +33,8 @@ import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Configuration; -import android.os.Bundle; import android.util.Log; import android.view.View; @@ -48,14 +48,11 @@ import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.appprediction.PredictionRowView; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.folder.Folder; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.BgDataModel.FixedContainerItems; -import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; import com.android.launcher3.testing.TestProtocol; @@ -70,13 +67,14 @@ import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchContro import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController; -import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.TouchController; import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.util.UiThreadHelper.AsyncCommand; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.QuickstepOnboardingPrefs; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -84,7 +82,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -98,14 +95,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); private FixedContainerItems mAllAppsPredictions; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (mHotseatPredictionController != null) { - mHotseatPredictionController.createPredictor(); - } - } + private HotseatPredictionController mHotseatPredictionController; @Override protected void setupViews() { @@ -143,6 +133,18 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + /** + * Returns Prediction controller for hybrid hotseat + */ + public HotseatPredictionController getHotseatPredictionController() { + return mHotseatPredictionController; + } + + @Override + protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) { + return new QuickstepOnboardingPrefs(this, sharedPrefs); + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -178,22 +180,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { super.showAllAppsFromIntent(alreadyOnHome); } - @Override - public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) { - super.folderCreatedFromItem(folder, itemInfo); - if (mHotseatPredictionController != null) { - mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo()); - } - } - - @Override - public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) { - super.folderConvertedToItem(folder, itemInfo); - if (mHotseatPredictionController != null) { - mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo()); - } - } - @Override public Stream getSupportedShortcuts() { if (mHotseatPredictionController != null) { @@ -221,20 +207,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } - @Override - public void bindPredictedItems(List appInfos, IntArray ranks) { - super.bindPredictedItems(appInfos, ranks); - if (mHotseatPredictionController != null) { - mHotseatPredictionController.showCachedItems(appInfos, ranks); - } - } - @Override public void bindExtraContainerItems(FixedContainerItems item) { if (item.containerId == Favorites.CONTAINER_PREDICTION) { mAllAppsPredictions = item; getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) .setPredictedApps(item.items); + } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION + && mHotseatPredictionController != null) { + mHotseatPredictionController.setPredictedItems(item); } } diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java index 7eda627934..b10adb43b5 100644 --- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java +++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java @@ -26,13 +26,13 @@ import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import android.content.SharedPreferences; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.OnboardingPrefs; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.views.AllAppsEduView; @@ -40,9 +40,9 @@ import com.android.quickstep.views.AllAppsEduView; /** * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data. */ -public class QuickstepOnboardingPrefs extends OnboardingPrefs { +public class QuickstepOnboardingPrefs extends OnboardingPrefs { - public QuickstepOnboardingPrefs(BaseQuickstepLauncher launcher, SharedPreferences sharedPrefs) { + public QuickstepOnboardingPrefs(QuickstepLauncher launcher, SharedPreferences sharedPrefs) { super(launcher, sharedPrefs); StateManager stateManager = launcher.getStateManager(); diff --git a/res/values/config.xml b/res/values/config.xml index dc8bdffca3..fc0a5e10dc 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -69,7 +69,6 @@ - diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index b27abc4369..fd4c30c6f2 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -113,18 +113,6 @@ public interface DropTarget { return res; } - - - /** - * This is used to determine if an object is dropped at a different location than it was - * dragged from - */ - public boolean isMoved() { - return dragInfo.cellX != originalDragInfo.cellX - || dragInfo.cellY != originalDragInfo.cellY - || dragInfo.screenId != originalDragInfo.screenId - || dragInfo.container != originalDragInfo.container; - } } /** diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 53b65e812f..cbb577af74 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -108,7 +108,6 @@ import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; -import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.IconCache; @@ -1742,16 +1741,6 @@ public class Launcher extends StatefulActivity implements Launche return newFolder; } - /** - * Called when a workspace item is converted into a folder - */ - public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo){} - - /** - * Called when a folder is converted into a workspace item - */ - public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {} - /** * Unbinds the view for the specified item, and removes the item and all its children. * @@ -2189,9 +2178,6 @@ public class Launcher extends StatefulActivity implements Launche workspace.requestLayout(); } - @Override - public void bindPredictedItems(List appInfos, IntArray ranks) { } - /** * Add the views for a widget to the workspace. */ diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 782a8699cc..b278e81655 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -33,7 +33,6 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.model.PredictionModel; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.InstallSessionTracker; @@ -58,7 +57,6 @@ public class LauncherAppState { private final IconCache mIconCache; private final WidgetPreviewLoader mWidgetCache; private final InvariantDeviceProfile mInvariantDeviceProfile; - private final PredictionModel mPredictionModel; private SecureSettingsObserver mNotificationDotsObserver; private InstallSessionTracker mInstallSessionTracker; @@ -125,7 +123,6 @@ public class LauncherAppState { mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext)); - mPredictionModel = PredictionModel.newInstance(mContext); } protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { @@ -182,10 +179,6 @@ public class LauncherAppState { return mModel; } - public PredictionModel getPredictionModel() { - return mPredictionModel; - } - public WidgetPreviewLoader getWidgetCache() { return mWidgetCache; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index f58e0b496a..6aa4c430b1 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -217,6 +217,13 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } } + /** + * Called when the workspace items have drastically changed + */ + public void onWorkspaceUiChanged() { + MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete); + } + /** * Called when the model is destroyed */ diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 3be9ac7e07..252e64e534 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -1702,7 +1702,6 @@ public class Workspace extends PagedView fi.addItem(destInfo); fi.addItem(sourceInfo); } - mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo); return true; } return false; @@ -1719,7 +1718,7 @@ public class Workspace extends PagedView if (dropOverView instanceof FolderIcon) { FolderIcon fi = (FolderIcon) dropOverView; if (fi.acceptDrop(d.dragInfo)) { - mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId) + mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId) .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED); fi.onDrop(d, false /* itemReturnedOnFailedDrop */); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index b91d1c3260..61543c4ce5 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -24,6 +24,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; @@ -1183,7 +1184,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo newIcon.requestFocus(); } if (finalItem != null) { - mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem); + mStatsLogManager.logger().withItemInfo(finalItem) + .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON); } } } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index cca9836835..0f1432a9b6 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -19,6 +19,7 @@ import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.View.VISIBLE; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER; import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; @@ -72,12 +73,12 @@ import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; import com.android.launcher3.model.ModelDelegate; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetsModel; -import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; @@ -95,6 +96,7 @@ import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -467,12 +469,14 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper } IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, mIdp.numHotseatIcons); - int count = Math.min(ranks.size(), workspaceResult.mCachedPredictedItems.size()); + List predictions = workspaceResult.mHotseatPredictions == null + ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items; + int count = Math.min(ranks.size(), predictions.size()); for (int i = 0; i < count; i++) { - AppInfo appInfo = workspaceResult.mCachedPredictedItems.get(i); int rank = ranks.get(i); - WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(appInfo); - itemInfo.container = Favorites.CONTAINER_HOTSEAT_PREDICTION; + WorkspaceItemInfo itemInfo = + new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i)); + itemInfo.container = CONTAINER_HOTSEAT_PREDICTION; itemInfo.rank = rank; itemInfo.cellX = mHotseat.getCellXFromOrder(rank); itemInfo.cellY = mHotseat.getCellYFromOrder(rank); @@ -569,8 +573,7 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper return null; } - return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets, - mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel, null); + return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null); } } @@ -594,11 +597,10 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper } @Override - public WorkspaceResult call() throws Exception { + public WorkspaceResult call() { List allShortcuts = new ArrayList<>(); loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI); - return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets, - mBgDataModel.cachedPredictedItems, null, mWidgetProvidersMap); + return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap); } } @@ -618,17 +620,16 @@ public class LauncherPreviewRenderer extends ContextThemeWrapper private static class WorkspaceResult { private final ArrayList mWorkspaceItems; private final ArrayList mAppWidgets; - private final ArrayList mCachedPredictedItems; + private final FixedContainerItems mHotseatPredictions; private final WidgetsModel mWidgetsModel; private final Map mWidgetProvidersMap; - private WorkspaceResult(ArrayList workspaceItems, - ArrayList appWidgets, - ArrayList cachedPredictedItems, WidgetsModel widgetsModel, + private WorkspaceResult(BgDataModel dataModel, + WidgetsModel widgetsModel, Map widgetProviderInfoMap) { - mWorkspaceItems = workspaceItems; - mAppWidgets = appWidgets; - mCachedPredictedItems = cachedPredictedItems; + mWorkspaceItems = dataModel.workspaceItems; + mAppWidgets = dataModel.appWidgets; + mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); mWidgetsModel = widgetsModel; mWidgetProvidersMap = widgetProviderInfoMap; } diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 22823391dd..ec1c3ef779 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -316,7 +316,13 @@ public class StatsLogManager implements ResourceBasedOverride { LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625), @UiEvent(doc = "User tapped on image content in Overview Select mode.") - LAUNCHER_SELECT_MODE_IMAGE(627); + LAUNCHER_SELECT_MODE_IMAGE(627), + + @UiEvent(doc = "A folder was replaced by a single item") + LAUNCHER_FOLDER_CONVERTED_TO_ICON(628), + + @UiEvent(doc = "A hotseat prediction item was pinned") + LAUNCHER_HOTSEAT_PREDICTION_PINNED(629); // ADD MORE diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 586333fd2e..5c85babb76 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -17,7 +17,6 @@ package com.android.launcher3.model; import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; -import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; import android.util.Log; @@ -206,9 +205,6 @@ public abstract class BaseLoaderResults { mExtraItems.forEach(item -> executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor)); - // Locate available spots for prediction using currentWorkspaceItems - IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons); - bindPredictedItems(gaps, mainExecutor); // In case of validFirstPage, only bind the first screen, and defer binding the // remaining screens after first onDraw (and an optional the fade animation whichever // happens later). @@ -261,11 +257,6 @@ public abstract class BaseLoaderResults { } } - private void bindPredictedItems(IntArray ranks, final Executor executor) { - ArrayList items = new ArrayList<>(mBgDataModel.cachedPredictedItems); - executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor); - } - protected void executeCallbacksTask(CallbackTask task, Executor executor) { executor.execute(() -> { if (mMyBindingId != mBgDataModel.lastBindId) { diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 140342f98c..1fc897af7c 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -103,11 +103,6 @@ public class BgDataModel { */ public final IntSparseArrayMap extraItems = new IntSparseArrayMap<>(); - /** - * List of all cached predicted items visible on home screen - */ - public final ArrayList cachedPredictedItems = new ArrayList<>(); - /** * Maps all launcher activities to counts of their shortcuts. */ @@ -472,10 +467,5 @@ public class BgDataModel { default void bindExtraContainerItems(FixedContainerItems item) { } void bindAllApplications(AppInfo[] apps, int flags); - - /** - * Binds predicted appInfos at at available prediction slots. - */ - void bindPredictedItems(List appInfos, IntArray ranks); } } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 1dd8c112c6..983ec0c176 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -48,8 +48,6 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.TimingLogger; -import androidx.annotation.WorkerThread; - import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; @@ -190,13 +188,13 @@ public class LoaderTask implements Runnable { try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { List allShortcuts = new ArrayList<>(); loadWorkspace(allShortcuts); - loadCachedPredictions(); logger.addSplit("loadWorkspace"); verifyNotStopped(); mResults.bindWorkspace(); logger.addSplit("bindWorkspace"); + mModelDelegate.workspaceLoadComplete(); // Notify the installer packages of packages with active installs on the first screen. sendFirstScreenActiveInstallsBroadcast(); logger.addSplit("sendFirstScreenActiveInstallsBroadcast"); @@ -839,24 +837,6 @@ public class LoaderTask implements Runnable { } } - @WorkerThread - private void loadCachedPredictions() { - synchronized (mBgDataModel) { - List componentKeys = - mApp.getPredictionModel().getPredictionComponentKeys(); - List l; - mBgDataModel.cachedPredictedItems.clear(); - for (ComponentKey key : componentKeys) { - l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user); - if (l.size() == 0) continue; - AppInfo info = new AppInfo(l.get(0), key.user, - mUserManagerState.isUserQuiet(key.user)); - mBgDataModel.cachedPredictedItems.add(info); - mIconCache.getTitleAndIcon(info, false); - } - } - } - private void sanitizeData() { Context context = mApp.getContext(); if (mItemsDeleted) { @@ -900,14 +880,6 @@ public class LoaderTask implements Runnable { PackageInstallInfo.fromInstallingState(info)); } } - for (AppInfo item : mBgDataModel.cachedPredictedItems) { - List l = mLauncherApps.getActivityList( - item.componentName.getPackageName(), item.user); - for (LauncherActivityInfo info : l) { - boolean quietMode = mUserManagerState.isUserQuiet(item.user); - mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info); - } - } mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED, mUserManagerState.isAnyProfileQuietModeEnabled()); diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java index 53e8a86078..3ed880906d 100644 --- a/src/com/android/launcher3/model/ModelDelegate.java +++ b/src/com/android/launcher3/model/ModelDelegate.java @@ -71,6 +71,12 @@ public class ModelDelegate implements ResourceBasedOverride { @WorkerThread public void loadItems(UserManagerState ums, Map pinnedShortcuts) { } + /** + * Called during loader after workspace loading is complete + */ + @WorkerThread + public void workspaceLoadComplete() { } + /** * Called when the delegate is no loner needed */ diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java deleted file mode 100644 index cb3903d4ed..0000000000 --- a/src/com/android/launcher3/model/PredictionModel.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.model; - -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; - -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.UserHandle; - -import androidx.annotation.AnyThread; -import androidx.annotation.WorkerThread; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.Preconditions; -import com.android.launcher3.util.ResourceBasedOverride; - -import java.util.ArrayList; -import java.util.List; - -/** - * Model Helper for app predictions - */ -public class PredictionModel implements ResourceBasedOverride { - - private static final String CACHED_ITEMS_KEY = "predicted_item_keys"; - private static final int MAX_CACHE_ITEMS = 5; - - protected Context mContext; - private SharedPreferences mDevicePrefs; - private UserCache mUserCache; - - - /** - * Retrieve instance of this object that can be overridden in runtime based on the build - * variant of the application. - */ - public static PredictionModel newInstance(Context context) { - PredictionModel model = Overrides.getObject(PredictionModel.class, context, - R.string.prediction_model_class); - model.init(context); - return model; - } - - protected void init(Context context) { - mContext = context; - mDevicePrefs = Utilities.getDevicePrefs(mContext); - mUserCache = UserCache.INSTANCE.get(mContext); - - } - - /** - * Formats and stores a list of component key in device preferences. - */ - @AnyThread - public void cachePredictionComponentKeys(List componentKeys) { - MODEL_EXECUTOR.execute(() -> { - LauncherAppState appState = LauncherAppState.getInstance(mContext); - StringBuilder builder = new StringBuilder(); - int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); - for (int i = 0; i < count; i++) { - builder.append(serializeComponentKeyToString(componentKeys.get(i))); - builder.append("\n"); - } - if (componentKeys.isEmpty() /* should invalidate loader items */) { - appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { - @Override - public void execute(LauncherAppState app, BgDataModel model, AllAppsList apps) { - model.cachedPredictedItems.clear(); - } - }); - } - mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); - }); - } - - /** - * parses and returns ComponentKeys saved by - * {@link PredictionModel#cachePredictionComponentKeys(List)} - */ - @WorkerThread - public List getPredictionComponentKeys() { - Preconditions.assertWorkerThread(); - ArrayList items = new ArrayList<>(); - String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); - for (String line : cachedBlob.split("\n")) { - ComponentKey key = getComponentKeyFromSerializedString(line); - if (key != null) { - items.add(key); - } - - } - return items; - } - - private String serializeComponentKeyToString(ComponentKey componentKey) { - long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user); - return componentKey.componentName.flattenToString() + "#" + userSerialNumber; - } - - private ComponentKey getComponentKeyFromSerializedString(String str) { - int sep = str.indexOf('#'); - if (sep < 0 || (sep + 1) >= str.length()) { - return null; - } - ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep)); - if (componentName == null) { - return null; - } - try { - long serialNumber = Long.parseLong(str.substring(sep + 1)); - UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber); - return userHandle != null ? new ComponentKey(componentName, userHandle) : null; - } catch (NumberFormatException ex) { - return null; - } - } -} diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index 31c3014874..2b04365ddc 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -196,9 +196,6 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity @Override public void bindItems(List shortcuts, boolean forceAnimateIcons) { } - @Override - public void bindPredictedItems(List appInfos, IntArray ranks) { } - @Override public void bindScreens(IntArray orderedScreenIds) { } diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java index 6e5e7d9355..1b33197879 100644 --- a/src/com/android/launcher3/util/OnboardingPrefs.java +++ b/src/com/android/launcher3/util/OnboardingPrefs.java @@ -44,7 +44,8 @@ public class OnboardingPrefs { */ @StringDef(value = { HOME_BOUNCE_SEEN, - SHELF_BOUNCE_SEEN + SHELF_BOUNCE_SEEN, + HOTSEAT_LONGPRESS_TIP_SEEN }) @Retention(RetentionPolicy.SOURCE) public @interface EventBoolKey {}