Load All Apps before the workspace if All Apps is showing.

This change reorganizes the increasingly Byzantine loader
thread so that the order of (workspace, allapps) can be
reversed if the user is currently looking at the all apps
view. The perceptual improvement in performance is huge if
you change the Launcher's orientation while All Apps is
visible (which forces a reload/bind of everything); now that
AA doesn't have to wait behind the workspace it
(specifically, its first batch of icons) appears much, much
faster.

Bug: 2722977
Change-Id: I07a9afd5f1cff4019f2640b082872176ba0a887e
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 52f1224..62aaa8b 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -49,6 +49,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcelable;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.LiveFolders;
 import android.text.Selection;
@@ -91,10 +92,10 @@
 public final class Launcher extends Activity
         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
     static final String TAG = "Launcher";
-    static final boolean LOGD = false;
+    static final boolean LOGD = true;
 
     static final boolean PROFILE_STARTUP = false;
-    static final boolean PROFILE_ROTATE = false;
+    static final boolean DEBUG_WIDGETS = false;
     static final boolean DEBUG_USER_INTERFACE = false;
 
     private static final int WALLPAPER_SCREENS_SPAN = 2;
@@ -586,10 +587,6 @@
         // Flag the loader to stop early before switching
         mModel.stopLoader();
         mAllAppsGrid.surrender();
-
-        if (PROFILE_ROTATE) {
-            android.os.Debug.startMethodTracing("/sdcard/launcher-rotate");
-        }
         return Boolean.TRUE;
     }
 
@@ -1885,8 +1882,9 @@
         }
     }
 
-    boolean isAllAppsVisible() {
-        return mAllAppsGrid.isVisible();
+    // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
+    public boolean isAllAppsVisible() {
+        return (mAllAppsGrid != null) ? mAllAppsGrid.isVisible() : false;
     }
 
     // AllAppsView.Watcher
@@ -2185,10 +2183,18 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindAppWidget(LauncherAppWidgetInfo item) {
+        final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
+        if (DEBUG_WIDGETS) {
+            Log.d(TAG, "bindAppWidget: " + item);
+        }
         final Workspace workspace = mWorkspace;
 
         final int appWidgetId = item.appWidgetId;
         final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+        if (DEBUG_WIDGETS) {
+            Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
+        }
+
         item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
 
         item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
@@ -2200,6 +2206,11 @@
         workspace.requestLayout();
 
         mDesktopItems.add(item);
+
+        if (DEBUG_WIDGETS) {
+            Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
+                    + (SystemClock.uptimeMillis()-start) + "ms");
+        }
     }
 
     /**
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index d2cebc0..96ceb74 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -60,6 +60,7 @@
  */
 public class LauncherModel extends BroadcastReceiver {
     static final boolean DEBUG_LOADERS = false;
+    static final boolean PROFILE_LOADERS = false;
     static final String TAG = "Launcher.Model";
 
     private int mBatchSize; // 0 is all apps at once
@@ -90,6 +91,7 @@
         public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
         public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
         public void bindAppsRemoved(ArrayList<ApplicationInfo> apps);
+        public boolean isAllAppsVisible();
     }
 
     LauncherModel(LauncherApplication app, IconCache iconCache) {
@@ -439,6 +441,7 @@
                 if (DEBUG_LOADERS) {
                     Log.d(TAG, "startLoader isLaunching=" + isLaunching);
                 }
+
                 // Don't bother to start the thread if we know it's not going to do anything
                 if (mCallbacks != null && mCallbacks.get() != null) {
                     LoaderThread oldThread = mLoaderThread;
@@ -486,7 +489,7 @@
             private Thread mWaitThread;
             private boolean mIsLaunching;
             private boolean mStopped;
-            private boolean mWorkspaceDoneBinding;
+            private boolean mLoadAndBindStepFinished;
 
             LoaderThread(Context context, Thread waitThread, boolean isLaunching) {
                 mContext = context;
@@ -520,16 +523,7 @@
                 }
             }
 
-            public void run() {
-                waitForOtherThread();
-
-                // Elevate priority when Home launches for the first time to avoid
-                // starving at boot time. Staring at a blank home is not cool.
-                synchronized (mLock) {
-                    android.os.Process.setThreadPriority(mIsLaunching
-                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
-                }
-
+            private void loadAndBindWorkspace() {
                 // Load the workspace only if it's dirty.
                 int workspaceSeq;
                 boolean workspaceDirty;
@@ -543,6 +537,10 @@
                 synchronized (mLock) {
                     // If we're not stopped, and nobody has incremented mWorkspaceSeq.
                     if (mStopped) {
+                        if (PROFILE_LOADERS) {
+                            android.os.Debug.stopMethodTracing();
+                        }
+
                         return;
                     }
                     if (workspaceSeq == mWorkspaceSeq) {
@@ -552,44 +550,9 @@
 
                 // Bind the workspace
                 bindWorkspace();
-                
-                // Wait until the either we're stopped or the other threads are done.
-                // This way we don't start loading all apps until the workspace has settled
-                // down.
-                synchronized (LoaderThread.this) {
-                    mHandler.postIdle(new Runnable() {
-                            public void run() {
-                                synchronized (LoaderThread.this) {
-                                    mWorkspaceDoneBinding = true;
-                                    if (DEBUG_LOADERS) {
-                                        Log.d(TAG, "done with workspace");
-                                        }
-                                    LoaderThread.this.notify();
-                                }
-                            }
-                        });
-                    if (DEBUG_LOADERS) {
-                        Log.d(TAG, "waiting to be done with workspace");
-                    }
-                    while (!mStopped && !mWorkspaceDoneBinding) {
-                        try {
-                            this.wait();
-                        } catch (InterruptedException ex) {
-                            // Ignore
-                        }
-                    }
-                    if (DEBUG_LOADERS) {
-                        Log.d(TAG, "done waiting to be done with workspace");
-                    }
-                }
+            }
 
-                // Whew! Hard work done.
-                synchronized (mLock) {
-                    if (mIsLaunching) {
-                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                    }
-                }
-
+            private void loadAndBindAllAppsIfDirty() {
                 // Load all apps if they're dirty
                 int allAppsSeq;
                 boolean allAppsDirty;
@@ -613,6 +576,85 @@
                         mLastAllAppsSeq = mAllAppsSeq;
                     }
                 }
+            }
+
+            private void waitForIdle() {
+                // Wait until the either we're stopped or the other threads are done.
+                // This way we don't start loading all apps until the workspace has settled
+                // down.
+                synchronized (LoaderThread.this) {
+                    final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+                    mHandler.postIdle(new Runnable() {
+                            public void run() {
+                                synchronized (LoaderThread.this) {
+                                    mLoadAndBindStepFinished = true;
+                                    if (DEBUG_LOADERS) {
+                                        Log.d(TAG, "done with previous binding step");
+                                    }
+                                    LoaderThread.this.notify();
+                                }
+                            }
+                        });
+
+                    while (!mStopped && !mLoadAndBindStepFinished) {
+                        try {
+                            this.wait();
+                        } catch (InterruptedException ex) {
+                            // Ignore
+                        }
+                    }
+                    if (DEBUG_LOADERS) {
+                        Log.d(TAG, "waited "
+                                + (SystemClock.uptimeMillis()-workspaceWaitTime) 
+                                + "ms for previous step to finish binding");
+                    }
+                }
+            }
+
+            public void run() {
+                waitForOtherThread();
+
+                // Optimize for end-user experience: if the Launcher is up and // running with the
+                // All Apps interface in the foreground, load All Apps first. Otherwise, load the
+                // workspace first (default).
+                final Callbacks cbk = mCallbacks.get();
+                final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true;
+
+                // Elevate priority when Home launches for the first time to avoid
+                // starving at boot time. Staring at a blank home is not cool.
+                synchronized (mLock) {
+                    android.os.Process.setThreadPriority(mIsLaunching
+                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
+                }
+
+                if (PROFILE_LOADERS) {
+                    android.os.Debug.startMethodTracing("/sdcard/launcher-loaders");
+                }
+                
+                if (loadWorkspaceFirst) {
+                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
+                    loadAndBindWorkspace();
+                } else {
+                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
+                    loadAndBindAllAppsIfDirty();
+                }
+
+                // Whew! Hard work done.
+                synchronized (mLock) {
+                    if (mIsLaunching) {
+                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    }
+                }
+
+                // second step
+                if (loadWorkspaceFirst) {
+                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
+                    loadAndBindAllAppsIfDirty();
+                } else {
+                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
+                    loadAndBindWorkspace();
+                }
 
                 // Clear out this reference, otherwise we end up holding it until all of the
                 // callback runnables are done.
@@ -624,6 +666,10 @@
                     mLoaderThread = null;
                 }
 
+                if (PROFILE_LOADERS) {
+                    android.os.Debug.stopMethodTracing();
+                }
+
                 // Trigger a gc to try to clean up after the stuff is done, since the
                 // renderscript allocations aren't charge to the java heap.
                 mHandler.post(new Runnable() {
@@ -672,10 +718,6 @@
 
             // check & update map of what's occupied; used to discard overlapping/invalid items
             private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) {
-                if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                    return true;
-                }
-
                 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
                     for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
                         if (occupied[item.screen][x][y] != null) {
@@ -697,7 +739,7 @@
             }
 
             private void loadWorkspace() {
-                long t = SystemClock.uptimeMillis();
+                final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
                 final Context context = mContext;
                 final ContentResolver contentResolver = context.getContentResolver();
@@ -1028,7 +1070,7 @@
                     }
                 });
                 // Wait until the queue goes empty.
-                mHandler.postIdle(new Runnable() {
+                mHandler.post(new Runnable() {
                     public void run() {
                         if (DEBUG_LOADERS) {
                             Log.d(TAG, "Going to start binding widgets soon.");
@@ -1086,9 +1128,6 @@
                             Log.d(TAG, "bound workspace in "
                                 + (SystemClock.uptimeMillis()-t) + "ms");
                         }
-                        if (Launcher.PROFILE_ROTATE) {
-                            android.os.Debug.stopMethodTracing();
-                        }
                     }
                 });
             }
@@ -1152,7 +1191,7 @@
                                     new ResolveInfo.DisplayNameComparator(packageManager));
                             if (DEBUG_LOADERS) {
                                 Log.d(TAG, "sort took "
-                                        + (SystemClock.uptimeMillis()-qiaTime) + "ms");
+                                        + (SystemClock.uptimeMillis()-sortTime) + "ms");
                             }
                         }
 
@@ -1218,7 +1257,7 @@
                 Log.d(TAG, "mLoader.mLoaderThread.mWaitThread=" + mWaitThread);
                 Log.d(TAG, "mLoader.mLoaderThread.mIsLaunching=" + mIsLaunching);
                 Log.d(TAG, "mLoader.mLoaderThread.mStopped=" + mStopped);
-                Log.d(TAG, "mLoader.mLoaderThread.mWorkspaceDoneBinding=" + mWorkspaceDoneBinding);
+                Log.d(TAG, "mLoader.mLoaderThread.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
             }
         }