Merge "Moving workspace bind logic into a separate class." into ub-launcher3-dorval-polish
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b5ca301..ee06d9e 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -60,6 +60,7 @@
import com.android.launcher3.model.ExtendedModelTask;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.model.LoaderCursor;
+import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageItemInfo;
@@ -90,13 +91,11 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
@@ -112,9 +111,6 @@
static final String TAG = "Launcher.Model";
- private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
- private static final long INVALID_SCREEN_ID = -1L;
-
private final MainThreadExecutor mUiExecutor = new MainThreadExecutor();
@Thunk final LauncherAppState mApp;
@Thunk final Object mLock = new Object();
@@ -531,13 +527,21 @@
// If there is already one running, tell it to stop.
stopLoaderLocked();
- mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
+ LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
+ mBgAllAppsList, synchronousBindPage, mCallbacks);
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mModelLoaded && !mIsLoaderTaskRunning) {
- mLoaderTask.runBindSynchronousPage(synchronousBindPage);
+
+ // Divide the set of loaded items into those that we are binding synchronously,
+ // and everything else that is to be bound normally (asynchronously).
+ loaderResults.bindWorkspace();
+ // For now, continue posting the binding of AllApps as there are other
+ // issues that arise from that.
+ loaderResults.bindAllApps();
+ loaderResults.bindDeepShortcuts();
return true;
} else {
- sWorkerThread.setPriority(Thread.NORM_PRIORITY);
+ mLoaderTask = new LoaderTask(mApp.getContext(), loaderResults);
sWorker.post(mLoaderTask);
}
}
@@ -593,13 +597,13 @@
*/
private class LoaderTask implements Runnable {
private Context mContext;
- private int mPageToBindFirst;
+ private final LoaderResults mResults;
private boolean mStopped;
- LoaderTask(Context context, int pageToBindFirst) {
+ LoaderTask(Context context, LoaderResults results) {
mContext = context;
- mPageToBindFirst = pageToBindFirst;
+ mResults = results;
}
private void waitForIdle() {
@@ -614,35 +618,6 @@
}
}
- void runBindSynchronousPage(int synchronousBindPage) {
- if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
- // Ensure that we have a valid page index to load synchronously
- throw new RuntimeException("Should not call runBindSynchronousPage() without " +
- "valid page index");
- }
- if (!mModelLoaded) {
- // Ensure that we don't try and bind a specified page when the pages have not been
- // loaded already (we should load everything asynchronously in that case)
- throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
- }
- synchronized (mLock) {
- if (mIsLoaderTaskRunning) {
- // Ensure that we are never running the background loading at this point since
- // we also touch the background collections
- throw new RuntimeException("Error! Background loading is already running");
- }
- }
-
- // Divide the set of loaded items into those that we are binding synchronously, and
- // everything else that is to be bound normally (asynchronously).
- bindWorkspace(synchronousBindPage);
- // XXX: For now, continue posting the binding of AllApps as there are other issues that
- // arise from that.
- onlyBindAllApps();
-
- bindDeepShortcuts();
- }
-
private void verifyNotStopped() throws CancellationException {
synchronized (LoaderTask.this) {
if (mStopped) {
@@ -666,7 +641,7 @@
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
- bindWorkspace(mPageToBindFirst);
+ mResults.bindWorkspace();
// Take a break
if (DEBUG_LOADERS) {
@@ -681,8 +656,12 @@
if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
loadAllApps();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Binding all apps");
verifyNotStopped();
- if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
+ mResults.bindAllApps();
+
+ verifyNotStopped();
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2.3: Update icon cache");
updateIconCache();
// Take a break
@@ -700,7 +679,7 @@
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
- bindDeepShortcuts();
+ mResults.bindDeepShortcuts();
// Take a break
if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
@@ -741,36 +720,6 @@
}
}
- /**
- * Gets the callbacks object. If we've been stopped, or if the launcher object
- * has somehow been garbage collected, return null instead. Pass in the Callbacks
- * object that was around when the deferred message was scheduled, and if there's
- * a new Callbacks object around then also return null. This will save us from
- * calling onto it with data that will be ignored.
- */
- Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
- synchronized (mLock) {
- if (mStopped) {
- return null;
- }
-
- if (mCallbacks == null) {
- return null;
- }
-
- final Callbacks callbacks = mCallbacks.get();
- if (callbacks != oldCallbacks) {
- return null;
- }
- if (callbacks == null) {
- Log.w(TAG, "no mCallbacks");
- return null;
- }
-
- return callbacks;
- }
- }
-
private void loadWorkspace() {
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Loading Workspace");
@@ -1313,291 +1262,6 @@
}
}
- /** Filters the set of items who are directly or indirectly (via another container) on the
- * specified screen. */
- private void filterCurrentWorkspaceItems(long currentScreenId,
- ArrayList<ItemInfo> allWorkspaceItems,
- ArrayList<ItemInfo> currentScreenItems,
- ArrayList<ItemInfo> otherScreenItems) {
- // Purge any null ItemInfos
- Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
- while (iter.hasNext()) {
- ItemInfo i = iter.next();
- if (i == null) {
- iter.remove();
- }
- }
-
- // Order the set of items by their containers first, this allows use to walk through the
- // list sequentially, build up a list of containers that are in the specified screen,
- // as well as all items in those containers.
- Set<Long> itemsOnScreen = new HashSet<Long>();
- Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
- @Override
- public int compare(ItemInfo lhs, ItemInfo rhs) {
- return Utilities.longCompare(lhs.container, rhs.container);
- }
- });
- for (ItemInfo info : allWorkspaceItems) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (info.screenId == currentScreenId) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- if (itemsOnScreen.contains(info.container)) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- }
- }
- }
-
- /** Filters the set of widgets which are on the specified screen. */
- private void filterCurrentAppWidgets(long currentScreenId,
- ArrayList<LauncherAppWidgetInfo> appWidgets,
- ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
- ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
-
- for (LauncherAppWidgetInfo widget : appWidgets) {
- if (widget == null) continue;
- if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- widget.screenId == currentScreenId) {
- currentScreenWidgets.add(widget);
- } else {
- otherScreenWidgets.add(widget);
- }
- }
- }
-
- /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
- * right) */
- private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
- final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
- final int screenCols = profile.numColumns;
- final int screenCellCount = profile.numColumns * profile.numRows;
- Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
- @Override
- public int compare(ItemInfo lhs, ItemInfo rhs) {
- if (lhs.container == rhs.container) {
- // Within containers, order by their spatial position in that container
- switch ((int) lhs.container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
- long lr = (lhs.screenId * screenCellCount +
- lhs.cellY * screenCols + lhs.cellX);
- long rr = (rhs.screenId * screenCellCount +
- rhs.cellY * screenCols + rhs.cellX);
- return Utilities.longCompare(lr, rr);
- }
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
- // We currently use the screen id as the rank
- return Utilities.longCompare(lhs.screenId, rhs.screenId);
- }
- default:
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
- throw new RuntimeException("Unexpected container type when " +
- "sorting workspace items.");
- }
- return 0;
- }
- } else {
- // Between containers, order by hotseat, desktop
- return Utilities.longCompare(lhs.container, rhs.container);
- }
- }
- });
- }
-
- private void bindWorkspaceScreens(final Callbacks oldCallbacks,
- final ArrayList<Long> orderedScreens) {
- final Runnable r = new Runnable() {
- @Override
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.bindScreens(orderedScreens);
- }
- }
- };
- mUiExecutor.execute(r);
- }
-
- private void bindWorkspaceItems(final Callbacks oldCallbacks,
- final ArrayList<ItemInfo> workspaceItems,
- final ArrayList<LauncherAppWidgetInfo> appWidgets,
- final Executor executor) {
-
- // Bind the workspace items
- int N = workspaceItems.size();
- for (int i = 0; i < N; i += ITEMS_CHUNK) {
- final int start = i;
- final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
- final Runnable r = new Runnable() {
- @Override
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.bindItems(workspaceItems, start, start+chunkSize,
- false);
- }
- }
- };
- executor.execute(r);
- }
-
- // Bind the widgets, one at a time
- N = appWidgets.size();
- for (int i = 0; i < N; i++) {
- final LauncherAppWidgetInfo widget = appWidgets.get(i);
- final Runnable r = new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.bindAppWidget(widget);
- }
- }
- };
- executor.execute(r);
- }
- }
-
- /**
- * Binds all loaded data to actual views on the main thread.
- */
- private void bindWorkspace(int synchronizeBindPage) {
- final long t = SystemClock.uptimeMillis();
- Runnable r;
-
- // Don't use these two variables in any of the callback runnables.
- // Otherwise we hold a reference to them.
- final Callbacks oldCallbacks = mCallbacks.get();
- if (oldCallbacks == null) {
- // This launcher has exited and nobody bothered to tell us. Just bail.
- Log.w(TAG, "LoaderTask running with no launcher");
- return;
- }
-
- // Save a copy of all the bg-thread collections
- ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
- ArrayList<Long> orderedScreenIds = new ArrayList<>();
-
- synchronized (sBgDataModel) {
- workspaceItems.addAll(sBgDataModel.workspaceItems);
- appWidgets.addAll(sBgDataModel.appWidgets);
- orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
- }
-
- final int currentScreen;
- {
- int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
- ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
- if (currScreen >= orderedScreenIds.size()) {
- // There may be no workspace screens (just hotseat items and an empty page).
- currScreen = PagedView.INVALID_RESTORE_PAGE;
- }
- currentScreen = currScreen;
- }
- final boolean validFirstPage = currentScreen >= 0;
- final long currentScreenId =
- validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
-
- // Separate the items that are on the current screen, and all the other remaining items
- ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
- ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
-
- filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
- otherWorkspaceItems);
- filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
- otherAppWidgets);
- sortWorkspaceItemsSpatially(currentWorkspaceItems);
- sortWorkspaceItemsSpatially(otherWorkspaceItems);
-
- // Tell the workspace that we're about to start binding items
- r = new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.clearPendingBinds();
- callbacks.startBinding();
- }
- }
- };
- mUiExecutor.execute(r);
-
- bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
-
- Executor mainExecutor = mUiExecutor;
- // Load items on the current page.
- bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, 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).
- // This ensures that the first screen is immediately visible (eg. during rotation)
- // In case of !validFirstPage, bind all pages one after other.
- final Executor deferredExecutor =
- validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor;
-
- mainExecutor.execute(new Runnable() {
- @Override
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.finishFirstPageBind(
- validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
- }
- }
- });
-
- bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
-
- // Tell the workspace that we're done binding items
- r = new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.finishBindingItems();
- }
-
- // If we're profiling, ensure this is the last thing in the queue.
- if (DEBUG_LOADERS) {
- Log.d(TAG, "bound workspace in "
- + (SystemClock.uptimeMillis()-t) + "ms");
- }
-
- }
- };
- deferredExecutor.execute(r);
-
- if (validFirstPage) {
- r = new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- // We are loading synchronously, which means, some of the pages will be
- // bound after first draw. Inform the callbacks that page binding is
- // not complete, and schedule the remaining pages.
- if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
- callbacks.onPageBoundSynchronously(currentScreen);
- }
- callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
- }
- }
- };
- mUiExecutor.execute(r);
- }
- }
-
private void updateIconCache() {
// Ignore packages which have a promise icon.
HashSet<String> packagesToIgnore = new HashSet<>();
@@ -1619,44 +1283,9 @@
mIconCache.updateDbIcons(packagesToIgnore);
}
- private void onlyBindAllApps() {
- final Callbacks oldCallbacks = mCallbacks.get();
- if (oldCallbacks == null) {
- // This launcher has exited and nobody bothered to tell us. Just bail.
- Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
- return;
- }
-
- // shallow copy
- @SuppressWarnings("unchecked")
- final ArrayList<AppInfo> list
- = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
- Runnable r = new Runnable() {
- public void run() {
- final long t = SystemClock.uptimeMillis();
- final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.bindAllApplications(list);
- }
- if (DEBUG_LOADERS) {
- Log.d(TAG, "bound all " + list.size() + " apps from cache in "
- + (SystemClock.uptimeMillis() - t) + "ms");
- }
- }
- };
- mUiExecutor.execute(r);
- }
-
private void loadAllApps() {
final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
- final Callbacks oldCallbacks = mCallbacks.get();
- if (oldCallbacks == null) {
- // This launcher has exited and nobody bothered to tell us. Just bail.
- Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
- return;
- }
-
final List<UserHandle> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
@@ -1695,30 +1324,9 @@
}
}
- // Huh? Shouldn't this be inside the Runnable below?
- final ArrayList<AppInfo> added = mBgAllAppsList.added;
- mBgAllAppsList.added = new ArrayList<AppInfo>();
-
-
- // Post callback on main thread
- mUiExecutor.execute(new Runnable() {
- public void run() {
-
- final long bindTime = SystemClock.uptimeMillis();
- final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
- callbacks.bindAllApplications(added);
- if (DEBUG_LOADERS) {
- Log.d(TAG, "bound " + added.size() + " apps in "
- + (SystemClock.uptimeMillis() - bindTime) + "ms");
- }
- } else {
- Log.i(TAG, "not binding apps: no Launcher activity");
- }
- }
- });
+ mBgAllAppsList.added = new ArrayList<>();
if (DEBUG_LOADERS) {
- Log.d(TAG, "Icons processed in "
+ Log.d(TAG, "All apps loaded in in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
}
}
@@ -1739,21 +1347,6 @@
}
}
- public void bindDeepShortcuts() {
- final MultiHashMap<ComponentKey, String> shortcutMapCopy =
- sBgDataModel.deepShortcutMap.clone();
- Runnable r = new Runnable() {
- @Override
- public void run() {
- Callbacks callbacks = getCallback();
- if (callbacks != null) {
- callbacks.bindDeepShortcutMap(shortcutMapCopy);
- }
- }
- };
- mUiExecutor.execute(r);
- }
-
/**
* Refreshes the cached shortcuts if the shortcut permission has changed.
* Current implementation simply reloads the workspace, but it can be optimized to
@@ -1925,15 +1518,6 @@
}
/**
- * @return {@link FolderInfo} if its already loaded.
- */
- public FolderInfo findFolderById(Long folderId) {
- synchronized (sBgDataModel) {
- return sBgDataModel.folders.get(folderId);
- }
- }
-
- /**
* @return the looper for the worker thread which can be used to start background tasks.
*/
public static Looper getWorkerLooper() {
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
new file mode 100644
index 0000000..61fd356
--- /dev/null
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 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 android.util.Log;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class to handle results of {@link com.android.launcher3.LauncherModel.LoaderTask}.
+ */
+public class LoaderResults {
+
+ private static final String TAG = "LoaderResults";
+ private static final long INVALID_SCREEN_ID = -1L;
+ private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
+
+ private final Executor mUiExecutor;
+
+ private final LauncherAppState mApp;
+ private final BgDataModel mBgDataModel;
+ private final AllAppsList mBgAllAppsList;
+ private final int mPageToBindFirst;
+ private final WeakReference<Callbacks> mCallbacks;
+
+ public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+ AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
+ mUiExecutor = new MainThreadExecutor();
+ mApp = app;
+ mBgDataModel = dataModel;
+ mBgAllAppsList = allAppsList;
+ mPageToBindFirst = pageToBindFirst;
+ mCallbacks = callbacks == null ? new WeakReference<Callbacks>(null) : callbacks;
+ }
+
+ /**
+ * Binds all loaded data to actual views on the main thread.
+ */
+ public void bindWorkspace() {
+ Runnable r;
+
+ Callbacks callbacks = mCallbacks.get();
+ // Don't use these two variables in any of the callback runnables.
+ // Otherwise we hold a reference to them.
+ if (callbacks == null) {
+ // This launcher has exited and nobody bothered to tell us. Just bail.
+ Log.w(TAG, "LoaderTask running with no launcher");
+ return;
+ }
+
+ // Save a copy of all the bg-thread collections
+ ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+ final ArrayList<Long> orderedScreenIds = new ArrayList<>();
+
+ synchronized (mBgDataModel) {
+ workspaceItems.addAll(mBgDataModel.workspaceItems);
+ appWidgets.addAll(mBgDataModel.appWidgets);
+ orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
+ }
+
+ final int currentScreen;
+ {
+ int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE
+ ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen();
+ if (currScreen >= orderedScreenIds.size()) {
+ // There may be no workspace screens (just hotseat items and an empty page).
+ currScreen = PagedView.INVALID_RESTORE_PAGE;
+ }
+ currentScreen = currScreen;
+ }
+ final boolean validFirstPage = currentScreen >= 0;
+ final long currentScreenId =
+ validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
+
+ // Separate the items that are on the current screen, and all the other remaining items
+ ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+ ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+
+ filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
+ otherWorkspaceItems);
+ filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
+ otherAppWidgets);
+ sortWorkspaceItemsSpatially(currentWorkspaceItems);
+ sortWorkspaceItemsSpatially(otherWorkspaceItems);
+
+ // Tell the workspace that we're about to start binding items
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.clearPendingBinds();
+ callbacks.startBinding();
+ }
+ }
+ };
+ mUiExecutor.execute(r);
+
+ // Bind workspace screens
+ mUiExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindScreens(orderedScreenIds);
+ }
+ }
+ });
+
+ Executor mainExecutor = mUiExecutor;
+ // Load items on the current page.
+ bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, 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).
+ // This ensures that the first screen is immediately visible (eg. during rotation)
+ // In case of !validFirstPage, bind all pages one after other.
+ final Executor deferredExecutor =
+ validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor;
+
+ mainExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.finishFirstPageBind(
+ validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
+ }
+ }
+ });
+
+ bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
+
+ // Tell the workspace that we're done binding items
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.finishBindingItems();
+ }
+ }
+ };
+ deferredExecutor.execute(r);
+
+ if (validFirstPage) {
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ // We are loading synchronously, which means, some of the pages will be
+ // bound after first draw. Inform the callbacks that page binding is
+ // not complete, and schedule the remaining pages.
+ if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
+ callbacks.onPageBoundSynchronously(currentScreen);
+ }
+ callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
+ }
+ }
+ };
+ mUiExecutor.execute(r);
+ }
+ }
+
+
+ /** Filters the set of items who are directly or indirectly (via another container) on the
+ * specified screen. */
+ private void filterCurrentWorkspaceItems(long currentScreenId,
+ ArrayList<ItemInfo> allWorkspaceItems,
+ ArrayList<ItemInfo> currentScreenItems,
+ ArrayList<ItemInfo> otherScreenItems) {
+ // Purge any null ItemInfos
+ Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
+ while (iter.hasNext()) {
+ ItemInfo i = iter.next();
+ if (i == null) {
+ iter.remove();
+ }
+ }
+
+ // Order the set of items by their containers first, this allows use to walk through the
+ // list sequentially, build up a list of containers that are in the specified screen,
+ // as well as all items in those containers.
+ Set<Long> itemsOnScreen = new HashSet<Long>();
+ Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
+ @Override
+ public int compare(ItemInfo lhs, ItemInfo rhs) {
+ return Utilities.longCompare(lhs.container, rhs.container);
+ }
+ });
+ for (ItemInfo info : allWorkspaceItems) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (info.screenId == currentScreenId) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ otherScreenItems.add(info);
+ }
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ if (itemsOnScreen.contains(info.container)) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ otherScreenItems.add(info);
+ }
+ }
+ }
+ }
+
+ /** Filters the set of widgets which are on the specified screen. */
+ private void filterCurrentAppWidgets(long currentScreenId,
+ ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
+ ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
+
+ for (LauncherAppWidgetInfo widget : appWidgets) {
+ if (widget == null) continue;
+ if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ widget.screenId == currentScreenId) {
+ currentScreenWidgets.add(widget);
+ } else {
+ otherScreenWidgets.add(widget);
+ }
+ }
+ }
+
+ /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
+ * right) */
+ private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
+ final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
+ final int screenCols = profile.numColumns;
+ final int screenCellCount = profile.numColumns * profile.numRows;
+ Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
+ @Override
+ public int compare(ItemInfo lhs, ItemInfo rhs) {
+ if (lhs.container == rhs.container) {
+ // Within containers, order by their spatial position in that container
+ switch ((int) lhs.container) {
+ case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+ long lr = (lhs.screenId * screenCellCount +
+ lhs.cellY * screenCols + lhs.cellX);
+ long rr = (rhs.screenId * screenCellCount +
+ rhs.cellY * screenCols + rhs.cellX);
+ return Utilities.longCompare(lr, rr);
+ }
+ case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+ // We currently use the screen id as the rank
+ return Utilities.longCompare(lhs.screenId, rhs.screenId);
+ }
+ default:
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ throw new RuntimeException("Unexpected container type when " +
+ "sorting workspace items.");
+ }
+ return 0;
+ }
+ } else {
+ // Between containers, order by hotseat, desktop
+ return Utilities.longCompare(lhs.container, rhs.container);
+ }
+ }
+ });
+ }
+
+ private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
+ final ArrayList<LauncherAppWidgetInfo> appWidgets,
+ final Executor executor) {
+
+ // Bind the workspace items
+ int N = workspaceItems.size();
+ for (int i = 0; i < N; i += ITEMS_CHUNK) {
+ final int start = i;
+ final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
+ final Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindItems(workspaceItems, start, start+chunkSize, false);
+ }
+ }
+ };
+ executor.execute(r);
+ }
+
+ // Bind the widgets, one at a time
+ N = appWidgets.size();
+ for (int i = 0; i < N; i++) {
+ final LauncherAppWidgetInfo widget = appWidgets.get(i);
+ final Runnable r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindAppWidget(widget);
+ }
+ }
+ };
+ executor.execute(r);
+ }
+ }
+
+ public void bindDeepShortcuts() {
+ final MultiHashMap<ComponentKey, String> shortcutMapCopy;
+ synchronized (mBgDataModel) {
+ shortcutMapCopy = mBgDataModel.deepShortcutMap.clone();
+ }
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindDeepShortcutMap(shortcutMapCopy);
+ }
+ }
+ };
+ mUiExecutor.execute(r);
+ }
+
+
+ public void bindAllApps() {
+ // shallow copy
+ @SuppressWarnings("unchecked")
+ final ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
+
+ Runnable r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindAllApplications(list);
+ }
+ }
+ };
+ mUiExecutor.execute(r);
+ }
+}