summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTony Wickham <twickham@google.com>2016-05-19 11:19:39 -0700
committerTony Wickham <twickham@google.com>2016-06-21 15:49:16 -0700
commitbfbf7f9f4a0b300613f0ff27a4eb592d88c08325 (patch)
tree6906865484b6a03199eecd0f352eba24345af33e
parentae50284e0a838139c67caf0064a0977c871497fa (diff)
downloadandroid_packages_apps_Trebuchet-bfbf7f9f4a0b300613f0ff27a4eb592d88c08325.tar.gz
android_packages_apps_Trebuchet-bfbf7f9f4a0b300613f0ff27a4eb592d88c08325.tar.bz2
android_packages_apps_Trebuchet-bfbf7f9f4a0b300613f0ff27a4eb592d88c08325.zip
Add support for launcher shortcuts.
- This CL has no UI but provides the necessary backing for one. - Adds new item type: ITEM_TYPE_DEEP_SHORTCUT, to distinguish from ITEM_TYPE_SHORTCUT. We can reconsider these names. - Adds ShortcutCache, using LruCache for up to 30 dynamic shortcuts (pinned shortcuts are always cached in a HashMap). - DeepShortcutManager queries for shortcuts and other things like pin them. In a future CL it will use the cache, but for now it simply makes an RPC for all queries. - LauncherModel maintains counts for pinned shortcuts, pinning and unpinning when counts reach 1 or 0, respectively. - LauncherModel maintains a map of components to lists of shortcut ids, which Launcher gets a copy of after it is changed in the background. This will allow us to know how many shortcuts an app has immediately, and query for details as the UI is animating. Change-Id: Ic526f374dd10d72a261bae67f07f098fca8d8bca
-rw-r--r--build.gradle2
-rw-r--r--src/com/android/launcher3/BubbleTextView.java17
-rw-r--r--src/com/android/launcher3/ItemInfo.java2
-rw-r--r--src/com/android/launcher3/Launcher.java35
-rw-r--r--src/com/android/launcher3/LauncherAppState.java10
-rw-r--r--src/com/android/launcher3/LauncherBackupHelper.java12
-rw-r--r--src/com/android/launcher3/LauncherModel.java296
-rw-r--r--src/com/android/launcher3/LauncherSettings.java5
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java49
-rw-r--r--src/com/android/launcher3/Workspace.java5
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompat.java16
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatV16.java26
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatVL.java19
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java123
-rw-r--r--src/com/android/launcher3/folder/Folder.java5
-rw-r--r--src/com/android/launcher3/folder/FolderIcon.java3
-rw-r--r--src/com/android/launcher3/model/GridSizeMigrationTask.java10
-rw-r--r--src/com/android/launcher3/shortcuts/DeepShortcutManager.java136
-rw-r--r--src/com/android/launcher3/shortcuts/ShortcutCache.java76
-rw-r--r--src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java104
-rw-r--r--src/com/android/launcher3/shortcuts/ShortcutKey.java30
-rw-r--r--src/com/android/launcher3/util/ManagedProfileHeuristic.java7
-rw-r--r--src/com/android/launcher3/util/MultiHashMap.java20
23 files changed, 947 insertions, 61 deletions
diff --git a/build.gradle b/build.gradle
index 899767fc8..f98021c69 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,7 +18,7 @@ android {
defaultConfig {
applicationId "com.android.launcher3"
minSdkVersion 21
- targetSdkVersion 23
+ targetSdkVersion 'N'
versionCode 1
versionName "1.0"
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index be00aec34..97515a8d4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -158,7 +158,7 @@ public class BubbleTextView extends TextView
if (info.isDisabled()) {
iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
}
- setIcon(iconDrawable, mIconSize);
+ setIcon(iconDrawable);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
}
@@ -175,7 +175,7 @@ public class BubbleTextView extends TextView
if (info.isDisabled()) {
iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
}
- setIcon(iconDrawable, mIconSize);
+ setIcon(iconDrawable);
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
@@ -188,7 +188,7 @@ public class BubbleTextView extends TextView
}
public void applyFromPackageItemInfo(PackageItemInfo info) {
- setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize);
+ setIcon(mLauncher.createIconDrawable(info.iconBitmap));
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
@@ -205,7 +205,7 @@ public class BubbleTextView extends TextView
*/
public void applyDummyInfo() {
ColorDrawable d = new ColorDrawable();
- setIcon(mLauncher.resizeIconDrawable(d), mIconSize);
+ setIcon(mLauncher.resizeIconDrawable(d));
setText("");
}
@@ -477,7 +477,7 @@ public class BubbleTextView extends TextView
preloadDrawable = (PreloadIconDrawable) mIcon;
} else {
preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
- setIcon(preloadDrawable, mIconSize);
+ setIcon(preloadDrawable);
}
preloadDrawable.setLevel(progressLevel);
@@ -506,10 +506,10 @@ public class BubbleTextView extends TextView
* Sets the icon for this view based on the layout direction.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- private Drawable setIcon(Drawable icon, int iconSize) {
+ public void setIcon(Drawable icon) {
mIcon = icon;
- if (iconSize != -1) {
- mIcon.setBounds(0, 0, iconSize, iconSize);
+ if (mIconSize != -1) {
+ mIcon.setBounds(0, 0, mIconSize, mIconSize);
}
if (mLayoutHorizontal) {
if (Utilities.ATLEAST_JB_MR1) {
@@ -520,7 +520,6 @@ public class BubbleTextView extends TextView
} else {
setCompoundDrawables(null, mIcon, null, null);
}
- return icon;
}
@Override
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 286a7f104..f54a2d47a 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -32,7 +32,7 @@ public class ItemInfo {
/**
* Intent extra to store the profile. Format: UserHandle
*/
- static final String EXTRA_PROFILE = "profile";
+ public static final String EXTRA_PROFILE = "profile";
public static final int NO_ID = -1;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 156c1b0b0..cc31de208 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -117,6 +117,7 @@ import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
@@ -293,6 +294,9 @@ public class Launcher extends Activity
private boolean mHasFocus = false;
private boolean mAttached = false;
+ /** Maps launcher activity components to their list of shortcut ids. */
+ private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+
private LauncherClings mClings;
private View.OnTouchListener mHapticFeedbackTouchListener;
@@ -2353,7 +2357,7 @@ public class Launcher extends Activity
* @param itemInfo the {@link ItemInfo} for this view.
* @param deleteFromDb whether or not to delete this item from the db.
*/
- public boolean removeItem(View v, ItemInfo itemInfo, boolean deleteFromDb) {
+ public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) {
if (itemInfo instanceof ShortcutInfo) {
// Remove the shortcut from the folder before removing it from launcher
View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
@@ -2381,7 +2385,6 @@ public class Launcher extends Activity
if (deleteFromDb) {
deleteWidgetInfo(widgetInfo);
}
-
} else {
return false;
}
@@ -2818,8 +2821,16 @@ public class Launcher extends Activity
// is enabled by default on NYC.
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
.penaltyLog().build());
- // Could be launching some bookkeeping activity
- startActivity(intent, optsBundle);
+
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ String id = ((ShortcutInfo) info).getDeepShortcutId();
+ String packageName = intent.getPackage();
+ LauncherAppsCompat.getInstance(this).startShortcut(
+ packageName, id, intent.getSourceBounds(), optsBundle, info.user);
+ } else {
+ // Could be launching some bookkeeping activity
+ startActivity(intent, optsBundle);
+ }
} finally {
StrictMode.setVmPolicy(oldPolicy);
}
@@ -2895,8 +2906,9 @@ public class Launcher extends Activity
new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()));
}
try {
- if (Utilities.ATLEAST_MARSHMALLOW &&
- item != null && item.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+ if (Utilities.ATLEAST_MARSHMALLOW && item != null
+ && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item);
} else if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
@@ -3702,6 +3714,7 @@ public class Launcher extends Activity
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
@@ -4052,6 +4065,16 @@ public class Launcher extends Activity
}
/**
+ * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
+ * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+ */
+ @Override
+ public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+ mDeepShortcutMap = deepShortcutMapCopy;
+ if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
+ }
+
+ /**
* A package was updated.
*
* Implementation of the method from LauncherModel.Callbacks.
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index c2e7f1aac..2ba4982b4 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -27,6 +27,8 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutCache;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
@@ -39,6 +41,7 @@ public class LauncherAppState {
@Thunk final LauncherModel mModel;
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
+ private final DeepShortcutManager mDeepShortcutManager;
@Thunk boolean mWallpaperChangedSinceLastCheck;
@@ -92,9 +95,10 @@ public class LauncherAppState {
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
+ mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
- mModel = new LauncherModel(this, mIconCache, mAppFilter);
+ mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
@@ -165,6 +169,10 @@ public class LauncherAppState {
return mWidgetCache;
}
+ public DeepShortcutManager getShortcutManager() {
+ return mDeepShortcutManager;
+ }
+
public boolean hasWallpaperChangedSinceLastCheck() {
boolean result = mWallpaperChangedSinceLastCheck;
mWallpaperChangedSinceLastCheck = false;
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index bca2ffbe0..e987a9bdf 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -560,7 +560,8 @@ public class LauncherBackupHelper implements BackupHelper {
// Don't backup apps in other profiles for now.
String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
- Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
+ Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + " OR " +
+ Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_DEEP_SHORTCUT + ") AND " +
getUserSelectionArg();
Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
where, null, null);
@@ -798,7 +799,8 @@ public class LauncherBackupHelper implements BackupHelper {
return favorite.container == Favorites.CONTAINER_HOTSEAT
&& favorite.intent != null
&& (favorite.itemType == Favorites.ITEM_TYPE_APPLICATION
- || favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT);
+ || favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ || favorite.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT);
}
/** Serialize a Favorite for persistence, including a checksum wrapper. */
@@ -835,7 +837,8 @@ public class LauncherBackupHelper implements BackupHelper {
if (!TextUtils.isEmpty(appWidgetProvider)) {
favorite.appWidgetProvider = appWidgetProvider;
}
- } else if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+ } else if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ || favorite.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
String iconPackage = c.getString(ICON_PACKAGE_INDEX);
String iconResource = c.getString(ICON_RESOURCE_INDEX);
if (!TextUtils.isEmpty(iconPackage) && !TextUtils.isEmpty(iconResource)) {
@@ -897,7 +900,8 @@ public class LauncherBackupHelper implements BackupHelper {
values.put(Favorites.SPANY, favorite.spanY);
values.put(Favorites.RANK, favorite.rank);
- if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+ if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ || favorite.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
values.put(Favorites.ICON, favorite.icon);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index a5e703eb0..9e8766005 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -41,6 +41,7 @@ import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.MutableInt;
import android.util.Pair;
import com.android.launcher3.compat.AppWidgetManagerCompat;
@@ -59,6 +60,9 @@ import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.CursorIconInfo;
import com.android.launcher3.util.FlagOp;
@@ -68,6 +72,7 @@ import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -82,6 +87,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -118,8 +124,9 @@ public class LauncherModel extends BroadcastReceiver
// 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.
- @Thunk boolean mWorkspaceLoaded;
- @Thunk boolean mAllAppsLoaded;
+ private boolean mWorkspaceLoaded;
+ private boolean mAllAppsLoaded;
+ private boolean mDeepShortcutsLoaded;
/**
* Set of runnables to be called on the background thread after the workspace binding
@@ -134,6 +141,9 @@ public class LauncherModel extends BroadcastReceiver
// Entire list of widgets.
private final WidgetsModel mBgWidgetsModel;
+ // Maps all launcher activities to the id's of their shortcuts (if they have any).
+ private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
+
// The lock that must be acquired before referencing any static bg data structures. Unlike
// other locks, this one can generally be held long-term because we never expect any of these
// static data structures to be referenced outside of the worker thread except on the first
@@ -159,16 +169,21 @@ public class LauncherModel extends BroadcastReceiver
// sBgWorkspaceScreens is the ordered set of workspace screens.
static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
+ // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
+ // times it is pinned.
+ static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
+
// sPendingPackages is a set of packages which could be on sdcard and are not available yet
static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
new HashMap<UserHandleCompat, HashSet<String>>();
// </ only access in worker thread >
- @Thunk IconCache mIconCache;
+ private IconCache mIconCache;
+ private DeepShortcutManager mDeepShortcutManager;
- @Thunk final LauncherAppsCompat mLauncherApps;
- @Thunk final UserManagerCompat mUserManager;
+ private final LauncherAppsCompat mLauncherApps;
+ private final UserManagerCompat mUserManager;
public interface Callbacks {
public boolean setLoadOnResume();
@@ -198,18 +213,21 @@ public class LauncherModel extends BroadcastReceiver
public void bindWidgetsModel(WidgetsModel model);
public void onPageBoundSynchronously(int page);
public void executeOnNextDraw(ViewOnDrawExecutor executor);
+ public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
}
public interface ItemInfoFilter {
public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
}
- LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+ LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
+ DeepShortcutManager deepShortcutManager) {
Context context = app.getContext();
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
mIconCache = iconCache;
+ mDeepShortcutManager = deepShortcutManager;
mLauncherApps = LauncherAppsCompat.getInstance(context);
mUserManager = UserManagerCompat.getInstance(context);
@@ -678,6 +696,7 @@ public class LauncherModel extends BroadcastReceiver
switch (modelItem.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
if (!sBgWorkspaceItems.contains(modelItem)) {
sBgWorkspaceItems.add(modelItem);
@@ -891,6 +910,7 @@ public class LauncherModel extends BroadcastReceiver
// Fall through
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
sBgWorkspaceItems.add(item);
@@ -902,6 +922,14 @@ public class LauncherModel extends BroadcastReceiver
Log.e(TAG, msg);
}
}
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ ShortcutInfo shortcutInfo = (ShortcutInfo) item;
+ ShortcutKey shortcutToPin = new ShortcutKey(
+ shortcutInfo.intent.getPackage(),
+ shortcutInfo.user,
+ shortcutInfo.getDeepShortcutId());
+ incrementPinnedShortcutCount(shortcutToPin, true /* shouldPin */);
+ }
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
sBgAppWidgets.add((LauncherAppWidgetInfo) item);
@@ -968,6 +996,14 @@ public class LauncherModel extends BroadcastReceiver
}
sBgWorkspaceItems.remove(item);
break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ ShortcutInfo shortcutInfo = ((ShortcutInfo) item);
+ ShortcutKey pinnedShortcut = new ShortcutKey(
+ shortcutInfo.intent.getPackage(),
+ shortcutInfo.user,
+ shortcutInfo.getDeepShortcutId());
+ decrementPinnedShortcutCount(pinnedShortcut);
+ // Fall through.
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
sBgWorkspaceItems.remove(item);
@@ -985,6 +1021,39 @@ public class LauncherModel extends BroadcastReceiver
}
/**
+ * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
+ */
+ private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
+ synchronized (sBgLock) {
+ MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
+ if (count == null || --count.value == 0) {
+ LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
+ }
+ }
+ }
+
+ /**
+ * Increment the count for the given shortcut, pinning it if the count becomes 1.
+ *
+ * As an optimization, the caller can pass shouldPin == false to avoid
+ * unnecessary RPC's if the shortcut is already pinned.
+ */
+ private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
+ synchronized (sBgLock) {
+ MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
+ if (count == null) {
+ count = new MutableInt(1);
+ sBgPinnedShortcutCounts.put(pinnedShortcut, count);
+ } else {
+ count.value++;
+ }
+ if (shouldPin && count.value == 1) {
+ LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
+ }
+ }
+ }
+
+ /**
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
@@ -1076,28 +1145,28 @@ public class LauncherModel extends BroadcastReceiver
@Override
public void onPackageChanged(String packageName, UserHandleCompat user) {
int op = PackageUpdatedTask.OP_UPDATE;
- enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+ enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
user));
}
@Override
public void onPackageRemoved(String packageName, UserHandleCompat user) {
int op = PackageUpdatedTask.OP_REMOVE;
- enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+ enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
user));
}
@Override
public void onPackageAdded(String packageName, UserHandleCompat user) {
int op = PackageUpdatedTask.OP_ADD;
- enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+ enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
user));
}
@Override
public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
boolean replacing) {
- enqueuePackageUpdated(
+ enqueueItemUpdatedTask(
new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
}
@@ -1105,7 +1174,7 @@ public class LauncherModel extends BroadcastReceiver
public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
boolean replacing) {
if (!replacing) {
- enqueuePackageUpdated(new PackageUpdatedTask(
+ enqueueItemUpdatedTask(new PackageUpdatedTask(
PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
user));
}
@@ -1113,18 +1182,24 @@ public class LauncherModel extends BroadcastReceiver
@Override
public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
- enqueuePackageUpdated(new PackageUpdatedTask(
+ enqueueItemUpdatedTask(new PackageUpdatedTask(
PackageUpdatedTask.OP_SUSPEND, packageNames,
user));
}
@Override
public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
- enqueuePackageUpdated(new PackageUpdatedTask(
+ enqueueItemUpdatedTask(new PackageUpdatedTask(
PackageUpdatedTask.OP_UNSUSPEND, packageNames,
user));
}
+ @Override
+ public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+ UserHandleCompat user) {
+ enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user));
+ }
+
/**
* Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
* ACTION_PACKAGE_CHANGED.
@@ -1145,7 +1220,7 @@ public class LauncherModel extends BroadcastReceiver
LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
UserHandleCompat user = UserHandleCompat.fromIntent(intent);
if (user != null) {
- enqueuePackageUpdated(new PackageUpdatedTask(
+ enqueueItemUpdatedTask(new PackageUpdatedTask(
PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
new String[0], user));
}
@@ -1170,6 +1245,8 @@ public class LauncherModel extends BroadcastReceiver
stopLoaderLocked();
if (resetAllAppsLoaded) mAllAppsLoaded = false;
if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
+ // Always reset deep shortcuts loaded.
+ mDeepShortcutsLoaded = false;
}
}
@@ -1256,6 +1333,7 @@ public class LauncherModel extends BroadcastReceiver
* - workspace icons
* - widgets
* - all apps icons
+ * - deep shortcuts within apps
*/
private class LoaderTask implements Runnable {
private Context mContext;
@@ -1387,6 +1465,12 @@ public class LauncherModel extends BroadcastReceiver
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
+
+ waitForIdle();
+
+ // third step
+ if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
+ loadAndBindDeepShortcuts();
}
// Clear out this reference, otherwise we end up holding it until all of the
@@ -1538,6 +1622,7 @@ public class LauncherModel extends BroadcastReceiver
sBgFolders.clear();
sBgItemsIdMap.clear();
sBgWorkspaceScreens.clear();
+ sBgPinnedShortcutCounts.clear();
}
}
@@ -1581,6 +1666,7 @@ public class LauncherModel extends BroadcastReceiver
final ArrayList<Long> itemsToRemove = new ArrayList<>();
final ArrayList<Long> restoredRows = new ArrayList<>();
+ Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
@@ -1631,6 +1717,13 @@ public class LauncherModel extends BroadcastReceiver
long serialNo = mUserManager.getSerialNumberForUser(user);
allUsers.put(serialNo, user);
quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
+
+ List<ShortcutInfoCompat> pinnedShortcuts = mDeepShortcutManager
+ .queryForPinnedShortcuts(null, user);
+ for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
+ shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
+ shortcut);
+ }
}
ShortcutInfo info;
@@ -1653,6 +1746,7 @@ public class LauncherModel extends BroadcastReceiver
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
id = c.getLong(idIndex);
intentDescription = c.getString(intentIndex);
serialNumber = c.getInt(profileIdIndex);
@@ -1815,7 +1909,37 @@ public class LauncherModel extends BroadcastReceiver
info = getAppShortcutInfo(intent, user, context, c,
cursorIconInfo.iconIndex, titleIndex,
allowMissingTarget, useLowResIcon);
- } else {
+ } else if (itemType ==
+ LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ String shortcutId = intent.getStringExtra(
+ ShortcutInfoCompat.EXTRA_SHORTCUT_ID);
+ String packageName = intent.getPackage();
+ ShortcutKey key = new ShortcutKey(intent.getPackage(),
+ user, shortcutId);
+ ShortcutInfoCompat pinnedShortcut =
+ shortcutKeyToPinnedShortcuts.get(key);
+ boolean shouldPin = false; // It's already pinned.
+ if (pinnedShortcut == null) {
+ // It shouldn't be possible for a shortcut to be on the
+ // workspace without being pinned, but if one somehow is,
+ // we should pin it now to get back to a good state.
+ Log.w(TAG, "Shortcut was on workspace but wasn't pinned");
+ // Get full details; incrementing the count will pin it.
+ List<ShortcutInfoCompat> fullDetails = mDeepShortcutManager
+ .queryForFullDetails(packageName,
+ Collections.singletonList(shortcutId), user);
+ if (fullDetails == null || fullDetails.isEmpty()) {
+ itemsToRemove.add(id);
+ continue;
+ } else {
+ pinnedShortcut = fullDetails.get(0);
+ shouldPin = true;
+ }
+ }
+ incrementPinnedShortcutCount(key, shouldPin);
+ info = ShortcutInfo.fromDeepShortcutInfo(pinnedShortcut,
+ context, launcherApps);
+ } else { // item type == ITEM_TYPE_SHORTCUT
info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
// Shortcuts are only available on the primary profile
@@ -2094,6 +2218,15 @@ public class LauncherModel extends BroadcastReceiver
}
}
+ // Unpin shortcuts that don't exist on the workspace.
+ for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
+ MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
+ if (numTimesPinned == null || numTimesPinned.value == 0) {
+ // Shortcut is pinned but doesn't exist on the workspace; unpin it.
+ mDeepShortcutManager.unpinShortcut(key);
+ }
+ }
+
// Sort all the folder items and make sure the first 3 items are high resolution.
for (FolderInfo folder : sBgFolders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
@@ -2622,6 +2755,27 @@ public class LauncherModel extends BroadcastReceiver
}
}
+ private void loadAndBindDeepShortcuts() {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
+ }
+ if (!mDeepShortcutsLoaded) {
+ mBgDeepShortcutMap.clear();
+ for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+ List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
+ .queryForAllShortcuts(user);
+ updateDeepShortcutMap(null, shortcuts);
+ }
+ synchronized (LoaderTask.this) {
+ if (mStopped) {
+ return;
+ }
+ mDeepShortcutsLoaded = true;
+ }
+ }
+ bindDeepShortcutMapOnMainThread();
+ }
+
public void dumpState() {
synchronized (sBgLock) {
Log.d(TAG, "mLoaderTask.mContext=" + mContext);
@@ -2632,6 +2786,40 @@ public class LauncherModel extends BroadcastReceiver
}
}
+ // Clear all the shortcuts for the given package, and re-add the new shortcuts.
+ private void updateDeepShortcutMap(String packageName, List<ShortcutInfoCompat> shortcuts) {
+ // Remove all keys associated with the given package.
+ if (packageName != null) {
+ Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
+ while (keysIter.hasNext()) {
+ if (keysIter.next().componentName.getPackageName().equals(packageName)) {
+ keysIter.remove();
+ }
+ }
+ }
+
+ // Now add the new shortcuts to the map.
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ ComponentKey targetComponent
+ = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+ mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+ }
+ }
+
+ private void bindDeepShortcutMapOnMainThread() {
+ final MultiHashMap<ComponentKey, String> shortcutMapCopy = new MultiHashMap<>();
+ shortcutMapCopy.putAll(mBgDeepShortcutMap);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = getCallback();
+ if (callbacks != null) {
+ callbacks.bindDeepShortcutMap(shortcutMapCopy);
+ }
+ }
+ });
+ }
+
/**
* Called when the icons for packages have been updated in the icon cache.
*/
@@ -2657,34 +2845,40 @@ public class LauncherModel extends BroadcastReceiver
mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
}
- if (!updatedShortcuts.isEmpty()) {
- final UserHandleCompat userFinal = user;
+ bindUpdatedShortcuts(updatedShortcuts, user);
+
+ if (!updatedApps.isEmpty()) {
mHandler.post(new Runnable() {
public void run() {
Callbacks cb = getCallback();
if (cb != null && callbacks == cb) {
- cb.bindShortcutsChanged(updatedShortcuts,
- new ArrayList<ShortcutInfo>(), userFinal);
+ cb.bindAppsUpdated(updatedApps);
}
}
});
}
+ }
- if (!updatedApps.isEmpty()) {
+ private void bindUpdatedShortcuts(final ArrayList<ShortcutInfo> updatedShortcuts,
+ UserHandleCompat user) {
+ if (!updatedShortcuts.isEmpty()) {
+ final Callbacks callbacks = getCallback();
+ final UserHandleCompat userFinal = user;
mHandler.post(new Runnable() {
public void run() {
Callbacks cb = getCallback();
if (cb != null && callbacks == cb) {
- cb.bindAppsUpdated(updatedApps);
+ cb.bindShortcutsChanged(updatedShortcuts,
+ new ArrayList<ShortcutInfo>(), userFinal);
}
}
});
}
}
- void enqueuePackageUpdated(PackageUpdatedTask task) {
+ void enqueueItemUpdatedTask(Runnable task) {
sWorker.post(task);
}
@@ -2712,11 +2906,11 @@ public class LauncherModel extends BroadcastReceiver
}
}
if (!packagesRemoved.isEmpty()) {
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
+ enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
}
if (!packagesUnavailable.isEmpty()) {
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
+ enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
}
}
@@ -3074,6 +3268,60 @@ public class LauncherModel extends BroadcastReceiver
}
}
+ private class ShortcutsChangedTask implements Runnable {
+ private String mPackageName;
+ private List<ShortcutInfoCompat> mShortcuts;
+ private UserHandleCompat mUser;
+
+ public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+ UserHandleCompat user) {
+ mPackageName = packageName;
+ mShortcuts = shortcuts;
+ mUser = user;
+ }
+
+ @Override
+ public void run() {
+ mDeepShortcutManager.onShortcutsChanged(mShortcuts);
+
+ Map<String, ShortcutInfoCompat> idsToShortcuts = new HashMap<>();
+ for (ShortcutInfoCompat shortcut : mShortcuts) {
+ idsToShortcuts.put(shortcut.getId(), shortcut);
+ }
+
+ // Find ShortcutInfo's that have changed on the workspace.
+ MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+ for (ItemInfo itemInfo : sBgItemsIdMap) {
+ if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ ShortcutInfo si = (ShortcutInfo) itemInfo;
+ String shortcutId = si.getDeepShortcutId();
+ if (idsToShortcuts.containsKey(shortcutId)) {
+ idsToWorkspaceShortcutInfos.addToList(shortcutId, si);
+ }
+ }
+ }
+
+ // Update the workspace to reflect the changes to updated shortcuts residing on it.
+ List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
+ mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+ ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+ Context context = LauncherAppState.getInstance().getContext();
+ for (ShortcutInfoCompat fullDetails : shortcuts) {
+ List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+ .get(fullDetails.getId());
+ for (ShortcutInfo shortcutInfo : shortcutInfos) {
+ shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context, mLauncherApps);
+ updatedShortcutInfos.add(shortcutInfo);
+ }
+ }
+ bindUpdatedShortcuts(updatedShortcutInfos, mUser);
+
+ // Update the deep shortcut map, in case the list of ids has changed for an activity.
+ updateDeepShortcutMap(mPackageName, mShortcuts);
+ bindDeepShortcutMapOnMainThread();
+ }
+ }
+
private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
mHandler.post(new Runnable() {
@Override
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 52668d721..c213c8d77 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -211,6 +211,11 @@ public class LauncherSettings {
public static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
/**
+ * The gesture is an application created deep shortcut
+ */
+ public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
+
+ /**
* The appWidgetId of the widget
*
* <P>Type: INTEGER</P>
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index d051665f3..63f49e0d5 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -16,20 +16,23 @@
package com.android.launcher3;
+import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.util.Log;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.folder.FolderIcon;
-
-import java.util.ArrayList;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
/**
* Represents a launchable icon on the workspaces and in folders.
@@ -274,6 +277,46 @@ public class ShortcutInfo extends ItemInfo {
return shortcut;
}
+ /**
+ * Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}. Pardon the overloaded name.
+ */
+ @TargetApi(Build.VERSION_CODES.N)
+ public static ShortcutInfo fromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo,
+ Context context, LauncherAppsCompat launcherApps) {
+ ShortcutInfo si = new ShortcutInfo();
+ si.user = shortcutInfo.getUserHandle();
+ si.itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ si.intent = shortcutInfo.makeIntent(context);
+ si.flags = 0;
+ si.updateFromDeepShortcutInfo(shortcutInfo, context, launcherApps);
+ return si;
+ }
+
+ public void updateFromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo,
+ Context context, LauncherAppsCompat launcherApps) {
+ title = shortcutInfo.getShortLabel();
+
+ CharSequence label = shortcutInfo.getLongLabel();
+ if (TextUtils.isEmpty(label)) {
+ label = shortcutInfo.getShortLabel();
+ }
+ this.contentDescription = UserManagerCompat.getInstance(context)
+ .getBadgedLabelForUser(label, user);
+
+ LauncherAppState launcherAppState = LauncherAppState.getInstance();
+ Drawable unbadgedIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, launcherAppState
+ .getInvariantDeviceProfile().fillResIconDpi);
+ Bitmap icon = unbadgedIcon == null ? null
+ : Utilities.createBadgedIconBitmap(unbadgedIcon, user, context);
+ setIcon(icon != null ? icon : launcherAppState.getIconCache().getDefaultIcon(user));
+ }
+
+ /** Returns the ShortcutInfo id associated with the deep shortcut. */
+ public String getDeepShortcutId() {
+ return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
+ intent.getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
+ }
+
@Override
public boolean isDisabled() {
return isDisabled != 0;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0e4fe8b19..47cf1237e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -59,7 +59,6 @@ import android.widget.TextView;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.Launcher.LauncherOverlay;
import com.android.launcher3.UninstallDropTarget.DropTargetSource;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
@@ -2554,7 +2553,8 @@ public class Workspace extends PagedView
boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
boolean willBecomeShortcut =
(info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+ info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
+ info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
return (aboveShortcut && willBecomeShortcut);
}
@@ -3488,6 +3488,7 @@ public class Workspace extends PagedView
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
if (info.container == NO_ID && info instanceof AppInfo) {
// Came from all apps -- make a copy
info = ((AppInfo) info).makeShortcut();
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 237a9e9fb..338106427 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -19,10 +19,13 @@ package com.android.launcher3.compat;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import com.android.launcher3.Utilities;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import java.util.List;
@@ -45,6 +48,8 @@ public abstract class LauncherAppsCompat {
void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
void onPackagesSuspended(String[] packageNames, UserHandleCompat user);
void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user);
+ void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+ UserHandleCompat user);
}
protected LauncherAppsCompat() {
@@ -56,7 +61,9 @@ public abstract class LauncherAppsCompat {
public static LauncherAppsCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.ATLEAST_LOLLIPOP) {
+ if (Utilities.isNycOrAbove()) {
+ sInstance = new LauncherAppsCompatVNMR1(context.getApplicationContext());
+ } else if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
} else {
sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
@@ -79,4 +86,11 @@ public abstract class LauncherAppsCompat {
public abstract boolean isActivityEnabledForProfile(ComponentName component,
UserHandleCompat user);
public abstract boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user);
+ public abstract List<ShortcutInfoCompat> getShortcuts(LauncherApps.ShortcutQuery q,
+ UserHandleCompat userHandle);
+ public abstract void pinShortcuts(String packageName, List<String> pinnedIds,
+ UserHandleCompat userHandle);
+ public abstract void startShortcut(String packageName, String id, Rect sourceBounds,
+ Bundle startActivityOptions, UserHandleCompat user);
+ public abstract Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density);
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index 4e2fc055e..1a144e859 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -22,15 +22,18 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import com.android.launcher3.Utilities;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Thunk;
@@ -130,6 +133,29 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat {
return false;
}
+ @Override
+ public List<ShortcutInfoCompat> getShortcuts(LauncherApps.ShortcutQuery q,
+ UserHandleCompat userHandle) {
+ return null;
+ }
+
+ @Override
+ public void pinShortcuts(String packageName, List<String> pinnedIds,
+ UserHandleCompat userHandle) {
+ // Not supported, so do nothing.
+ }
+
+ @Override
+ public void startShortcut(String packageName, String id, Rect sourceBounds,
+ Bundle startActivityOptions, UserHandleCompat user) {
+ // Not supported, so do nothing.
+ }
+
+ @Override
+ public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
+ return null;
+ }
+
private void unregisterForPackageIntents() {
mContext.unregisterReceiver(mPackageMonitor);
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 7270d023b..d97bf2f74 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -22,11 +22,14 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -34,7 +37,7 @@ import java.util.List;
import java.util.Map;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class LauncherAppsCompatVL extends LauncherAppsCompat {
+public class LauncherAppsCompatVL extends LauncherAppsCompatV16 {
protected LauncherApps mLauncherApps;
@@ -42,7 +45,7 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat {
= new HashMap<OnAppsChangedCallbackCompat, WrappedCallback>();
LauncherAppsCompatVL(Context context) {
- super();
+ super(context);
mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
}
@@ -146,6 +149,18 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat {
public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user));
}
+
+ @Override
+ public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
+ UserHandle user) {
+ List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfo shortcutInfo : shortcuts) {
+ shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+ }
+
+ mCallback.onShortcutsChanged(packageName, shortcutInfoCompats,
+ UserHandleCompat.fromUser(user));
+ }
}
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java b/src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java
new file mode 100644
index 000000000..0c1db1385
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVNMR1.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(Build.VERSION_CODES.N)
+public class LauncherAppsCompatVNMR1 extends LauncherAppsCompatVL {
+
+ LauncherAppsCompatVNMR1(Context context) {
+ super(context);
+ }
+
+ @Override
+ public List<ShortcutInfoCompat> getShortcuts(LauncherApps.ShortcutQuery q,
+ UserHandleCompat userHandle) {
+ List<ShortcutInfo> shortcutInfos = mLauncherApps.getShortcuts(q, userHandle.getUser());
+ if (shortcutInfos == null) {
+ return null;
+ }
+ List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcutInfos.size());
+ for (ShortcutInfo shortcutInfo : shortcutInfos) {
+ shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+ }
+ return shortcutInfoCompats;
+ }
+
+ @Override
+ public void pinShortcuts(String packageName, List<String> pinnedIds,
+ UserHandleCompat userHandle) {
+ mLauncherApps.pinShortcuts(packageName, pinnedIds, userHandle.getUser());
+ }
+
+ @Override
+ public void startShortcut(String packageName, String id, Rect sourceBounds,
+ Bundle startActivityOptions, UserHandleCompat user) {
+ mLauncherApps.startShortcut(packageName, id, sourceBounds,
+ startActivityOptions, user.getUser());
+ }
+
+ @Override
+ public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
+ return mLauncherApps.getShortcutIconDrawable(shortcutInfo.getShortcutInfo(), density);
+ }
+
+ private static class WrappedCallback extends LauncherApps.Callback {
+ private OnAppsChangedCallbackCompat mCallback;
+
+ public WrappedCallback(OnAppsChangedCallbackCompat callback) {
+ mCallback = callback;
+ }
+
+ public void onPackageRemoved(String packageName, UserHandle user) {
+ mCallback.onPackageRemoved(packageName, UserHandleCompat.fromUser(user));
+ }
+
+ public void onPackageAdded(String packageName, UserHandle user) {
+ mCallback.onPackageAdded(packageName, UserHandleCompat.fromUser(user));
+ }
+
+ public void onPackageChanged(String packageName, UserHandle user) {
+ mCallback.onPackageChanged(packageName, UserHandleCompat.fromUser(user));
+ }
+
+ public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+ mCallback.onPackagesAvailable(packageNames, UserHandleCompat.fromUser(user), replacing);
+ }
+
+ public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ mCallback.onPackagesUnavailable(packageNames, UserHandleCompat.fromUser(user),
+ replacing);
+ }
+
+ public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+ mCallback.onPackagesSuspended(packageNames, UserHandleCompat.fromUser(user));
+ }
+
+ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user));
+ }
+
+ @Override
+ public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
+ UserHandle user) {
+ List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfo shortcutInfo : shortcuts) {
+ shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+ }
+
+ mCallback.onShortcutsChanged(packageName, shortcutInfoCompats,
+ UserHandleCompat.fromUser(user));
+ }
+ }
+}
+
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 93238de86..2035f9960 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -510,7 +510,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
// This is set to true in close(), but isn't reset to false until onDropCompleted(). This
- // leads to an consistent state if you drag out of the folder and drag back in without
+ // leads to an inconsistent state if you drag out of the folder and drag back in without
// dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
mDeleteFolderOnDropCompleted = false;
@@ -737,7 +737,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
final ItemInfo item = d.dragInfo;
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+ itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
+ itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
!isFull());
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d7952c570..d08cf548e 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -213,7 +213,8 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+ itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
+ itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
!mFolder.isFull() && item != mInfo && !mInfo.opened);
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 8e8e551eb..9d3399fc3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -641,10 +641,11 @@ public class GridSizeMigrationTask {
// calculate weight
switch (entry.itemType) {
case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_APPLICATION: {
verifyIntent(c.getString(indexIntent));
- entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
- ? WT_SHORTCUT : WT_APPLICATION;
+ entry.weight = entry.itemType == Favorites.ITEM_TYPE_APPLICATION ?
+ WT_APPLICATION : WT_SHORTCUT;
break;
}
case Favorites.ITEM_TYPE_FOLDER: {
@@ -715,10 +716,11 @@ public class GridSizeMigrationTask {
// calculate weight
switch (entry.itemType) {
case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_APPLICATION: {
verifyIntent(c.getString(indexIntent));
- entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
- ? WT_SHORTCUT : WT_APPLICATION;
+ entry.weight = entry.itemType == Favorites.ITEM_TYPE_APPLICATION ?
+ WT_APPLICATION : WT_SHORTCUT;
break;
}
case Favorites.ITEM_TYPE_APPWIDGET: {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
new file mode 100644
index 000000000..e2e06af9a
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class DeepShortcutManager {
+ private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_GET_DYNAMIC
+ | ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_MANIFEST;
+
+ private final LauncherAppsCompat mLauncherApps;
+
+ public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
+ mLauncherApps = LauncherAppsCompat.getInstance(context);
+ }
+
+ public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
+ // mShortcutCache.removeShortcuts(shortcuts);
+ }
+
+ /**
+ * Queries for the shortcuts with the package name and provided ids.
+ *
+ * This method is intended to get the full details for shortcuts when they are added or updated,
+ * because we only get "key" fields in onShortcutsChanged().
+ */
+ public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
+ List<String> shortcutIds, UserHandleCompat user) {
+ return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
+ }
+
+ /**
+ * Gets all the shortcuts associated with the given package and user.
+ */
+ public List<ShortcutInfoCompat> queryForAllAppShortcuts(ComponentName activity,
+ List<String> ids, UserHandleCompat user) {
+ return query(FLAG_GET_ALL, activity.getPackageName(), activity, ids, user);
+ }
+
+ /**
+ * Removes the given shortcut from the current list of pinned shortcuts.
+ * (Runs on background thread)
+ */
+ public void unpinShortcut(final ShortcutKey key) {
+ String packageName = key.componentName.getPackageName();
+ String id = key.id;
+ UserHandleCompat user = key.user;
+ List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
+ pinnedIds.remove(id);
+ mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
+ }
+
+ /**
+ * Adds the given shortcut to the current list of pinned shortcuts.
+ * (Runs on background thread)
+ */
+ public void pinShortcut(final ShortcutKey key) {
+ String packageName = key.componentName.getPackageName();
+ String id = key.id;
+ UserHandleCompat user = key.user;
+ List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
+ pinnedIds.add(id);
+ mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
+ }
+
+ /**
+ * Returns the id's of pinned shortcuts associated with the given package and user.
+ *
+ * If packageName is null, returns all pinned shortcuts regardless of package.
+ */
+ public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
+ UserHandleCompat user) {
+ return query(ShortcutQuery.FLAG_GET_PINNED, packageName, null, null, user);
+ }
+
+ public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
+ return query(FLAG_GET_ALL, null, null, null, user);
+ }
+
+ private List<String> extractIds(List<ShortcutInfoCompat> shortcuts) {
+ List<String> shortcutIds = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ shortcutIds.add(shortcut.getId());
+ }
+ return shortcutIds;
+ }
+
+ /**
+ * Query the system server for all the shortcuts matching the given parameters.
+ * If packageName == null, we query for all shortcuts with the passed flags, regardless of app.
+ *
+ * TODO: Use the cache to optimize this so we don't make an RPC every time.
+ */
+ private List<ShortcutInfoCompat> query(int flags, String packageName,
+ ComponentName activity, List<String> shortcutIds, UserHandleCompat user) {
+ ShortcutQuery q = new ShortcutQuery();
+ q.setQueryFlags(flags);
+ if (packageName != null) {
+ q.setPackage(packageName);
+ q.setActivity(activity);
+ q.setShortcutIds(shortcutIds);
+ }
+ return mLauncherApps.getShortcuts(q, user);
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutCache.java b/src/com/android/launcher3/shortcuts/ShortcutCache.java
new file mode 100644
index 000000000..fc118a86e
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutCache.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.LruCache;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Loads {@link ShortcutInfoCompat}s on demand (e.g. when launcher
+ * loads for pinned shortcuts and on long-press for dynamic shortcuts), and caches them
+ * for handful of apps in an LruCache while launcher lives.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class ShortcutCache {
+ private static final String TAG = "ShortcutCache";
+ private static final boolean LOGD = false;
+
+ private static final int CACHE_SIZE = 30; // Max number shortcuts we cache.
+
+ private LruCache<ShortcutKey, ShortcutInfoCompat> mCachedShortcuts;
+ // We always keep pinned shortcuts in the cache.
+ private HashMap<ShortcutKey, ShortcutInfoCompat> mPinnedShortcuts;
+
+ public ShortcutCache() {
+ mCachedShortcuts = new LruCache<>(CACHE_SIZE);
+ mPinnedShortcuts = new HashMap<>();
+ }
+
+ /**
+ * Removes shortcuts from the cache when shortcuts change for a given package.
+ *
+ * Returns a map of ids to their evicted shortcuts.
+ *
+ * @see android.content.pm.LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle).
+ */
+ public void removeShortcuts(List<ShortcutInfoCompat> shortcuts) {
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ ShortcutKey key = ShortcutKey.fromInfo(shortcut);
+ mCachedShortcuts.remove(key);
+ }
+ }
+
+ public ShortcutInfoCompat get(ShortcutKey key) {
+ if (mPinnedShortcuts.containsKey(key)) {
+ return mPinnedShortcuts.get(key);
+ }
+ return mCachedShortcuts.get(key);
+ }
+
+ public void put(ShortcutKey key, ShortcutInfoCompat shortcut) {
+ if (shortcut.isPinned()) {
+ mPinnedShortcuts.put(key, shortcut);
+ } else {
+ mCachedShortcuts.put(key, shortcut);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
new file mode 100644
index 000000000..8dbeaa741
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ComponentKey;
+
+/**
+ * Wrapper class for {@link android.content.pm.ShortcutInfo}, representing deep shortcuts into apps.
+ *
+ * Not to be confused with {@link com.android.launcher3.ShortcutInfo}.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class ShortcutInfoCompat {
+ private static final String INTENT_CATEGORY = "com.android.launcher3.DEEP_SHORTCUT";
+ public static final String EXTRA_SHORTCUT_ID = "shortcut_id";
+
+ private ShortcutInfo mShortcutInfo;
+
+ public ShortcutInfoCompat(ShortcutInfo shortcutInfo) {
+ mShortcutInfo = shortcutInfo;
+ }
+
+ @TargetApi(Build.VERSION_CODES.N)
+ public Intent makeIntent(Context context) {
+ long serialNumber = UserManagerCompat.getInstance(context)
+ .getSerialNumberForUser(getUserHandle());
+ return new Intent(Intent.ACTION_MAIN)
+ .addCategory(INTENT_CATEGORY)
+ .setComponent(getActivity())
+ .setPackage(getPackage())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ .putExtra(ItemInfo.EXTRA_PROFILE, serialNumber)
+ .putExtra(EXTRA_SHORTCUT_ID, getId());
+ }
+
+ public ShortcutInfo getShortcutInfo() {
+ return mShortcutInfo;
+ }
+
+ public String getPackage() {
+ return mShortcutInfo.getPackage();
+ }
+
+ public String getId() {
+ return mShortcutInfo.getId();
+ }
+
+ public CharSequence getShortLabel() {
+ return mShortcutInfo.getShortLabel();
+ }
+
+ public CharSequence getLongLabel() {
+ return mShortcutInfo.getLongLabel();
+ }
+
+ public long getLastChangedTimestamp() {
+ return mShortcutInfo.getLastChangedTimestamp();
+ }
+
+ public ComponentName getActivity() {
+ return mShortcutInfo.getActivity();
+ }
+
+ public UserHandleCompat getUserHandle() {
+ return UserHandleCompat.fromUser(mShortcutInfo.getUserHandle());
+ }
+
+ public boolean hasKeyFieldsOnly() {
+ return mShortcutInfo.hasKeyFieldsOnly();
+ }
+
+ public boolean isPinned() {
+ return mShortcutInfo.isPinned();
+ }
+
+ @Override
+ public String toString() {
+ return mShortcutInfo.toString();
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
new file mode 100644
index 000000000..c9d66eb7c
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -0,0 +1,30 @@
+package com.android.launcher3.shortcuts;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
+
+/**
+ * A key that uniquely identifies a shortcut using its package, id, and user handle.
+ */
+public class ShortcutKey extends ComponentKey {
+ final String id;
+
+ public ShortcutKey(String packageName, UserHandleCompat user, String id) {
+ // Use the id as the class name.
+ super(new ComponentName(packageName, id), user);
+ this.id = id;
+ }
+
+ public static ShortcutKey fromInfo(ShortcutInfoCompat shortcutInfo) {
+ return new ShortcutKey(shortcutInfo.getPackage(), shortcutInfo.getUserHandle(),
+ shortcutInfo.getId());
+ }
+
+ @Override
+ public String toString() {
+ return flattenToString(LauncherAppState.getInstance().getContext());
+ }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index df23abe08..7dbc0e7a8 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -29,6 +29,7 @@ import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
@@ -180,6 +181,12 @@ public class ManagedProfileHeuristic {
saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
}
}
+
+ @Override
+ public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
+ UserHandleCompat user) {
+ // Do nothing
+ }
}
/**
diff --git a/src/com/android/launcher3/util/MultiHashMap.java b/src/com/android/launcher3/util/MultiHashMap.java
new file mode 100644
index 000000000..f54ab8840
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiHashMap.java
@@ -0,0 +1,20 @@
+package com.android.launcher3.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A utility map from keys to an ArrayList of values.
+ */
+public class MultiHashMap<K, V> extends HashMap<K, ArrayList<V>> {
+ public void addToList(K key, V value) {
+ ArrayList<V> list = get(key);
+ if (list == null) {
+ list = new ArrayList<>();
+ list.add(value);
+ put(key, list);
+ } else {
+ list.add(value);
+ }
+ }
+}