diff options
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 3287015f4..1762ca4bd 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 ae1b6b8fa..c0c04a187 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -119,6 +119,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; @@ -2333,7 +2337,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); @@ -2361,7 +2365,6 @@ public class Launcher extends Activity if (deleteFromDb) { deleteWidgetInfo(widgetInfo); } - } else { return false; } @@ -2798,8 +2801,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); } @@ -2875,8 +2886,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())) { @@ -3688,6 +3700,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; @@ -4044,6 +4057,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 fe65b31fb..4bc76fb15 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -28,6 +28,8 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.ProviderConfig; 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; @@ -42,6 +44,7 @@ public class LauncherAppState { @Thunk final LauncherModel mModel; private final IconCache mIconCache; private final WidgetPreviewLoader mWidgetCache; + private final DeepShortcutManager mDeepShortcutManager; @Thunk boolean mWallpaperChangedSinceLastCheck; @@ -95,9 +98,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); @@ -168,6 +172,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 68a9c7e7f..8e404a74e 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -42,6 +42,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; @@ -60,6 +61,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; @@ -69,6 +73,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; @@ -83,6 +88,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; @@ -119,8 +125,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 @@ -135,6 +142,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 @@ -160,16 +170,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(); @@ -199,18 +214,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); @@ -679,6 +697,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); @@ -892,6 +911,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); @@ -903,6 +923,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); @@ -969,6 +997,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); @@ -986,6 +1022,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. */ @@ -1077,28 +1146,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)); } @@ -1106,7 +1175,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)); } @@ -1114,18 +1183,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. @@ -1146,7 +1221,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)); } @@ -1171,6 +1246,8 @@ public class LauncherModel extends BroadcastReceiver stopLoaderLocked(); if (resetAllAppsLoaded) mAllAppsLoaded = false; if (resetWorkspaceLoaded) mWorkspaceLoaded = false; + // Always reset deep shortcuts loaded. + mDeepShortcutsLoaded = false; } } @@ -1257,6 +1334,7 @@ public class LauncherModel extends BroadcastReceiver * - workspace icons * - widgets * - all apps icons + * - deep shortcuts within apps */ private class LoaderTask implements Runnable { private Context mContext; @@ -1388,6 +1466,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 @@ -1539,6 +1623,7 @@ public class LauncherModel extends BroadcastReceiver sBgFolders.clear(); sBgItemsIdMap.clear(); sBgWorkspaceScreens.clear(); + sBgPinnedShortcutCounts.clear(); } } @@ -1585,6 +1670,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); @@ -1635,6 +1721,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; @@ -1657,6 +1750,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); @@ -1819,7 +1913,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 @@ -2098,6 +2222,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); @@ -2629,6 +2762,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); @@ -2639,6 +2793,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. */ @@ -2664,34 +2852,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); } @@ -2719,11 +2913,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)); } } @@ -3081,6 +3275,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 24736d48e..1e28785b8 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; @@ -2575,7 +2574,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); } @@ -3509,6 +3509,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 9db79f061..cb1acaac2 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); + } + } +} |