diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml index 6aa9619cc9..1937164b7d 100644 --- a/quickstep/recents_ui_overrides/res/values/override.xml +++ b/quickstep/recents_ui_overrides/res/values/override.xml @@ -25,8 +25,11 @@ com.android.quickstep.QuickstepProcessInitializer - com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension + com.android.quickstep.logging.UserEventDispatcherExtension com.android.launcher3.hybridhotseat.HotseatPredictionModel + + com.android.launcher3.model.QuickstepModelDelegate + diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index e11c701640..55384af96b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -30,7 +30,6 @@ import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.util.IntProperty; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Interpolator; @@ -44,19 +43,15 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; -import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; import com.android.launcher3.anim.AlphaUpdateListener; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.keyboard.FocusIndicatorHelper; import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper; import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; -import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -67,15 +62,12 @@ import com.android.launcher3.util.Themes; import com.android.quickstep.AnimatedFloat; import java.util.ArrayList; -import java.util.Collections; import java.util.List; @TargetApi(Build.VERSION_CODES.P) public class PredictionRowView extends LinearLayout implements LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow { - private static final String TAG = "PredictionRowView"; - private static final IntProperty TEXT_ALPHA = new IntProperty("textAlpha") { @Override @@ -93,16 +85,14 @@ public class PredictionRowView extends LinearLayout implements (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f; private final Launcher mLauncher; - private final PredictionUiStateManager mPredictionUiStateManager; private int mNumPredictedAppsPerRow; - // The set of predicted app component names - private final List mPredictedAppComponents = new ArrayList<>(); - // The set of predicted apps resolved from the component names and the current set of apps - private final ArrayList mPredictedApps = new ArrayList<>(); // Helper to drawing the focus indicator. private final FocusIndicatorHelper mFocusHelper; + // The set of predicted apps resolved from the component names and the current set of apps + private final List mPredictedApps = new ArrayList<>(); + private final int mIconTextColor; private final int mIconFullTextAlpha; private int mIconLastSetTextAlpha; @@ -134,8 +124,6 @@ public class PredictionRowView extends LinearLayout implements mLauncher = Launcher.getLauncher(context); mLauncher.addOnDeviceProfileChangeListener(this); - mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context); - mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary); mIconFullTextAlpha = Color.alpha(mIconTextColor); mIconCurrentTextAlpha = mIconFullTextAlpha; @@ -146,24 +134,9 @@ public class PredictionRowView extends LinearLayout implements @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - - mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView()); - getAppsStore().registerIconContainer(this); AllAppsTipView.scheduleShowIfNeeded(mLauncher); } - private AllAppsStore getAppsStore() { - return mLauncher.getAppsView().getAppsStore(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - mPredictionUiStateManager.setTargetAppsView(null); - getAppsStore().unregisterIconContainer(this); - } - public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { mParent = parent; } @@ -205,7 +178,7 @@ public class PredictionRowView extends LinearLayout implements * Returns the predicted apps. */ public List getPredictedApps() { - return mPredictedApps; + return new ArrayList<>(mPredictedApps); } /** @@ -217,12 +190,12 @@ public class PredictionRowView extends LinearLayout implements * If the number of predicted apps is the same as the previous list of predicted apps, * we can optimize by swapping them in place. */ - public void setPredictedApps(List apps) { - mPredictedAppComponents.clear(); - mPredictedAppComponents.addAll(apps); - + public void setPredictedApps(List items) { mPredictedApps.clear(); - mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); + items.stream() + .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo) + .map(itemInfo -> (WorkspaceItemInfo) itemInfo) + .forEach(mPredictedApps::add); applyPredictionApps(); } @@ -264,11 +237,7 @@ public class PredictionRowView extends LinearLayout implements icon.reset(); if (predictionCount > i) { icon.setVisibility(View.VISIBLE); - if (mPredictedApps.get(i) instanceof AppInfo) { - icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i)); - } else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) { - icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i)); - } + icon.applyFromWorkspaceItem(mPredictedApps.get(i)); icon.setTextColor(iconColor); } else { icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE); @@ -284,33 +253,6 @@ public class PredictionRowView extends LinearLayout implements mParent.onHeightUpdated(); } - private List processPredictedAppComponents( - List components) { - if (getAppsStore().getApps().length == 0) { - // Apps have not been bound yet. - return Collections.emptyList(); - } - - List predictedApps = new ArrayList<>(); - for (ComponentKeyMapper mapper : components) { - ItemInfoWithIcon info = mapper.getApp(getAppsStore()); - if (info != null) { - ItemInfoWithIcon predictedApp = info.clone(); - predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION; - predictedApps.add(predictedApp); - } else { - if (FeatureFlags.IS_STUDIO_BUILD) { - Log.e(TAG, "Predicted app not found: " + mapper); - } - } - // Stop at the number of predicted apps - if (predictedApps.size() == mNumPredictedAppsPerRow) { - break; - } - } - return predictedApps; - } - @Override public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child, ArrayList parents) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java deleted file mode 100644 index 830c1032ba..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ /dev/null @@ -1,348 +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.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.OVERVIEW; - -import android.app.prediction.AppPredictor; -import android.app.prediction.AppTarget; -import android.content.ComponentName; -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherState; -import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; -import com.android.launcher3.hybridhotseat.HotseatPredictionController; -import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.statemanager.StateManager.StateListener; -import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.MainThreadInitializedObject; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.OptionalInt; -import java.util.stream.IntStream; - -/** - * Handler responsible to updating the UI due to predicted apps changes. Operations: - * 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes - * invisible again before applying the changes. This ensures that the UI does not change abruptly - * in front of the user, even if an app launched and user pressed back button to return to the - * all-apps UI again. - * 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory - * even if all-apps is not opened as they are shown in search UI as well - * 3) Load instant app if it is not already in memory. As predictions are persisted on disk, - * instant app will not be in memory when launcher starts. - * 4) Maintains the current active client id (for the predictions) and all updates are performed on - * that client id. - */ -public class PredictionUiStateManager implements StateListener, - ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener { - - public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; - - // TODO (b/129421797): Update the client constants - public enum Client { - HOME("home"); - - public final String id; - - Client(String id) { - this.id = id; - } - } - - public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(PredictionUiStateManager::new); - - private final Context mContext; - - private final DynamicItemCache mDynamicItemCache; - private List mPredictionServicePredictions = Collections.emptyList(); - - private int mMaxIconsPerRow; - - private AllAppsContainerView mAppsView; - - private PredictionState mPendingState; - private PredictionState mCurrentState; - - private boolean mGettingValidPredictionResults; - - private PredictionUiStateManager(Context context) { - mContext = context; - - mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated); - - InvariantDeviceProfile idp = LauncherAppState.getIDP(context); - mMaxIconsPerRow = idp.numColumns; - - idp.addOnChangeListener(this); - mGettingValidPredictionResults = Utilities.getDevicePrefs(context) - .getBoolean(LAST_PREDICTION_ENABLED_STATE, true); - - // Call this last - mCurrentState = parseLastState(); - } - - @Override - public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - mMaxIconsPerRow = profile.numColumns; - } - - public void setTargetAppsView(AllAppsContainerView appsView) { - if (mAppsView != null) { - mAppsView.getAppsStore().removeUpdateListener(this); - } - mAppsView = appsView; - if (mAppsView != null) { - mAppsView.getAppsStore().addUpdateListener(this); - } - if (mPendingState != null) { - applyState(mPendingState); - mPendingState = null; - } else { - applyState(mCurrentState); - } - updateDependencies(mCurrentState); - } - - @Override - public void reapplyItemInfo(ItemInfoWithIcon info) { } - - @Override - public void onStateTransitionComplete(LauncherState state) { - if (mAppsView == null) { - return; - } - if (mPendingState != null && canApplyPredictions(mPendingState)) { - applyState(mPendingState); - mPendingState = null; - } - if (mPendingState == null) { - Launcher.getLauncher(mAppsView.getContext()).getStateManager() - .removeStateListener(this); - } - } - - private void scheduleApplyPredictedApps(PredictionState state) { - boolean registerListener = mPendingState == null; - mPendingState = state; - if (registerListener) { - // Add a listener and wait until appsView is invisible again. - Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this); - } - } - - private void applyState(PredictionState state) { - mCurrentState = state; - if (mAppsView != null) { - mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) - .setPredictedApps(mCurrentState.apps); - } - } - - private void updatePredictionStateAfterCallback() { - boolean validResults = mPredictionServicePredictions != null - && !mPredictionServicePredictions.isEmpty(); - if (validResults != mGettingValidPredictionResults) { - mGettingValidPredictionResults = validResults; - Utilities.getDevicePrefs(mContext).edit() - .putBoolean(LAST_PREDICTION_ENABLED_STATE, true) - .apply(); - } - dispatchOnChange(true); - } - - public AppPredictor.Callback appPredictorCallback(Client client) { - return targets -> { - mPredictionServicePredictions = targets; - updatePredictionStateAfterCallback(); - }; - } - - private void dispatchOnChange(boolean changed) { - PredictionState newState = changed - ? parseLastState() - : mPendingState != null && canApplyPredictions(mPendingState) - ? mPendingState - : mCurrentState; - if (changed && mAppsView != null && !canApplyPredictions(newState)) { - scheduleApplyPredictedApps(newState); - } else { - applyState(newState); - } - } - - private PredictionState parseLastState() { - PredictionState state = new PredictionState(); - state.isEnabled = mGettingValidPredictionResults; - if (!state.isEnabled) { - state.apps = Collections.EMPTY_LIST; - return state; - } - - state.apps = new ArrayList<>(); - - List appTargets = mPredictionServicePredictions; - if (!appTargets.isEmpty()) { - 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()); - } - state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache)); - } - } - updateDependencies(state); - return state; - } - - private void updateDependencies(PredictionState state) { - if (!state.isEnabled || mAppsView == null) { - return; - } - mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this, - mMaxIconsPerRow); - } - - @Override - public void onAppsUpdated() { - dispatchOnChange(false); - } - - private boolean canApplyPredictions(PredictionState newState) { - if (mAppsView == null) { - // If there is no apps view, no need to schedule. - return true; - } - Launcher launcher = Launcher.getLauncher(mAppsView.getContext()); - PredictionRowView predictionRow = mAppsView.getFloatingHeaderView(). - findFixedRowByType(PredictionRowView.class); - if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 || - launcher.isForceInvisible()) { - return true; - } - - if (mCurrentState.isEnabled != newState.isEnabled - || mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) { - // If the visibility of the prediction row is changing, apply immediately. - return true; - } - - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - // If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way - return false; - } - if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) { - // Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI - return false; - } - - // Instead of checking against 1, we should check against (1 + delta), where delta accounts - // for the nav-bar height (as app icon can still be visible under the nav-bar). Checking - // against 1, keeps the logic simple :) - return launcher.getAllAppsController().getProgress() > 1; - } - - public PredictionState getCurrentState() { - return mCurrentState; - } - - /** - * Returns ranking info for the app within all apps prediction. - * Only applicable when {@link ItemInfo#itemType} is one of the followings: - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} - */ - public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) { - if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) { - return OptionalInt.empty(); - } - - if (itemInfo.itemType == ITEM_TYPE_APPLICATION - || itemInfo.itemType == ITEM_TYPE_SHORTCUT - || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) { - ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), - itemInfo.user); - final List apps = getCurrentState().apps; - return IntStream.range(0, apps.size()) - .filter(index -> key.equals(apps.get(index).getComponentKey())) - .findFirst(); - } - - return OptionalInt.empty(); - } - - /** - * Fill in predicted_rank field based on app prediction. - * Only applicable when {@link ItemInfo#itemType} is one of the followings: - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} - */ - public static void fillInPredictedRank( - @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { - - final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate(); - if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null - || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT - && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { - return; - } - if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) { - HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target); - return; - } - - final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); - final List predictedApps = manager.getCurrentState().apps; - IntStream.range(0, predictedApps.size()) - .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) - .findFirst() - .ifPresent((rank) -> target.predictedRank = 0 - rank); - } - - public static class PredictionState { - - public boolean isEnabled; - public List apps; - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java similarity index 65% rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java rename to quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java index 8cabe3d80b..8e4c43f464 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java @@ -13,175 +13,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.appprediction; +package com.android.launcher3.model; -import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST; 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; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; -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.content.Context; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; -import android.util.Log; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.appprediction.PredictionUiStateManager.Client; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; 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.AppLaunchTracker; import com.android.launcher3.pm.UserCache; -import com.android.quickstep.logging.StatsLogCompatManager; import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer; import java.util.Locale; +import java.util.function.Consumer; import java.util.function.Predicate; /** - * Subclass of app tracker which publishes the data to the prediction engine and gets back results. + * Utility class to track stats log and emit corresponding app events */ -@TargetApi(Build.VERSION_CODES.Q) -public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer { +@TargetApi(Build.VERSION_CODES.R) +public class AppEventProducer implements StatsLogConsumer { - private static final String TAG = "PredictionAppTracker"; - private static final boolean DBG = false; + private static final int MSG_LAUNCH = 0; - private static final int MSG_INIT = 0; - private static final int MSG_DESTROY = 1; - private static final int MSG_LAUNCH = 2; - private static final int MSG_PREDICT = 3; - - protected final Context mContext; + private final Context mContext; private final Handler mMessageHandler; + private final Consumer mCallback; - // Accessed only on worker thread - private AppPredictor mHomeAppPredictor; - - public PredictionAppTracker(Context context) { + public AppEventProducer(Context context, Consumer callback) { mContext = context; - mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage); - InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged); - - mMessageHandler.sendEmptyMessage(MSG_INIT); - } - - @UiThread - private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - if ((changeFlags & CHANGE_FLAG_GRID) != 0) { - // Reinitialize everything - mMessageHandler.sendEmptyMessage(MSG_INIT); - } - } - - @WorkerThread - private void destroy() { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.destroy(); - mHomeAppPredictor = null; - } - StatsLogCompatManager.LOGS_CONSUMER.remove(this); - } - - @WorkerThread - private AppPredictor createPredictor(Client client, int count) { - AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class); - - if (apm == null) { - return null; - } - - AppPredictor predictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mContext) - .setUiSurface(client.id) - .setPredictedTargetCount(count) - .setExtras(getAppPredictionContextExtras(client)) - .build()); - predictor.registerPredictionUpdates(mContext.getMainExecutor(), - PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client)); - predictor.requestPredictionUpdate(); - return predictor; - } - - /** - * Override to add custom extras. - */ - @WorkerThread - @Nullable - public Bundle getAppPredictionContextExtras(Client client) { - return null; + mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage); + mCallback = callback; } @WorkerThread private boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_INIT: { - // Destroy any existing clients - destroy(); - - // Initialize the clients - int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns; - mHomeAppPredictor = createPredictor(Client.HOME, count); - StatsLogCompatManager.LOGS_CONSUMER.add(this); - return true; - } - case MSG_DESTROY: { - destroy(); - return true; - } case MSG_LAUNCH: { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj); - } - return true; - } - case MSG_PREDICT: { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.requestPredictionUpdate(); - } + mCallback.accept((AppTargetEvent) msg.obj); return true; } } return false; } - @Override - @UiThread - public void onReturnedToHome() { - String client = Client.HOME.id; - mMessageHandler.removeMessages(MSG_PREDICT, client); - Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget(); - if (DBG) { - Log.d(TAG, String.format("Sent immediate message to update %s", client)); - } - } - @AnyThread private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) { AppTarget target = toAppTarget(atomInfo); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java new file mode 100644 index 0000000000..721e2bebd5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java @@ -0,0 +1,117 @@ +/* + * 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.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE; +import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; + +import android.app.prediction.AppTarget; +import android.content.ComponentName; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.os.UserHandle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.Utilities; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Task to update model as a result of predicted apps update + */ +public class PredictionUpdateTask extends BaseModelUpdateTask { + + private final List mTargets; + private final int mContainerId; + + PredictionUpdateTask(int containerId, List targets) { + mContainerId = containerId; + mTargets = targets; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + // TODO: persist the whole list + Utilities.getDevicePrefs(app.getContext()).edit() + .putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply(); + + FixedContainerItems fci; + synchronized (dataModel) { + fci = dataModel.extraItems.get(mContainerId); + if (fci == null) { + return; + } + } + + Set usersForChangedShortcuts = new HashSet<>(fci.items.stream() + .filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT) + .map(info -> info.user) + .collect(Collectors.toSet())); + fci.items.clear(); + + for (AppTarget target : mTargets) { + WorkspaceItemInfo itemInfo; + ShortcutInfo si = target.getShortcutInfo(); + if (si != null) { + usersForChangedShortcuts.add(si.getUserHandle()); + itemInfo = new WorkspaceItemInfo(si, app.getContext()); + app.getIconCache().getShortcutIcon(itemInfo, si); + } else { + String className = target.getClassName(); + if (COMPONENT_CLASS_MARKER.equals(className)) { + // TODO: Implement this + continue; + } + ComponentName cn = new ComponentName(target.getPackageName(), className); + UserHandle user = target.getUser(); + itemInfo = apps.data.stream() + .filter(info -> user.equals(info.user) && cn.equals(info.componentName)) + .map(AppInfo::makeWorkspaceItem) + .findAny() + .orElseGet(() -> { + LauncherActivityInfo lai = app.getContext() + .getSystemService(LauncherApps.class) + .resolveActivity(AppInfo.makeLaunchIntent(cn), user); + if (lai == null) { + return null; + } + AppInfo ai = new AppInfo(app.getContext(), lai, user); + app.getIconCache().getTitleAndIcon(ai, lai, false); + return ai.makeWorkspaceItem(); + }); + + if (itemInfo == null) { + continue; + } + } + + itemInfo.container = mContainerId; + fci.items.add(itemInfo); + } + + bindExtraContainerItems(fci); + usersForChangedShortcuts.forEach( + u -> dataModel.updateShortcutPinnedState(app.getContext(), u)); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java new file mode 100644 index 0000000000..b5164695d5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -0,0 +1,138 @@ +/* + * 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.InvariantDeviceProfile.CHANGE_FLAG_GRID; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; + +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.Context; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.util.Executors; +import com.android.quickstep.logging.StatsLogCompatManager; + +import java.util.List; + +/** + * Model delegate which loads prediction items + */ +public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener { + + public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; + + private final InvariantDeviceProfile mIDP; + private final AppEventProducer mAppEventProducer; + + private AppPredictor mAllAppsPredictor; + private boolean mActive = false; + + public QuickstepModelDelegate(Context context) { + mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent); + + mIDP = InvariantDeviceProfile.INSTANCE.get(context); + mIDP.addOnChangeListener(this); + StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer); + } + + @Override + public void loadItems() { + // TODO: Implement caching and preloading + super.loadItems(); + mDataModel.extraItems.put( + CONTAINER_PREDICTION, new FixedContainerItems(CONTAINER_PREDICTION)); + + mActive = true; + recreatePredictors(); + } + + @Override + public void validateData() { + super.validateData(); + if (mAllAppsPredictor != null) { + mAllAppsPredictor.requestPredictionUpdate(); + } + } + + @Override + public void destroy() { + super.destroy(); + mActive = false; + StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer); + + destroyPredictors(); + mIDP.removeOnChangeListener(this); + } + + private void destroyPredictors() { + if (mAllAppsPredictor != null) { + mAllAppsPredictor.destroy(); + mAllAppsPredictor = null; + } + } + + @WorkerThread + private void recreatePredictors() { + destroyPredictors(); + if (!mActive) { + return; + } + + Context context = mApp.getContext(); + AppPredictionManager apm = context.getSystemService(AppPredictionManager.class); + if (apm == null) { + return; + } + + int count = mIDP.numAllAppsColumns; + + mAllAppsPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(context) + .setUiSurface("home") + .setPredictedTargetCount(count) + .build()); + mAllAppsPredictor.registerPredictionUpdates( + Executors.MODEL_EXECUTOR, this::onAllAppsPredictionChanged); + mAllAppsPredictor.requestPredictionUpdate(); + } + + private void onAllAppsPredictionChanged(List targets) { + mApp.getModel().enqueueModelUpdateTask( + new PredictionUpdateTask(CONTAINER_PREDICTION, targets)); + } + + @Override + public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { + if ((changeFlags & CHANGE_FLAG_GRID) != 0) { + // Reinitialize everything + Executors.MODEL_EXECUTOR.execute(this::recreatePredictors); + } + } + + private void onAppTargetEvent(AppTargetEvent event) { + if (mAllAppsPredictor != null) { + mAllAppsPredictor.notifyAppTargetEvent(event); + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index beb2a6a156..43dba851da 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -17,6 +17,9 @@ package com.android.launcher3.uioverrides; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; +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.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; @@ -37,16 +40,18 @@ import android.view.View; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.appprediction.PredictionUiStateManager; +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; @@ -79,7 +84,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.OptionalInt; +import java.util.Objects; import java.util.stream.Stream; public class QuickstepLauncher extends BaseQuickstepLauncher { @@ -91,6 +96,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); + private FixedContainerItems mAllAppsPredictions; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -111,8 +118,23 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { StatsLogger logger = getStatsLogManager() .logger().withItemInfo(info).withInstanceId(instanceId); - OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info); - allAppsRank.ifPresent(logger::withRank); + + if (mAllAppsPredictions != null + && (info.itemType == ITEM_TYPE_APPLICATION + || info.itemType == ITEM_TYPE_SHORTCUT + || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) { + int count = mAllAppsPredictions.items.size(); + for (int i = 0; i < count; i++) { + ItemInfo targetInfo = mAllAppsPredictions.items.get(i); + if (targetInfo.itemType == info.itemType + && targetInfo.user.equals(info.user) + && Objects.equals(targetInfo.getIntent(), info.getIntent())) { + logger.withRank(i); + break; + } + + } + } logger.log(LAUNCHER_APP_LAUNCH_TAP); if (mHotseatPredictionController != null) { @@ -199,6 +221,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + @Override + public void bindExtraContainerItems(FixedContainerItems item) { + if (item.containerId == Favorites.CONTAINER_PREDICTION) { + mAllAppsPredictions = item; + getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) + .setPredictedApps(item.items); + } + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index b02035516d..5a35eb5eb2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -41,7 +41,6 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.statemanager.StateManager; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java deleted file mode 100644 index e0008033b4..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 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.quickstep.logging; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.android.launcher3.appprediction.PredictionUiStateManager; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.userevent.nano.LauncherLogProto; - -import java.util.ArrayList; - -/** - * This class handles AOSP MetricsLogger function calls and logging around - * quickstep interactions and app launches. - */ -@SuppressWarnings("unused") -public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension { - - public static final int ALL_APPS_PREDICTION_TIPS = 2; - - private static final String TAG = "UserEventDispatcher"; - - public UserEventDispatcherAppPredictionExtension(Context context) { - super(context); - } - - @Override - protected void onFillInLogContainerData( - @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target, - @NonNull ArrayList targets) { - PredictionUiStateManager.fillInPredictedRank(itemInfo, target); - } -} diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java deleted file mode 100644 index 5904fcd691..0000000000 --- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java +++ /dev/null @@ -1,162 +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.quickstep; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -import android.app.prediction.AppPredictor; -import android.app.prediction.AppTarget; -import android.app.prediction.AppTargetId; -import android.content.ComponentName; -import android.content.pm.LauncherActivityInfo; -import android.content.pm.LauncherApps; -import android.os.Process; -import android.view.View; - -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.Launcher; -import com.android.launcher3.appprediction.PredictionRowView; -import com.android.launcher3.appprediction.PredictionUiStateManager; -import com.android.launcher3.appprediction.PredictionUiStateManager.Client; -import com.android.launcher3.model.AppLaunchTracker; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -@LargeTest -@RunWith(AndroidJUnit4.class) -public class AppPredictionsUITests extends AbstractQuickStepTest { - - private LauncherActivityInfo mSampleApp1; - private LauncherActivityInfo mSampleApp2; - private LauncherActivityInfo mSampleApp3; - - private AppPredictor.Callback mCallback; - - @Before - public void setUp() throws Exception { - super.setUp(); - - List activities = mTargetContext.getSystemService(LauncherApps.class) - .getActivityList(null, Process.myUserHandle()); - mSampleApp1 = activities.get(0); - mSampleApp2 = activities.get(1); - mSampleApp3 = activities.get(2); - - // Disable app tracker - AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); - PredictionUiStateManager.INSTANCE.initializeForTesting(null); - - mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback( - Client.HOME); - - mDevice.setOrientationNatural(); - } - - @After - public void tearDown() throws Throwable { - AppLaunchTracker.INSTANCE.initializeForTesting(null); - PredictionUiStateManager.INSTANCE.initializeForTesting(null); - mDevice.unfreezeRotation(); - } - - /** - * Test that prediction UI is updated as soon as we get predictions from the system - */ - @Test - public void testPredictionExistsInAllApps() { - mLauncher.pressHome().switchToAllApps(); - - // Dispatch an update - sendPredictionUpdate(mSampleApp1, mSampleApp2); - // The first update should apply immediately. - waitForLauncherCondition("Predictions were not updated in loading state", - launcher -> getPredictedApp(launcher).size() == 2); - } - - /** - * Test that prediction update is deferred if it is already visible - */ - @Test - public void testPredictionsDeferredUntilHome() { - mDevice.pressHome(); - sendPredictionUpdate(mSampleApp1, mSampleApp2); - mLauncher.pressHome().switchToAllApps(); - waitForLauncherCondition("Predictions were not updated in loading state", - launcher -> getPredictedApp(launcher).size() == 2); - - // Update predictions while all-apps is visible - sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3); - assertEquals(2, getFromLauncher(this::getPredictedApp).size()); - - // Go home and go back to all-apps - mLauncher.pressHome().switchToAllApps(); - assertEquals(3, getFromLauncher(this::getPredictedApp).size()); - } - - @Test - public void testPredictionsDisabled() { - mDevice.pressHome(); - sendPredictionUpdate(); - mLauncher.pressHome().switchToAllApps(); - - waitForLauncherCondition("Predictions were not updated in loading state", - launcher -> launcher.getAppsView().getFloatingHeaderView() - .findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE); - assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext) - .getCurrentState().isEnabled); - } - - public ArrayList getPredictedApp(Launcher launcher) { - PredictionRowView container = launcher.getAppsView().getFloatingHeaderView() - .findFixedRowByType(PredictionRowView.class); - - ArrayList predictedAppViews = new ArrayList<>(); - for (int i = 0; i < container.getChildCount(); i++) { - View view = container.getChildAt(i); - if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) { - predictedAppViews.add((BubbleTextView) view); - } - } - return predictedAppViews; - } - - private void sendPredictionUpdate(LauncherActivityInfo... activities) { - getOnUiThread(() -> { - List targets = new ArrayList<>(activities.length); - for (LauncherActivityInfo info : activities) { - ComponentName cn = info.getComponentName(); - AppTarget target = new AppTarget.Builder( - new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser()) - .setClassName(cn.getClassName()) - .build(); - targets.add(target); - } - mCallback.onTargetsAvailable(targets); - return null; - }); - } -} diff --git a/res/values/config.xml b/res/values/config.xml index 75fcc907db..dc8bdffca3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -70,6 +70,7 @@ + diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index f9fba6f80f..db1f1b0c6a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -119,7 +119,6 @@ import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.UserEventDispatcher; -import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.data.AppInfo; @@ -911,14 +910,12 @@ public class Launcher extends StatefulActivity implements Launche logStopAndResume(Action.Command.RESUME); getUserEventDispatcher().startSession(); - AppLaunchTracker.INSTANCE.get(this).onReturnedToHome(); - // Process any items that were added while Launcher was away. InstallShortcutReceiver.disableAndFlushInstallQueue( InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); // Refresh shortcuts if the permission changed. - mModel.refreshShortcutsIfRequired(); + mModel.validateModelDataOnResume(); // Set the notification listener and fetch updated notifications when we resume NotificationListener.setNotificationsChangedListener(mPopupDataProvider); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 53e5274c0c..e3fe87d1e9 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -128,7 +128,7 @@ public class LauncherAppState { mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context); mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); - mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); + mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext)); mPredictionModel = PredictionModel.newInstance(mContext); } @@ -157,6 +157,7 @@ public class LauncherAppState { * Call from Application.onTerminate(), which is not guaranteed to ever be called. */ public void onTerminate() { + mModel.destroy(); if (mModelChangeReceiver != null) { mContext.unregisterReceiver(mModelChangeReceiver); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index ff4b545a06..e2568d5ef6 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -20,7 +20,6 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; import android.content.Context; import android.content.Intent; @@ -46,6 +45,7 @@ import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; +import com.android.launcher3.model.ModelDelegate; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.PackageInstallStateChangedTask; import com.android.launcher3.model.PackageUpdatedTask; @@ -112,20 +112,22 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi */ private final BgDataModel mBgDataModel = new BgDataModel(); + private final ModelDelegate mModelDelegate; + // Runnable to check if the shortcuts permission has changed. - private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { + private final Runnable mDataValidationCheck = new Runnable() { @Override public void run() { - if (mModelLoaded && hasShortcutsPermission(mApp.getContext()) - != mBgAllAppsList.hasShortcutHostPermission()) { - forceReload(); + if (mModelLoaded) { + mModelDelegate.validateData(); } } }; - LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { + LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) { mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); + mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel); } /** @@ -217,6 +219,13 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } } + /** + * Called when the model is destroyed + */ + public void destroy() { + MODEL_EXECUTOR.execute(mModelDelegate::destroy); + } + public void onBroadcastIntent(Intent intent) { if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); final String action = intent.getAction(); @@ -372,7 +381,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi public void startLoaderForResults(LoaderResults results) { synchronized (mLock) { stopLoader(); - mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results); + mLoaderTask = new LoaderTask( + mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results); // Always post the loader task, instead of running directly (even on same thread) so // that we exit any nested synchronized blocks @@ -491,9 +501,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi * Current implementation simply reloads the workspace, but it can be optimized to * use partial updates similar to {@link UserCache} */ - public void refreshShortcutsIfRequired() { - MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable); - MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable); + public void validateModelDataOnResume() { + MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck); + MODEL_EXECUTOR.post(mDataValidationCheck); } /** diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index a424f8458a..d8eb838ed5 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -74,6 +74,7 @@ import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; 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; @@ -579,7 +580,7 @@ public class LauncherPreviewRenderer { private final FutureTask mTask = new FutureTask<>(this); WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) { - super(app, null, new BgDataModel(), null); + super(app, null, new BgDataModel(), new ModelDelegate(), null); } @Override diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java deleted file mode 100644 index a93c0dd77c..0000000000 --- a/src/com/android/launcher3/model/AppLaunchTracker.java +++ /dev/null @@ -1,33 +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.model; - -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import com.android.launcher3.R; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -/** - * Callback for receiving various app launch events - */ -public class AppLaunchTracker implements ResourceBasedOverride { - - public static final MainThreadInitializedObject INSTANCE = - forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class); - - public void onReturnedToHome() { } -} diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index dfdc1387ff..6158a9c6b4 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -108,13 +108,6 @@ public class BgDataModel { */ public final ArrayList cachedPredictedItems = new ArrayList<>(); - /** - * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION - * @see Callbacks#FLAG_QUIET_MODE_ENABLED - * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION - */ - public int flags; - /** * Maps all launcher activities to counts of their shortcuts. */ diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index bea00869ee..b0679095f3 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -109,6 +109,7 @@ public class LoaderTask implements Runnable { protected final LauncherAppState mApp; private final AllAppsList mBgAllAppsList; protected final BgDataModel mBgDataModel; + private final ModelDelegate mModelDelegate; private FirstScreenBroadcast mFirstScreenBroadcast; @@ -128,10 +129,11 @@ public class LoaderTask implements Runnable { private boolean mStopped; public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel, - LoaderResults results) { + ModelDelegate modelDelegate, LoaderResults results) { mApp = app; mBgAllAppsList = bgAllAppsList; mBgDataModel = dataModel; + mModelDelegate = modelDelegate; mResults = results; mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class); @@ -767,6 +769,9 @@ public class LoaderTask implements Runnable { IOUtils.closeSilently(c); } + // Load delegate items + mModelDelegate.loadItems(); + // Break early if we've stopped loading if (mStopped) { mBgDataModel.clear(); diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java new file mode 100644 index 0000000000..ce4eed5bbd --- /dev/null +++ b/src/com/android/launcher3/model/ModelDelegate.java @@ -0,0 +1,75 @@ +/* + * 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.PackageManagerHelper.hasShortcutsPermission; + +import android.content.Context; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.util.ResourceBasedOverride; + +/** + * Class to extend LauncherModel functionality to provide extra data + */ +public class ModelDelegate implements ResourceBasedOverride { + + /** + * Creates and initializes a new instance of the delegate + */ + public static ModelDelegate newInstance( + Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) { + ModelDelegate delegate = Overrides.getObject( + ModelDelegate.class, context, R.string.model_delegate_class); + + delegate.mApp = app; + delegate.mAppsList = appsList; + delegate.mDataModel = dataModel; + return delegate; + } + + protected LauncherAppState mApp; + protected AllAppsList mAppsList; + protected BgDataModel mDataModel; + + public ModelDelegate() { } + + /** + * Called periodically to validate and update any data + */ + @WorkerThread + public void validateData() { + if (hasShortcutsPermission(mApp.getContext()) + != mAppsList.hasShortcutHostPermission()) { + mApp.getModel().forceReload(); + } + } + + /** + * Load delegate items if any in the data model + */ + @WorkerThread + public void loadItems() { } + + /** + * Called when the delegate is no loner needed + */ + @WorkerThread + public void destroy() { } +} diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 858e183ab7..201218b79a 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -53,7 +53,6 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; import com.android.launcher3.common.WidgetUtils; -import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.tapl.LauncherInstrumentation; @@ -272,8 +271,6 @@ public abstract class AbstractLauncherUiTest { } mLauncherPid = 0; - // Disable app tracker - AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); mTargetContext = InstrumentationRegistry.getTargetContext(); mTargetPackage = mTargetContext.getPackageName();