Fix issue #3146784: ANR in manage applications

See the big comment.

Change-Id: I36164a6fcba58fe2b0e2b3aa216780d0d0e5ed37
diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java
index 814bbbf..6e9f2a2 100644
--- a/src/com/android/settings/applications/ApplicationsState.java
+++ b/src/com/android/settings/applications/ApplicationsState.java
@@ -39,6 +39,7 @@
 public class ApplicationsState {
     static final String TAG = "ApplicationsState";
     static final boolean DEBUG = false;
+    static final boolean DEBUG_LOCKING = false;
 
     public static interface Callbacks {
         public void onRunningStateChanged(boolean running);
@@ -66,15 +67,17 @@
                 .replaceAll("").toLowerCase();
     }
 
-    public static class AppEntry {
+    public static class SizeInfo {
+        long cacheSize;
+        long codeSize;
+        long dataSize;
+    }
+    
+    public static class AppEntry extends SizeInfo {
         final File apkFile;
         final long id;
         String label;
         long size;
-        
-        long cacheSize;
-        long codeSize;
-        long dataSize;
 
         boolean mounted;
         
@@ -334,9 +337,31 @@
                 Process.THREAD_PRIORITY_BACKGROUND);
         mThread.start();
         mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
+        
+        /**
+         * This is a trick to prevent the foreground thread from being delayed.
+         * The problem is that Dalvik monitors are initially spin locks, to keep
+         * them lightweight.  This leads to unfair contention -- Even though the
+         * background thread only holds the lock for a short amount of time, if
+         * it keeps running and locking again it can prevent the main thread from
+         * acquiring its lock for a long time...  sometimes even > 5 seconds
+         * (leading to an ANR).
+         * 
+         * Dalvik will promote a monitor to a "real" lock if it detects enough
+         * contention on it.  It doesn't figure this out fast enough for us
+         * here, though, so this little trick will force it to turn into a real
+         * lock immediately.
+         */
+        synchronized (mEntriesMap) {
+            try {
+                mEntriesMap.wait(1);
+            } catch (InterruptedException e) {
+            }
+        }
     }
 
     void resume(Callbacks callbacks) {
+        if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
         synchronized (mEntriesMap) {
             mCurCallbacks = callbacks;
             mResumed = true;
@@ -364,13 +389,16 @@
             if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
                 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
             }
+            if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
         }
     }
 
     void pause() {
+        if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
         synchronized (mEntriesMap) {
             mCurCallbacks = null;
             mResumed = false;
+            if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
         }
     }
 
@@ -438,10 +466,12 @@
             ApplicationInfo info = apps.get(i);
             if (filter == null || filter.filterApp(info)) {
                 synchronized (mEntriesMap) {
+                    if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
                     AppEntry entry = getEntryLocked(info);
                     entry.ensureLabel(mContext);
                     if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry);
                     filteredApps.add(entry);
+                    if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
                 }
             }
         }
@@ -467,6 +497,7 @@
     }
 
     AppEntry getEntry(String packageName) {
+        if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
         synchronized (mEntriesMap) {
             AppEntry entry = mEntriesMap.get(packageName);
             if (entry == null) {
@@ -478,6 +509,7 @@
                     }
                 }
             }
+            if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
             return entry;
         }
     }
@@ -492,20 +524,25 @@
     }
     
     void requestSize(String packageName) {
+        if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
         synchronized (mEntriesMap) {
             AppEntry entry = mEntriesMap.get(packageName);
             if (entry != null) {
                 mPm.getPackageSizeInfo(packageName, mBackgroundHandler.mStatsObserver);
             }
+            if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
         }
     }
 
     long sumCacheSizes() {
         long sum = 0;
+        if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
         synchronized (mEntriesMap) {
+            if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
             for (int i=mAppEntries.size()-1; i>=0; i--) {
                 sum += mAppEntries.get(i).cacheSize;
             }
+            if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
         }
         return sum;
     }
@@ -522,15 +559,18 @@
     void addPackage(String pkgName) {
         try {
             synchronized (mEntriesMap) {
+                if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
                 if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
                 if (!mResumed) {
                     // If we are not resumed, we will do a full query the
                     // next time we resume, so there is no reason to do work
                     // here.
+                    if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
                     return;
                 }
                 if (indexOfApplicationInfoLocked(pkgName) >= 0) {
                     if (DEBUG) Log.i(TAG, "Package already exists!");
+                    if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
                     return;
                 }
                 ApplicationInfo info = mPm.getApplicationInfo(pkgName,
@@ -543,6 +583,7 @@
                 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
                     mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
                 }
+                if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
             }
         } catch (NameNotFoundException e) {
         }
@@ -550,6 +591,7 @@
 
     void removePackage(String pkgName) {
         synchronized (mEntriesMap) {
+            if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
             int idx = indexOfApplicationInfoLocked(pkgName);
             if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
             if (idx >= 0) {
@@ -564,6 +606,7 @@
                     mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
                 }
             }
+            if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
         }
     }
 
@@ -611,6 +654,7 @@
             public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
                 boolean sizeChanged = false;
                 synchronized (mEntriesMap) {
+                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
                     AppEntry entry = mEntriesMap.get(stats.packageName);
                     if (entry != null) {
                         synchronized (entry) {
@@ -642,6 +686,7 @@
                         mCurComputingSizePkg = null;
                         sendEmptyMessage(MSG_LOAD_SIZES);
                     }
+                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
                 }
             }
         };
@@ -661,6 +706,7 @@
                 case MSG_LOAD_ENTRIES: {
                     int numDone = 0;
                     synchronized (mEntriesMap) {
+                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
                         for (int i=0; i<mApplications.size() && numDone<6; i++) {
                             if (!mRunning) {
                                 mRunning = true;
@@ -674,6 +720,7 @@
                                 getEntryLocked(info);
                             }
                         }
+                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
                     }
 
                     if (numDone >= 6) {
@@ -685,6 +732,7 @@
                 case MSG_LOAD_ICONS: {
                     int numDone = 0;
                     synchronized (mEntriesMap) {
+                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
                         for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
                             AppEntry entry = mAppEntries.get(i);
                             if (entry.icon == null || !entry.mounted) {
@@ -701,6 +749,7 @@
                                 }
                             }
                         }
+                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
                     }
                     if (numDone > 0) {
                         if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
@@ -715,7 +764,9 @@
                 } break;
                 case MSG_LOAD_SIZES: {
                     synchronized (mEntriesMap) {
+                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
                         if (mCurComputingSizePkg != null) {
+                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
                             return;
                         }
 
@@ -735,6 +786,7 @@
                                     mCurComputingSizePkg = entry.info.packageName;
                                     mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver);
                                 }
+                                if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
                                 return;
                             }
                         }
@@ -745,6 +797,7 @@
                                     MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
                             mMainHandler.sendMessage(m);
                         }
+                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
                     }
                 } break;
             }
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 345766e..9ec65c1 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -350,6 +350,12 @@
 
         @Override
         public void onRebuildComplete(ArrayList<AppEntry> apps) {
+            if (mLoadingContainer.getVisibility() == View.VISIBLE) {
+                mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
+                        ManageApplications.this, android.R.anim.fade_out));
+                mListContainer.startAnimation(AnimationUtils.loadAnimation(
+                        ManageApplications.this, android.R.anim.fade_in));
+            }
             mListContainer.setVisibility(View.VISIBLE);
             mLoadingContainer.setVisibility(View.GONE);
             mWaitingForData = false;