diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 7b7177e54e..7e9c5a332a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -958,6 +958,9 @@ public class Launcher extends BaseActivity } else if (mOnResumeState == State.WIDGETS) { showWidgetsView(false, false); } + if (mOnResumeState != State.APPS) { + tryAndUpdatePredictedApps(); + } mOnResumeState = State.NONE; mPaused = false; diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 0083d47f2a..ede3bea725 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -224,6 +224,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); + // Removes the animation that can occur when updating the predicted apps in place. + mAppsRecyclerView.getItemAnimator().setChangeDuration(0); if (FeatureFlags.LAUNCHER3_PHYSICS) { mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler); } diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 608e898ae8..ee2756f2cb 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -298,14 +298,74 @@ public class AlphabeticalAppsList { updateAdapterItems(); } + private List processPredictedAppComponents(List components) { + if (mComponentToAppMap.isEmpty()) { + // Apps have not been bound yet. + return Collections.emptyList(); + } + + List predictedApps = new ArrayList<>(); + for (ComponentKey ck : components) { + AppInfo info = mComponentToAppMap.get(ck); + if (info != null) { + predictedApps.add(info); + } else { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.e(TAG, "Predicted app not found: " + ck); + } + } + // Stop at the number of predicted apps + if (predictedApps.size() == mNumPredictedAppsPerRow) { + break; + } + } + return predictedApps; + } + /** - * Sets the current set of predicted apps. Since this can be called before we get the full set - * of applications, we should merge the results only in onAppsUpdated() which is idempotent. + * Sets the current set of predicted apps. + * + * This can be called before we get the full set of applications, we should merge the results + * only in onAppsUpdated() which is idempotent. + * + * 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); - onAppsUpdated(); + + List newPredictedApps = processPredictedAppComponents(apps); + // We only need to do work if any of the visible predicted apps have changed. + if (!newPredictedApps.equals(mPredictedApps)) { + if (newPredictedApps.size() == mPredictedApps.size()) { + swapInNewPredictedApps(newPredictedApps); + } else { + // We need to update the appIndex of all the items. + onAppsUpdated(); + } + } + } + + /** + * Swaps out the old predicted apps with the new predicted apps, in place. This optimization + * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged. + * + * Note: This should only be called if the # of predicted apps is the same. + * This method assumes that predicted apps are the first items in the adapter. + */ + private void swapInNewPredictedApps(List apps) { + mPredictedApps.clear(); + mPredictedApps.addAll(apps); + + int size = apps.size(); + for (int i = 0; i < size; ++i) { + AppInfo info = apps.get(i); + AdapterItem appItem = AdapterItem.asPredictedApp(i, "", info, i); + mAdapterItems.set(i, appItem); + mFilteredApps.set(i, info); + mAdapter.notifyItemChanged(i); + } } /** @@ -432,20 +492,7 @@ public class AlphabeticalAppsList { // Process the predicted app components mPredictedApps.clear(); if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { - for (ComponentKey ck : mPredictedAppComponents) { - AppInfo info = mComponentToAppMap.get(ck); - if (info != null) { - mPredictedApps.add(info); - } else { - if (FeatureFlags.IS_DOGFOOD_BUILD) { - Log.e(TAG, "Predicted app not found: " + ck); - } - } - // Stop at the number of predicted apps - if (mPredictedApps.size() == mNumPredictedAppsPerRow) { - break; - } - } + mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); if (!mPredictedApps.isEmpty()) { // Add a section for the predictions