Fix race in LauncherModel that causes it to show duplicate icons.
Change-Id: I78130d6f237f476bc33a4718ca5ef245fe502857
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 7a56bcd..a19eb4c 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -62,6 +62,7 @@
static final boolean DEBUG_LOADERS = false;
static final String TAG = "Launcher.Model";
+ private int mBatchSize; // 0 is all apps at once
private int mAllAppsLoadDelay; // milliseconds between batches
private final LauncherApplication mApp;
@@ -69,7 +70,7 @@
private DeferredHandler mHandler = new DeferredHandler();
private Loader mLoader = new Loader();
- private boolean mBeforeFirstLoad = true;
+ private boolean mBeforeFirstLoad = true; // only access this from main thread
private WeakReference<Callbacks> mCallbacks;
private AllAppsList mAllAppsList;
@@ -88,7 +89,6 @@
public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
public void bindAppsRemoved(ArrayList<ApplicationInfo> apps);
- public int getAppBatchSize();
}
LauncherModel(LauncherApplication app, IconCache iconCache) {
@@ -100,6 +100,8 @@
app.getPackageManager().getDefaultActivityIcon(), app);
mAllAppsLoadDelay = app.getResources().getInteger(R.integer.config_allAppsBatchLoadDelay);
+
+ mBatchSize = app.getResources().getInteger(R.integer.config_allAppsBatchSize);
}
public Bitmap getFallbackIcon() {
@@ -1024,49 +1026,107 @@
private void loadAndBindAllApps() {
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- final Callbacks callbacks = mCallbacks.get();
- if (callbacks == null) {
+ // 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, "LoaderThread running with no launcher (loadAndBindAllApps)");
return;
}
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
final PackageManager packageManager = mContext.getPackageManager();
- final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
+ List<ResolveInfo> apps = null;
- int N;
- int batchSize = callbacks.getAppBatchSize();
+ int N = Integer.MAX_VALUE;
- synchronized (mLock) {
- mBeforeFirstLoad = false;
- mAllAppsList.clear();
- if (apps == null) return;
- N = apps.size();
- if (batchSize <= 0)
- batchSize = N;
- }
-
+ int startIndex;
int i=0;
+ int batchSize = -1;
while (i < N && !mStopped) {
synchronized (mLock) {
+ if (i == 0) {
+ // This needs to happen inside the same lock block as when we
+ // prepare the first batch for bindAllApplications. Otherwise
+ // the package changed receiver can come in and double-add
+ // (or miss one?).
+ mAllAppsList.clear();
+ final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ apps = packageManager.queryIntentActivities(mainIntent, 0);
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "queryIntentActivities took "
+ + (SystemClock.uptimeMillis()-qiaTime) + "ms");
+ }
+ if (apps == null) {
+ return;
+ }
+ N = apps.size();
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "queryIntentActivities got " + N + " apps");
+ }
+ if (N == 0) {
+ // There are no apps?!?
+ return;
+ }
+ if (mBatchSize == 0) {
+ batchSize = N;
+ } else {
+ batchSize = mBatchSize;
+ }
+
+ final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ Collections.sort(apps,
+ new ResolveInfo.DisplayNameComparator(packageManager));
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "sort took "
+ + (SystemClock.uptimeMillis()-qiaTime) + "ms");
+ }
+ }
+
final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ startIndex = i;
for (int j=0; i<N && j<batchSize; j++) {
// This builds the icon bitmaps.
mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache));
i++;
}
+
+ final boolean first = i <= batchSize;
+ final ArrayList<ApplicationInfo> added = mAllAppsList.added;
+ mAllAppsList.added = new ArrayList<ApplicationInfo>();
+
+ mHandler.post(new Runnable() {
+ public void run() {
+ final long t = SystemClock.uptimeMillis();
+ final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (first) {
+ mBeforeFirstLoad = false;
+ callbacks.bindAllApplications(added);
+ } else {
+ callbacks.bindAppsAdded(added);
+ }
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "bound " + added.size() + " apps in "
+ + (SystemClock.uptimeMillis() - t) + "ms");
+ }
+ }
+ });
+
if (DEBUG_LOADERS) {
- Log.d(TAG, "batch of " + batchSize + " icons processed in "
+ Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "
+ (SystemClock.uptimeMillis()-t2) + "ms");
}
}
- mHandler.post(bindAllAppsTask);
-
if (mAllAppsLoadDelay > 0 && i < N) {
try {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms");
+ }
Thread.sleep(mAllAppsLoadDelay);
} catch (InterruptedException exc) { }
}
@@ -1079,34 +1139,6 @@
}
}
- final Runnable bindAllAppsTask = new Runnable() {
- public void run() {
- final long t = SystemClock.uptimeMillis();
- int count = 0;
- Callbacks callbacks = null;
- ArrayList<ApplicationInfo> results = null;
- synchronized (mLock) {
- mHandler.cancelRunnable(this);
-
- results = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
- // We're adding this now, so clear out this so we don't re-send them.
- mAllAppsList.added = new ArrayList<ApplicationInfo>();
- count = results.size();
-
- callbacks = tryGetCallbacks(mCallbacks.get());
- }
-
- if (callbacks != null && count > 0) {
- callbacks.bindAllApplications(results);
- }
-
- if (DEBUG_LOADERS) {
- Log.d(TAG, "bound " + count + " apps in "
- + (SystemClock.uptimeMillis() - t) + "ms");
- }
- }
- };
-
public void dumpState() {
Log.d(TAG, "mLoader.mLoaderThread.mContext=" + mContext);
Log.d(TAG, "mLoader.mLoaderThread.mWaitThread=" + mWaitThread);