Merge "Announce content desc of workspace immediately after unlocking the lock screen. b/22051420" into ub-launcher3-burnaby
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 873ca32..1fb8e8d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -93,8 +93,10 @@
             </intent-filter>
         </activity>
 
+        <!-- ENABLE_FOR_TESTING
+
         <activity
-            android:name="com.android.launcher3.LauncherExtension"
+            android:name="com.android.launcher3.testing.LauncherExtension"
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
             android:stateNotNeeded="true"
@@ -110,6 +112,8 @@
             </intent-filter>
         </activity>
 
+        -->
+
         <activity
             android:name="com.android.launcher3.ToggleWeightWatcher"
             android:label="@string/toggle_weight_watcher"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 440a537..88f149b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -167,10 +167,8 @@
     <string name="settings_button_text">Settings</string>
 
     <!-- Strings for settings -->
-    <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] [DO NOT TRANSLATE] -->
+    <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] -->
     <string name="allow_rotation_title">Allow rotation</string>
-    <!-- Summary for Allow Rotation setting. [CHAR LIMIT=150] [DO NOT TRANSLATE] -->
-    <string name="allow_rotation_summary">Allow rotation of the home screen</string>
 
     <!-- Label on an icon that references an uninstalled package, for which we have no information about when it might be installed. [CHAR_LIMIT=15] -->
     <string name="package_state_unknown">Unknown</string>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index f283575..624d9eb 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -19,7 +19,6 @@
     <SwitchPreference
             android:key="pref_allowRotation"
             android:title="@string/allow_rotation_title"
-            android:summary="@string/allow_rotation_summary"
             android:defaultValue="@bool/allow_rotation"
             android:persistent="true"
     />
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 0177fd4..2c4184d 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -170,10 +170,12 @@
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mIsDragging = false;
                 mTouchOffset = 0;
-                mPopup.animateVisibility(false);
-                animateScrollbar(false);
+                if (mIsDragging) {
+                    mIsDragging = false;
+                    mPopup.animateVisibility(false);
+                    animateScrollbar(false);
+                }
                 break;
         }
     }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 91b2428..14ad337 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -46,10 +46,12 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Thunk;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.Stack;
 
 /**
@@ -215,7 +217,7 @@
                 new String[] {packageName + "/%", Long.toString(userSerial)});
     }
 
-    public void updateDbIcons() {
+    public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
         // Remove all active icon update tasks.
         mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
 
@@ -231,7 +233,8 @@
 
             // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
             // is called by the icon cache when the job is complete.
-            updateDBIcons(user, apps);
+            updateDBIcons(user, apps, UserHandleCompat.myUserHandle().equals(user)
+                    ? ignorePackagesForMainUser : Collections.<String>emptySet());
         }
     }
 
@@ -240,7 +243,8 @@
      * the DB and are updated.
      * @return The set of packages for which icons have updated.
      */
-    private void updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps) {
+    private void updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps,
+            Set<String> ignorePackages) {
         long userSerial = mUserManager.getSerialNumberForUser(user);
         PackageManager pm = mContext.getPackageManager();
         HashMap<String, PackageInfo> pkgInfoMap = new HashMap<String, PackageInfo>();
@@ -275,8 +279,10 @@
             ComponentName component = ComponentName.unflattenFromString(cn);
             PackageInfo info = pkgInfoMap.get(component.getPackageName());
             if (info == null) {
-                remove(component, user);
-                itemsToRemove.add(c.getInt(rowIndex));
+                if (!ignorePackages.contains(component.getPackageName())) {
+                    remove(component, user);
+                    itemsToRemove.add(c.getInt(rowIndex));
+                }
                 continue;
             }
             if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 448cc1d..3fc8fc0 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -432,10 +432,4 @@
                 .fromResolveInfo(info, original.mContext);
         return new PendingInstallShortcutInfo(launcherInfo, original.mContext);
     }
-
-    public static boolean isLauncherActivity(Intent intent, Context context) {
-        Intent data = new Intent().putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
-        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
-        return convertToLauncherActivityIfPossible(info).isLuncherActivity();
-    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 210366e..a995f08 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3427,7 +3427,7 @@
     private void tryAndUpdatePredictedApps() {
         if (mLauncherCallbacks != null) {
             List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
-            if (!apps.isEmpty()) {
+            if (apps != null) {
                 mAppsView.setPredictedApps(apps);
             }
         }
@@ -4492,42 +4492,63 @@
         if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
     }
 
+    // TODO: These method should be a part of LauncherSearchCallback
     public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
-        // Called from search suggestion, not supported in other profiles.
-        final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+        // Called from search suggestion
+        return createAppDragInfo(appLaunchIntent, UserHandleCompat.myUserHandle());
+    }
+
+    // TODO: This method should be a part of LauncherSearchCallback
+    public ItemInfo createAppDragInfo(Intent appLaunchIntent, UserHandleCompat user) {
+        if (user == null) {
+            user = UserHandleCompat.myUserHandle();
+        }
+
+        // Called from search suggestion, add the profile extra to the intent to ensure that we
+        // can launch it correctly
+        long serialNumber = UserManagerCompat.getInstance(this).getSerialNumberForUser(user);
+        appLaunchIntent.putExtra(AppInfo.EXTRA_PROFILE, serialNumber);
         LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
         LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
-                myUser);
+                user);
         if (activityInfo == null) {
             return null;
         }
-        return new AppInfo(this, activityInfo, myUser, mIconCache);
+        return new AppInfo(this, activityInfo, user, mIconCache);
     }
 
+    // TODO: This method should be a part of LauncherSearchCallback
     public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
             Bitmap icon) {
-        // Called from search suggestion, not supported in other profiles.
+        // Called from search suggestion
         return createShortcutDragInfo(shortcutIntent, caption, icon,
                 UserHandleCompat.myUserHandle());
     }
 
+    // TODO: This method should be a part of LauncherSearchCallback
     public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
             Bitmap icon, UserHandleCompat user) {
+        if (user == null) {
+            user = UserHandleCompat.myUserHandle();
+        }
+
+        // Called from search suggestion
         UserManagerCompat userManager = UserManagerCompat.getInstance(this);
         CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
         return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
     }
 
-    protected void moveWorkspaceToDefaultScreen() {
-        mWorkspace.moveToDefaultScreen(false);
-    }
-
+    // TODO: This method should be a part of LauncherSearchCallback
     public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
         dragView.setTag(dragInfo);
         mWorkspace.onExternalDragStartedWithItem(dragView);
         mWorkspace.beginExternalDragShared(dragView, source);
     }
 
+    protected void moveWorkspaceToDefaultScreen() {
+        mWorkspace.moveToDefaultScreen(false);
+    }
+
     @Override
     public void onPageSwitch(View newPage, int newPageIndex) {
         if (mLauncherCallbacks != null) {
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index dc0bccc..f209736 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -771,22 +771,7 @@
         favorite.spanX = c.getInt(SPANX_INDEX);
         favorite.spanY = c.getInt(SPANY_INDEX);
         favorite.iconType = c.getInt(ICON_TYPE_INDEX);
-        if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
-            String iconPackage = c.getString(ICON_PACKAGE_INDEX);
-            if (!TextUtils.isEmpty(iconPackage)) {
-                favorite.iconPackage = iconPackage;
-            }
-            String iconResource = c.getString(ICON_RESOURCE_INDEX);
-            if (!TextUtils.isEmpty(iconResource)) {
-                favorite.iconResource = iconResource;
-            }
-        }
-        if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
-            byte[] blob = c.getBlob(ICON_INDEX);
-            if (blob != null && blob.length > 0) {
-                favorite.icon = blob;
-            }
-        }
+
         String title = c.getString(TITLE_INDEX);
         if (!TextUtils.isEmpty(title)) {
             favorite.title = title;
@@ -809,6 +794,22 @@
             if (!TextUtils.isEmpty(appWidgetProvider)) {
                 favorite.appWidgetProvider = appWidgetProvider;
             }
+        } else if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+            if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+                String iconPackage = c.getString(ICON_PACKAGE_INDEX);
+                if (!TextUtils.isEmpty(iconPackage)) {
+                    favorite.iconPackage = iconPackage;
+                }
+                String iconResource = c.getString(ICON_RESOURCE_INDEX);
+                if (!TextUtils.isEmpty(iconResource)) {
+                    favorite.iconResource = iconResource;
+                }
+            }
+
+            byte[] blob = c.getBlob(ICON_INDEX);
+            if (blob != null && blob.length > 0) {
+                favorite.icon = blob;
+            }
         }
 
         if (isReplaceableHotseatItem(favorite)) {
@@ -852,14 +853,16 @@
         values.put(Favorites.CELLY, favorite.cellY);
         values.put(Favorites.SPANX, favorite.spanX);
         values.put(Favorites.SPANY, favorite.spanY);
-        values.put(Favorites.ICON_TYPE, favorite.iconType);
-        if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
-            values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
-            values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
-        }
-        if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
+
+        if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
+            values.put(Favorites.ICON_TYPE, favorite.iconType);
+            if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+                values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
+                values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
+            }
             values.put(Favorites.ICON, favorite.icon);
         }
+
         if (!TextUtils.isEmpty(favorite.title)) {
             values.put(Favorites.TITLE, favorite.title);
         } else {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f09ad75..7414a22 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.Thunk;
@@ -903,11 +904,10 @@
     @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
         assertWorkspaceLoaded();
         final String intentWithPkg, intentWithoutPkg;
-        final String packageName;
         if (intent.getComponent() != null) {
             // If component is not null, an intent with null package will produce
             // the same result and should also be a match.
-            packageName = intent.getComponent().getPackageName();
+            String packageName = intent.getComponent().getPackageName();
             if (intent.getPackage() != null) {
                 intentWithPkg = intent.toUri(0);
                 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
@@ -918,15 +918,16 @@
         } else {
             intentWithPkg = intent.toUri(0);
             intentWithoutPkg = intent.toUri(0);
-            packageName = intent.getPackage();
         }
 
         synchronized (sBgLock) {
             for (ItemInfo item : sBgItemsIdMap) {
                 if (item instanceof ShortcutInfo) {
                     ShortcutInfo info = (ShortcutInfo) item;
-                    if (info.getIntent() != null && info.user.equals(user)) {
-                        String s = info.getIntent().toUri(0);
+                    Intent targetIntent = info.promisedIntent == null
+                            ? info.intent : info.promisedIntent;
+                    if (targetIntent != null && info.user.equals(user)) {
+                        String s = targetIntent.toUri(0);
                         if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
                             return true;
                         }
@@ -1793,13 +1794,6 @@
                             (LauncherSettings.Favorites.INTENT);
                     final int titleIndex = c.getColumnIndexOrThrow
                             (LauncherSettings.Favorites.TITLE);
-                    final int iconTypeIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.ICON_TYPE);
-                    final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
-                    final int iconPackageIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.ICON_PACKAGE);
-                    final int iconResourceIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.ICON_RESOURCE);
                     final int containerIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.CONTAINER);
                     final int itemTypeIndex = c.getColumnIndexOrThrow(
@@ -1826,6 +1820,7 @@
                             LauncherSettings.Favorites.PROFILE_ID);
                     final int optionsIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.OPTIONS);
+                    final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
 
                     final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
                     for (UserHandleCompat user : mUserManager.getUserProfiles()) {
@@ -1991,7 +1986,8 @@
                                 if (itemReplaced) {
                                     if (user.equals(UserHandleCompat.myUserHandle())) {
                                         info = getAppShortcutInfo(manager, intent, user, context, null,
-                                                iconIndex, titleIndex, false, useLowResIcon);
+                                                cursorIconInfo.iconIndex, titleIndex,
+                                                false, useLowResIcon);
                                     } else {
                                         // Don't replace items for other profiles.
                                         itemsToRemove.add(id);
@@ -2003,7 +1999,7 @@
                                                 "constructing info for partially restored package",
                                                 true);
                                         info = getRestoredItemInfo(c, titleIndex, intent,
-                                                promiseType);
+                                                promiseType, cursorIconInfo, context);
                                         intent = getRestoredItemIntent(c, context, intent);
                                     } else {
                                         // Don't restore items for other profiles.
@@ -2013,11 +2009,10 @@
                                 } else if (itemType ==
                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                     info = getAppShortcutInfo(manager, intent, user, context, c,
-                                            iconIndex, titleIndex, allowMissingTarget, useLowResIcon);
+                                            cursorIconInfo.iconIndex, titleIndex,
+                                            allowMissingTarget, useLowResIcon);
                                 } else {
-                                    info = getShortcutInfo(c, context, iconTypeIndex,
-                                            iconPackageIndex, iconResourceIndex, iconIndex,
-                                            titleIndex);
+                                    info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
 
                                     // App shortcuts that used to be automatically added to Launcher
                                     // didn't always have the correct intent flags set, so do that
@@ -2043,6 +2038,9 @@
                                     info.spanX = 1;
                                     info.spanY = 1;
                                     info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
+                                    if (info.promisedIntent != null) {
+                                        info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
+                                    }
                                     info.isDisabled = disabledState;
                                     if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
@@ -2687,7 +2685,7 @@
                         return;
                     }
                 }
-                mIconCache.updateDbIcons();
+                updateIconCache();
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
                         return;
@@ -2699,6 +2697,27 @@
             }
         }
 
+        private void updateIconCache() {
+            // Ignore packages which have a promise icon.
+            HashSet<String> packagesToIgnore = new HashSet<>();
+            synchronized (sBgLock) {
+                for (ItemInfo info : sBgItemsIdMap) {
+                    if (info instanceof ShortcutInfo) {
+                        ShortcutInfo si = (ShortcutInfo) info;
+                        if (si.isPromise() && si.getTargetComponent() != null) {
+                            packagesToIgnore.add(si.getTargetComponent().getPackageName());
+                        }
+                    } else if (info instanceof LauncherAppWidgetInfo) {
+                        LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
+                        if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
+                            packagesToIgnore.add(lawi.providerName.getPackageName());
+                        }
+                    }
+                }
+            }
+            mIconCache.updateDbIcons(packagesToIgnore);
+        }
+
         private void onlyBindAllApps() {
             final Callbacks oldCallbacks = mCallbacks.get();
             if (oldCallbacks == null) {
@@ -3360,20 +3379,27 @@
      * Make an ShortcutInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
      */
-    public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent,
-            int promiseType) {
+    public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
+            int promiseType, CursorIconInfo iconInfo, Context context) {
         final ShortcutInfo info = new ShortcutInfo();
         info.user = UserHandleCompat.myUserHandle();
-        mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
+
+        Bitmap icon = iconInfo.loadIcon(c, info, context);
+        // the fallback icon
+        if (icon == null) {
+            mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
+        } else {
+            info.setIcon(icon);
+        }
 
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
-            String title = (cursor != null) ? cursor.getString(titleIndex) : null;
+            String title = (c != null) ? c.getString(titleIndex) : null;
             if (!TextUtils.isEmpty(title)) {
                 info.title = Utilities.trim(title);
             }
         } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
             if (TextUtils.isEmpty(info.title)) {
-                info.title = (cursor != null) ? Utilities.trim(cursor.getString(titleIndex)) : "";
+                info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
             }
         } else {
             throw new InvalidParameterException("Invalid restoreType " + promiseType);
@@ -3506,10 +3532,7 @@
      * Make an ShortcutInfo object for a shortcut that isn't an application.
      */
     @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
-            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
-            int titleIndex) {
-
-        Bitmap icon = null;
+            int titleIndex, CursorIconInfo iconInfo) {
         final ShortcutInfo info = new ShortcutInfo();
         // Non-app shortcuts are only supported for current user.
         info.user = UserHandleCompat.myUserHandle();
@@ -3519,39 +3542,11 @@
 
         info.title = Utilities.trim(c.getString(titleIndex));
 
-        int iconType = c.getInt(iconTypeIndex);
-        switch (iconType) {
-        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
-            String packageName = c.getString(iconPackageIndex);
-            String resourceName = c.getString(iconResourceIndex);
-            info.customIcon = false;
-            // the resource
-            icon = Utilities.createIconBitmap(packageName, resourceName, context);
-            // the db
-            if (icon == null) {
-                icon = Utilities.createIconBitmap(c, iconIndex, context);
-            }
-            // the fallback icon
-            if (icon == null) {
-                icon = mIconCache.getDefaultIcon(info.user);
-                info.usingFallbackIcon = true;
-            }
-            break;
-        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
-            icon = Utilities.createIconBitmap(c, iconIndex, context);
-            if (icon == null) {
-                icon = mIconCache.getDefaultIcon(info.user);
-                info.customIcon = false;
-                info.usingFallbackIcon = true;
-            } else {
-                info.customIcon = true;
-            }
-            break;
-        default:
+        Bitmap icon = iconInfo.loadIcon(c, info, context);
+        // the fallback icon
+        if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
             info.usingFallbackIcon = true;
-            info.customIcon = false;
-            break;
         }
         info.setIcon(icon);
         return info;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 3ebc307..ee72aea 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -766,7 +766,7 @@
                         continue;
                     }
 
-                    if (!InstallShortcutReceiver.isLauncherActivity(intent, mContext)) {
+                    if (!Utilities.isLauncherAppTarget(intent)) {
                         continue;
                     }
 
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index a9a8216..56c0b9d 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -76,8 +76,9 @@
     /**
      * Indicates whether the icon comes from an application's resource (if false)
      * or from a custom Bitmap (if true.)
+     * TODO: remove this flag
      */
-    boolean customIcon;
+    public boolean customIcon;
 
     /**
      * Indicates whether we're using the default fallback icon instead of something from the
@@ -94,7 +95,7 @@
      * If isShortcut=true and customIcon=false, this contains a reference to the
      * shortcut icon as an application's resource.
      */
-    Intent.ShortcutIconResource iconResource;
+    public Intent.ShortcutIconResource iconResource;
 
     /**
      * The application icon.
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index af118d7..0d13c69 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -45,6 +45,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -62,6 +63,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Locale;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -676,14 +678,23 @@
      * @param launchIntent The intent that will be launched when the shortcut is clicked.
      */
     public static boolean isLauncherAppTarget(Intent launchIntent) {
-        return launchIntent != null
+        if (launchIntent != null
                 && Intent.ACTION_MAIN.equals(launchIntent.getAction())
                 && launchIntent.getComponent() != null
                 && launchIntent.getCategories() != null
                 && launchIntent.getCategories().size() == 1
                 && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
-                && launchIntent.getExtras() == null
-                && TextUtils.isEmpty(launchIntent.getDataString());
+                && TextUtils.isEmpty(launchIntent.getDataString())) {
+            // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
+            Bundle extras = launchIntent.getExtras();
+            if (extras == null) {
+                return true;
+            } else {
+                Set<String> keys = extras.keySet();
+                return keys.size() == 1 && keys.contains(ItemInfo.EXTRA_PROFILE);
+            }
+        };
+        return false;
     }
 
     public static float dpiFromPx(int size, DisplayMetrics metrics){
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 32b7be8..0651fb0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -417,7 +417,7 @@
                     new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
                             MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
 
-            mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
+            mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 874e895..988ecdd 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -21,13 +21,11 @@
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.BaseRecyclerViewFastScrollBar;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.Stats;
 import com.android.launcher3.util.Thunk;
 
@@ -57,7 +55,6 @@
     private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
     private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
 
-    private Launcher mLauncher;
     private ScrollPositionState mScrollPosState = new ScrollPositionState();
 
     public AllAppsRecyclerView(Context context) {
@@ -75,9 +72,6 @@
     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
-        // We have a theme on this view, so we need to coerce the base activity context from that
-        ContextThemeWrapper ctx = (ContextThemeWrapper) context;
-        mLauncher = (Launcher) ctx.getBaseContext();
     }
 
     /**
@@ -90,10 +84,9 @@
     /**
      * Sets the number of apps per row in this recycler view.
      */
-    public void setNumAppsPerRow(int numAppsPerRow) {
+    public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
         mNumAppsPerRow = numAppsPerRow;
 
-        DeviceProfile grid = mLauncher.getDeviceProfile();
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1);
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
index cd45d2c..c4b74d4 100644
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ b/src/com/android/launcher3/model/AppNameComparator.java
@@ -85,8 +85,10 @@
      */
     @Thunk int compareTitles(String titleA, String titleB) {
         // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
-        boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0));
-        boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0));
+        boolean aStartsWithLetter = (titleA.length() > 0) &&
+                Character.isLetterOrDigit(titleA.codePointAt(0));
+        boolean bStartsWithLetter = (titleB.length() > 0) &&
+                Character.isLetterOrDigit(titleB.codePointAt(0));
         if (aStartsWithLetter && !bStartsWithLetter) {
             return -1;
         } else if (!aStartsWithLetter && bStartsWithLetter) {
diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
similarity index 96%
rename from src/com/android/launcher3/LauncherExtension.java
rename to src/com/android/launcher3/testing/LauncherExtension.java
index 857ec57..b7a0729 100644
--- a/src/com/android/launcher3/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -1,7 +1,6 @@
-package com.android.launcher3;
+package com.android.launcher3.testing;
 
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.ComponentName;
@@ -11,6 +10,13 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherCallbacks;
+import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsSearchBarController;
 import com.android.launcher3.util.ComponentKey;
 
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
new file mode 100644
index 0000000..cdf9e3c
--- /dev/null
+++ b/src/com/android/launcher3/util/CursorIconInfo.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import android.content.Context;
+import android.content.Intent.ShortcutIconResource;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+
+/**
+ * Utility class to load icon from a cursor.
+ */
+public class CursorIconInfo {
+    public final int iconTypeIndex;
+    public final int iconPackageIndex;
+    public final int iconResourceIndex;
+    public final int iconIndex;
+
+    public CursorIconInfo(Cursor c) {
+        iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
+        iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+        iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
+        iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+    }
+
+    public Bitmap loadIcon(Cursor c, ShortcutInfo info, Context context) {
+        Bitmap icon = null;
+        int iconType = c.getInt(iconTypeIndex);
+        switch (iconType) {
+        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
+            String packageName = c.getString(iconPackageIndex);
+            String resourceName = c.getString(iconResourceIndex);
+            if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
+                info.iconResource = new ShortcutIconResource();
+                info.iconResource.packageName = packageName;
+                info.iconResource.resourceName = resourceName;
+                icon = Utilities.createIconBitmap(packageName, resourceName, context);
+            }
+            if (icon == null) {
+                // Failed to load from resource, try loading from DB.
+                icon = Utilities.createIconBitmap(c, iconIndex, context);
+            }
+            break;
+        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
+            icon = Utilities.createIconBitmap(c, iconIndex, context);
+            info.customIcon = icon != null;
+            break;
+        }
+        return icon;
+    }
+}