Moving install queue updates to worker thread.

This avoids acquiring a lock for upating the sharedPrefs during onResume
as all the logic runs on a single thread.

Bug: 67305604
Change-Id: I1bbea382da9fafb403b4e9508f393f78db28478d
This commit is contained in:
Sunny Goyal 2017-10-05 15:57:40 -07:00
parent 22b92df30f
commit 91498abf75
4 changed files with 95 additions and 111 deletions

View File

@ -27,7 +27,9 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
@ -61,6 +63,9 @@ import java.util.Set;
public class InstallShortcutReceiver extends BroadcastReceiver {
private static final int MSG_ADD_TO_QUEUE = 1;
private static final int MSG_FLUSH_QUEUE = 2;
public static final int FLAG_ACTIVITY_PAUSED = 1;
public static final int FLAG_LOADER_RUNNING = 2;
public static final int FLAG_DRAG_AND_DROP = 4;
@ -93,73 +98,98 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
private static final Object sLock = new Object();
private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
private static void addToInstallQueue(
SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
synchronized(sLock) {
String encoded = info.encodeToString();
if (encoded != null) {
Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
strings.add(encoded);
sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ADD_TO_QUEUE: {
Pair<Context, PendingInstallShortcutInfo> pair =
(Pair<Context, PendingInstallShortcutInfo>) msg.obj;
String encoded = pair.second.encodeToString();
SharedPreferences prefs = Utilities.getPrefs(pair.first);
Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
strings.add(encoded);
prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
return;
}
case MSG_FLUSH_QUEUE: {
Context context = (Context) msg.obj;
LauncherModel model = LauncherAppState.getInstance(context).getModel();
if (model.getCallback() == null) {
// Launcher not loaded
return;
}
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
SharedPreferences prefs = Utilities.getPrefs(context);
Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
if (strings == null) {
return;
}
LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, context);
if (info == null) {
continue;
}
String pkg = getIntentPackage(info.launchIntent);
if (!TextUtils.isEmpty(pkg)
&& !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+ info.launchIntent);
continue;
}
// Generate a shortcut info to add into the model
installQueue.add(info.getItemInfo());
}
prefs.edit().remove(APPS_PENDING_INSTALL).apply();
if (!installQueue.isEmpty()) {
model.addAndBindAddedWorkspaceItems(installQueue);
}
return;
}
}
}
}
};
public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
UserHandle user) {
if (packageNames.isEmpty()) {
return;
}
Preconditions.assertWorkerThread();
SharedPreferences sp = Utilities.getPrefs(context);
synchronized(sLock) {
Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) {
Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+ ", removing packages: " + packageNames);
}
if (Utilities.isEmpty(strings)) {
return;
}
Set<String> newStrings = new HashSet<>(strings);
Iterator<String> newStringsIter = newStrings.iterator();
while (newStringsIter.hasNext()) {
String encoded = newStringsIter.next();
try {
Decoder decoder = new Decoder(encoded, context);
if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
user.equals(decoder.user)) {
newStringsIter.remove();
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) {
Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+ ", removing packages: " + packageNames);
}
if (Utilities.isEmpty(strings)) {
return;
}
Set<String> newStrings = new HashSet<>(strings);
Iterator<String> newStringsIter = newStrings.iterator();
while (newStringsIter.hasNext()) {
String encoded = newStringsIter.next();
try {
Decoder decoder = new Decoder(encoded, context);
if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
user.equals(decoder.user)) {
newStringsIter.remove();
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
newStringsIter.remove();
}
sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
}
private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(Context context) {
SharedPreferences sharedPrefs = Utilities.getPrefs(context);
synchronized(sLock) {
ArrayList<PendingInstallShortcutInfo> infos = new ArrayList<>();
Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
if (strings == null) {
return infos;
}
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, context);
if (info != null) {
infos.add(info);
}
}
sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).apply();
return infos;
}
sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
public void onReceive(Context context, Intent data) {
@ -256,7 +286,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
// Queue the item up for adding if launcher has not loaded properly yet
addToInstallQueue(Utilities.getPrefs(context), info);
Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
flushInstallQueue(context);
}
@ -269,17 +299,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
}
static void flushInstallQueue(Context context) {
LauncherModel model = LauncherAppState.getInstance(context).getModel();
boolean launcherNotLoaded = model.getCallback() == null;
if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) {
if (sInstallQueueDisabledFlags != 0) {
return;
}
ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
if (!items.isEmpty()) {
model.addAndBindAddedWorkspaceItems(
new LazyShortcutsProvider(context.getApplicationContext(), items));
}
Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
}
/**
@ -601,42 +624,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
return new PendingInstallShortcutInfo(info, original.mContext);
}
private static class LazyShortcutsProvider extends Provider<List<Pair<ItemInfo, Object>>> {
private final Context mContext;
private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
public LazyShortcutsProvider(Context context, ArrayList<PendingInstallShortcutInfo> items) {
mContext = context;
mPendingItems = items;
}
/**
* This must be called on the background thread as this requires multiple calls to
* packageManager and icon cache.
*/
@Override
public ArrayList<Pair<ItemInfo, Object>> get() {
Preconditions.assertNonUiThread();
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
for (PendingInstallShortcutInfo pendingInfo : mPendingItems) {
// If the intent specifies a package, make sure the package exists
String packageName = getIntentPackage(pendingInfo.launchIntent);
if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile(
packageName, pendingInfo.user)) {
if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+ pendingInfo.launchIntent);
continue;
}
// Generate a shortcut info to add into the model
installQueue.add(pendingInfo.getItemInfo());
}
return installQueue;
}
}
private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);

View File

@ -192,9 +192,8 @@ public class LauncherModel extends BroadcastReceiver
/**
* Adds the provided items to the workspace.
*/
public void addAndBindAddedWorkspaceItems(
Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
public ModelWriter getWriter(boolean hasVerticalHotseat) {

View File

@ -38,7 +38,6 @@ import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo;
import com.android.launcher3.util.Provider;
import java.util.ArrayList;
import java.util.List;
@ -47,19 +46,18 @@ import java.util.List;
*/
public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
private final Provider<List<Pair<ItemInfo, Object>>> mAppsProvider;
private final List<Pair<ItemInfo, Object>> mItemList;
/**
* @param appsProvider items to add on the workspace
* @param itemList items to add on the workspace
*/
public AddWorkspaceItemsTask(Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
mAppsProvider = appsProvider;
public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
mItemList = itemList;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
List<Pair<ItemInfo, Object>> workspaceApps = mAppsProvider.get();
if (workspaceApps.isEmpty()) {
if (mItemList.isEmpty()) {
return;
}
Context context = app.getContext();
@ -75,7 +73,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
synchronized(dataModel) {
List<ItemInfo> filteredItems = new ArrayList<>();
for (Pair<ItemInfo, Object> entry : workspaceApps) {
for (Pair<ItemInfo, Object> entry : mItemList) {
ItemInfo item = entry.first;
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {

View File

@ -55,7 +55,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
for (ItemInfo item : items) {
list.add(Pair.create(item, null));
}
return new AddWorkspaceItemsTask(Provider.of(list)) {
return new AddWorkspaceItemsTask(list) {
@Override
protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }