Merge "Improve workspace's scrolling performance." into froyo
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 52f1224..13a39a3 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;
@@ -94,7 +95,7 @@
     static final boolean LOGD = false;
 
     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;
@@ -246,11 +247,6 @@
             android.os.Debug.stopMethodTracing();
         }
 
-        // We have a new AllAppsView, we need to re-bind everything, and it could have
-        // changed in our absence.
-        mModel.setAllAppsDirty();
-        mModel.setWorkspaceDirty();
-
         if (!mRestoring) {
             mModel.startLoader(this, true);
         }
@@ -586,10 +582,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;
     }
 
@@ -1859,7 +1851,6 @@
 
                 if (mWorkspaceLoading) {
                     lockAllApps();
-                    mModel.setWorkspaceDirty();
                     mModel.startLoader(Launcher.this, false);
                 } else {
                     final FolderIcon folderIcon = (FolderIcon)
@@ -1869,7 +1860,6 @@
                         getWorkspace().requestLayout();
                     } else {
                         lockAllApps();
-                        mModel.setWorkspaceDirty();
                         mWorkspaceLoading = true;
                         mModel.startLoader(Launcher.this, false);
                     }
@@ -1885,8 +1875,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 +2176,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 +2199,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/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java
index be448a8..eda92d9 100644
--- a/src/com/android/launcher2/LauncherApplication.java
+++ b/src/com/android/launcher2/LauncherApplication.java
@@ -73,8 +73,6 @@
     private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
         @Override
         public void onChange(boolean selfChange) {
-            // TODO: lockAllApps();
-            mModel.setWorkspaceDirty();
             mModel.startLoader(LauncherApplication.this, false);
         }
     };
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index d2cebc0..ce26f37 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
@@ -70,6 +71,12 @@
     private DeferredHandler mHandler = new DeferredHandler();
     private Loader mLoader = new Loader();
 
+    // We start off with everything not loaded.  After that, we assume that
+    // our monitoring of the package manager provides all updates and we never
+    // need to do a requery.  These are only ever touched from the loader thread.
+    private boolean mWorkspaceLoaded;
+    private boolean mAllAppsLoaded;
+
     private boolean mBeforeFirstLoad = true; // only access this from main thread
     private WeakReference<Callbacks> mCallbacks;
 
@@ -90,6 +97,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) {
@@ -285,17 +293,6 @@
     }
 
     /**
-     * We pick up most of the changes to all apps.
-     */
-    public void setAllAppsDirty() {
-        mLoader.setAllAppsDirty();
-    }
-
-    public void setWorkspaceDirty() {
-        mLoader.setWorkspaceDirty();
-    }
-
-    /**
      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
      * ACTION_PACKAGE_CHANGED.
      */
@@ -396,8 +393,9 @@
                      if (packages == null || packages.length == 0) {
                          return;
                      }
-                     setAllAppsDirty();
-                     setWorkspaceDirty();
+                     synchronized (this) {
+                         mAllAppsLoaded = mWorkspaceLoaded = false;
+                     }
                      startLoader(context, false);
                 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
                      String packages[] = intent.getStringArrayExtra(
@@ -405,8 +403,9 @@
                      if (packages == null || packages.length == 0) {
                          return;
                      }
-                     setAllAppsDirty();
-                     setWorkspaceDirty();
+                     synchronized (this) {
+                         mAllAppsLoaded = mWorkspaceLoaded = false;
+                     }
                      startLoader(context, false);
                 }
             }
@@ -418,12 +417,6 @@
 
         private LoaderThread mLoaderThread;
 
-        private int mLastWorkspaceSeq = 0;
-        private int mWorkspaceSeq = 1;
-
-        private int mLastAllAppsSeq = 0;
-        private int mAllAppsSeq = 1;
-
         final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
         final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
         final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>();
@@ -439,6 +432,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;
@@ -463,18 +457,6 @@
             }
         }
 
-        public void setWorkspaceDirty() {
-            synchronized (mLock) {
-                mWorkspaceSeq++;
-            }
-        }
-
-        public void setAllAppsDirty() {
-            synchronized (mLock) {
-                mAllAppsSeq++;
-            }
-        }
-
         /**
          * Runnable for the thread that loads the contents of the launcher:
          *   - workspace icons
@@ -486,7 +468,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,9 +502,77 @@
                 }
             }
 
+            private void loadAndBindWorkspace() {
+                // Load the workspace
+
+                // Other other threads can unset mWorkspaceLoaded, so atomically set it,
+                // and then if they unset it, or we unset it because of mStopped, it will
+                // be unset.
+                boolean loaded;
+                synchronized (this) {
+                    loaded = mWorkspaceLoaded;
+                    mWorkspaceLoaded = true;
+                }
+
+                // For now, just always reload the workspace.  It's ~100 ms vs. the
+                // binding which takes many hundreds of ms.
+                // We can reconsider.
+                if (DEBUG_LOADERS) Log.d(TAG, "loadAndBindWorkspace loaded=" + loaded);
+                if (true || !loaded) {
+                    loadWorkspace();
+                    if (mStopped) {
+                        mWorkspaceLoaded = false;
+                        return;
+                    }
+                }
+
+                // Bind the workspace
+                bindWorkspace();
+            }
+
+            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) {
@@ -530,57 +580,16 @@
                             ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
                 }
 
-                // Load the workspace only if it's dirty.
-                int workspaceSeq;
-                boolean workspaceDirty;
-                synchronized (mLock) {
-                    workspaceSeq = mWorkspaceSeq;
-                    workspaceDirty = mWorkspaceSeq != mLastWorkspaceSeq;
+                if (PROFILE_LOADERS) {
+                    android.os.Debug.startMethodTracing("/sdcard/launcher-loaders");
                 }
-                if (workspaceDirty) {
-                    loadWorkspace();
-                }
-                synchronized (mLock) {
-                    // If we're not stopped, and nobody has incremented mWorkspaceSeq.
-                    if (mStopped) {
-                        return;
-                    }
-                    if (workspaceSeq == mWorkspaceSeq) {
-                        mLastWorkspaceSeq = mWorkspaceSeq;
-                    }
-                }
-
-                // 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");
-                    }
+                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");
+                    loadAndBindAllApps();
                 }
 
                 // Whew! Hard work done.
@@ -590,28 +599,13 @@
                     }
                 }
 
-                // Load all apps if they're dirty
-                int allAppsSeq;
-                boolean allAppsDirty;
-                synchronized (mLock) {
-                    allAppsSeq = mAllAppsSeq;
-                    allAppsDirty = mAllAppsSeq != mLastAllAppsSeq;
-                    if (DEBUG_LOADERS) {
-                        Log.d(TAG, "mAllAppsSeq=" + mAllAppsSeq
-                                + " mLastAllAppsSeq=" + mLastAllAppsSeq + " allAppsDirty");
-                    }
-                }
-                if (allAppsDirty) {
+                // second step
+                if (loadWorkspaceFirst) {
+                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                     loadAndBindAllApps();
-                }
-                synchronized (mLock) {
-                    // If we're not stopped, and nobody has incremented mAllAppsSeq.
-                    if (mStopped) {
-                        return;
-                    }
-                    if (allAppsSeq == mAllAppsSeq) {
-                        mLastAllAppsSeq = mAllAppsSeq;
-                    }
+                } 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
@@ -624,8 +618,12 @@
                     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.
+                // renderscript allocations aren't charged to the java heap.
                 mHandler.post(new Runnable() {
                         public void run() {
                             System.gc();
@@ -672,10 +670,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 +691,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 +1022,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,14 +1080,60 @@
                             Log.d(TAG, "bound workspace in "
                                 + (SystemClock.uptimeMillis()-t) + "ms");
                         }
-                        if (Launcher.PROFILE_ROTATE) {
-                            android.os.Debug.stopMethodTracing();
-                        }
                     }
                 });
             }
 
             private void loadAndBindAllApps() {
+                // Other other threads can unset mAllAppsLoaded, so atomically set it,
+                // and then if they unset it, or we unset it because of mStopped, it will
+                // be unset.
+                boolean loaded;
+                synchronized (this) {
+                    loaded = mAllAppsLoaded;
+                    mAllAppsLoaded = true;
+                }
+
+                if (DEBUG_LOADERS) Log.d(TAG, "loadAndBindAllApps loaded=" + loaded);
+                if (!loaded) {
+                    loadAllAppsByBatch();
+                    if (mStopped) {
+                        mAllAppsLoaded = false;
+                        return;
+                    }
+                } else {
+                    onlyBindAllApps();
+                }
+            }
+
+            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, "LoaderThread running with no launcher (onlyBindAllApps)");
+                    return;
+                }
+
+                // shallow copy
+                final ArrayList<ApplicationInfo> list
+                        = (ArrayList<ApplicationInfo>)mAllAppsList.data.clone();
+                mHandler.post(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");
+                        }
+                    }
+                });
+
+            }
+
+            private void loadAllAppsByBatch() {
                 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
                 // Don't use these two variables in any of the callback runnables.
@@ -1101,7 +1141,7 @@
                 final Callbacks oldCallbacks = mCallbacks.get();
                 if (oldCallbacks == null) {
                     // This launcher has exited and nobody bothered to tell us.  Just bail.
-                    Log.w(TAG, "LoaderThread running with no launcher (loadAndBindAllApps)");
+                    Log.w(TAG, "LoaderThread running with no launcher (loadAllAppsByBatch)");
                     return;
                 }
 
@@ -1152,7 +1192,7 @@
                                     new ResolveInfo.DisplayNameComparator(packageManager));
                             if (DEBUG_LOADERS) {
                                 Log.d(TAG, "sort took "
-                                        + (SystemClock.uptimeMillis()-qiaTime) + "ms");
+                                        + (SystemClock.uptimeMillis()-sortTime) + "ms");
                             }
                         }
 
@@ -1218,15 +1258,11 @@
                 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);
             }
         }
 
         public void dumpState() {
-            Log.d(TAG, "mLoader.mLastWorkspaceSeq=" + mLoader.mLastWorkspaceSeq);
-            Log.d(TAG, "mLoader.mWorkspaceSeq=" + mLoader.mWorkspaceSeq);
-            Log.d(TAG, "mLoader.mLastAllAppsSeq=" + mLoader.mLastAllAppsSeq);
-            Log.d(TAG, "mLoader.mAllAppsSeq=" + mLoader.mAllAppsSeq);
             Log.d(TAG, "mLoader.mItems size=" + mLoader.mItems.size());
             if (mLoaderThread != null) {
                 mLoaderThread.dumpState();