OSDN Git Service

Trebuchet: Remove stats/tracking
[android-x86/packages-apps-Trebuchet.git] / src / com / android / launcher3 / LauncherModel.java
index b5922c6..2074ad6 100644 (file)
@@ -28,11 +28,13 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.Handler;
@@ -62,6 +64,9 @@ import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.Thunk;
+import cyanogenmod.providers.CMSettings;
+
+import com.android.launcher3.settings.SettingsProvider;
 
 import java.lang.ref.WeakReference;
 import java.net.URISyntaxException;
@@ -94,6 +99,7 @@ public class LauncherModel extends BroadcastReceiver
     public static final int LOADER_FLAG_NONE = 0;
     public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
     public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
+    public static final int LOADER_FLAG_RESIZE_GRID = 1 << 2;
 
     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
     private static final long INVALID_SCREEN_ID = -1L;
@@ -108,6 +114,8 @@ public class LauncherModel extends BroadcastReceiver
     @Thunk boolean mIsLoaderTaskRunning;
     @Thunk boolean mHasLoaderCompletedOnce;
 
+    private volatile boolean mFlushingWorkerThread;
+
     private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
 
     @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
@@ -203,8 +211,12 @@ public class LauncherModel extends BroadcastReceiver
         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
+        public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
         public void bindAllPackages(WidgetsModel model);
         public void bindSearchProviderChanged();
+        public void bindComponentsUnavailable(ArrayList<String> packageNames,
+                ArrayList<AppInfo> appInfos);
+        public void bindComponentsAvailable(ArrayList<ItemInfo> itemInfos);
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
         public void dumpLogsToLocalData();
@@ -433,6 +445,33 @@ public class LauncherModel extends BroadcastReceiver
             ArrayList<Long> workspaceScreens,
             ArrayList<Long> addedWorkspaceScreensFinal,
             int spanX, int spanY) {
+
+        // Preferred screen is the next one after the default.
+        long preferredScreenId = SettingsProvider.getLongCustomDefault(context,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID,
+                R.integer.preferences_interface_homescreen_id_default);
+        int preferredScreenIndex = 0;
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            if (workspaceScreens.get(i) == preferredScreenId) {
+                preferredScreenIndex = i + 1;
+                break;
+            }
+        }
+
+        return findSpaceForItem(context, workspaceScreens, addedWorkspaceScreensFinal,
+                spanX, spanY, preferredScreenIndex);
+    }
+
+    /**
+     * Find a position on the screen for the given size or adds a new screen. Checks
+     * preferredScreen first, and if no space is found then starts searching from the left.
+     * @return screenId and the coordinates for the item.
+     */
+    @Thunk Pair<Long, int[]> findSpaceForItem(
+            Context context,
+            ArrayList<Long> workspaceScreens,
+            ArrayList<Long> addedWorkspaceScreensFinal,
+            int spanX, int spanY, int preferredScreenIndex) {
         LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
 
         // Use sBgItemsIdMap as all the items are already loaded.
@@ -450,6 +489,12 @@ public class LauncherModel extends BroadcastReceiver
             }
         }
 
+        // If we have a zero-id screen then we skip over it.
+        boolean hasZero = false;
+        if (!workspaceScreens.isEmpty() && workspaceScreens.get(0) == 0) {
+            hasZero = true;
+        }
+
         // Find appropriate space for the item.
         long screenId = 0;
         int[] cordinates = new int[2];
@@ -457,7 +502,6 @@ public class LauncherModel extends BroadcastReceiver
 
         int screenCount = workspaceScreens.size();
         // First check the preferred screen.
-        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
         if (preferredScreenIndex < screenCount) {
             screenId = workspaceScreens.get(preferredScreenIndex);
             found = findNextAvailableIconSpaceInScreen(
@@ -466,7 +510,8 @@ public class LauncherModel extends BroadcastReceiver
 
         if (!found) {
             // Search on any of the screens starting from the first screen.
-            for (int screen = 1; screen < screenCount; screen++) {
+            int firstScreen = hasZero ? 1 : 0;
+            for (int screen = firstScreen; screen < screenCount; screen++) {
                 screenId = workspaceScreens.get(screen);
                 if (findNextAvailableIconSpaceInScreen(
                         screenItems.get(screenId), cordinates, spanX, spanY)) {
@@ -650,27 +695,27 @@ public class LauncherModel extends BroadcastReceiver
                         modelShortcut.spanX == shortcut.spanX &&
                         modelShortcut.spanY == shortcut.spanY &&
                         ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
-                        (modelShortcut.dropPos != null &&
-                                shortcut.dropPos != null &&
-                                modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
-                        modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
+                                (modelShortcut.dropPos != null &&
+                                        shortcut.dropPos != null &&
+                                        modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
+                                        modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
                     // For all intents and purposes, this is the same object
                     return;
                 }
-            }
 
-            // the modelItem needs to match up perfectly with item if our model is
-            // to be consistent with the database-- for now, just require
-            // modelItem == item or the equality check above
-            String msg = "item: " + ((item != null) ? item.toString() : "null") +
-                    "modelItem: " +
-                    ((modelItem != null) ? modelItem.toString() : "null") +
-                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
-            RuntimeException e = new RuntimeException(msg);
-            if (stackTrace != null) {
-                e.setStackTrace(stackTrace);
+                // the modelItem needs to match up perfectly with item if our model is
+                // to be consistent with the database-- for now, just require
+                // modelItem == item or the equality check above
+                String msg = "item: " + ((item != null) ? item.toString() : "null") +
+                        "modelItem: " +
+                        ((modelItem != null) ? modelItem.toString() : "null") +
+                        "Error: ItemInfo passed to checkItemInfo doesn't match original";
+                RuntimeException e = new RuntimeException(msg);
+                if (stackTrace != null) {
+                    e.setStackTrace(stackTrace);
+                }
+                throw e;
             }
-            throw e;
         }
     }
 
@@ -774,6 +819,35 @@ public class LauncherModel extends BroadcastReceiver
         }
     }
 
+    public void flushWorkerThread() {
+        mFlushingWorkerThread = true;
+        Runnable waiter = new Runnable() {
+                public void run() {
+                    synchronized (this) {
+                        notifyAll();
+                        mFlushingWorkerThread = false;
+                    }
+                }
+            };
+
+        synchronized(waiter) {
+            runOnWorkerThread(waiter);
+            if (mLoaderTask != null) {
+                synchronized(mLoaderTask) {
+                    mLoaderTask.notify();
+                }
+            }
+            boolean success = false;
+            while (!success) {
+                try {
+                    waiter.wait();
+                    success = true;
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
     /**
      * Move an item in the DB to a new <container, screen, cellX, cellY>
      */
@@ -950,6 +1024,8 @@ public class LauncherModel extends BroadcastReceiver
                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
                 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
+                final int hiddenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.HIDDEN);
+                final int subType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SUBTYPE);
 
                 FolderInfo folderInfo = null;
                 switch (c.getInt(itemTypeIndex)) {
@@ -966,6 +1042,8 @@ public class LauncherModel extends BroadcastReceiver
                 folderInfo.cellX = c.getInt(cellXIndex);
                 folderInfo.cellY = c.getInt(cellYIndex);
                 folderInfo.options = c.getInt(optionsIndex);
+                folderInfo.hidden = c.getInt(hiddenIndex) > 0;
+                folderInfo.subType = subType;
 
                 return folderInfo;
             }
@@ -977,10 +1055,21 @@ public class LauncherModel extends BroadcastReceiver
     }
 
     /**
+     * Saves the total widget count to a shared preference
+     *
+     * @param context {@link Context}
+     */
+    /* package */ static void saveWidgetCount(Context context) {
+        int widgetCount = LauncherModel.sBgAppWidgets.size();
+        SharedPreferences prefs = context.getSharedPreferences(LauncherAppState
+                        .getSharedPreferencesKey(), Context.MODE_PRIVATE);
+    }
+
+    /**
      * Add an item to the database in a specified container. Sets the container, screen, cellX and
      * cellY fields of the item. Also assigns an ID to the item.
      */
-    public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
+    public static void addItemToDatabase(final Context context, final ItemInfo item, final long container,
             final long screenId, final int cellX, final int cellY) {
         item.container = container;
         item.cellX = cellX;
@@ -1030,6 +1119,7 @@ public class LauncherModel extends BroadcastReceiver
                             break;
                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                             sBgAppWidgets.add((LauncherAppWidgetInfo) item);
+                            saveWidgetCount(context);
                             break;
                     }
                 }
@@ -1080,9 +1170,9 @@ public class LauncherModel extends BroadcastReceiver
     /**
      * Removes the specified items from the database
      * @param context
-     * @param item
+     * @param items
      */
-    static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
+    static void deleteItemsFromDatabase(final Context context, final ArrayList<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
         Runnable r = new Runnable() {
             public void run() {
@@ -1112,6 +1202,7 @@ public class LauncherModel extends BroadcastReceiver
                                 break;
                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                                 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
+                                saveWidgetCount(context);
                                 break;
                         }
                         sBgItemsIdMap.remove(item.id);
@@ -1123,10 +1214,21 @@ public class LauncherModel extends BroadcastReceiver
     }
 
     /**
+     * Saves the count of workspace pages
+     *
+     * @param context {@link Context}
+     */
+    /* package */ static void savePageCount(Context context) {
+        int pageCount = LauncherModel.sBgWorkspaceScreens.size();
+        SharedPreferences prefs = context.getSharedPreferences(LauncherAppState
+                        .getSharedPreferencesKey(), Context.MODE_PRIVATE);
+    }
+
+    /**
      * 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.
      */
-    public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+    public void updateWorkspaceScreenOrder(final Context context, final ArrayList<Long> screens) {
         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
         final ContentResolver cr = context.getContentResolver();
         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1164,6 +1266,7 @@ public class LauncherModel extends BroadcastReceiver
                 synchronized (sBgLock) {
                     sBgWorkspaceScreens.clear();
                     sBgWorkspaceScreens.addAll(screensCopy);
+                    savePageCount(context);
                 }
             }
         };
@@ -1612,7 +1715,7 @@ public class LauncherModel extends BroadcastReceiver
 
         // check & update map of what's occupied; used to discard overlapping/invalid items
         private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
-                   ArrayList<Long> workspaceScreens) {
+                   ArrayList<Long> workspaceScreens, boolean shouldResizeAndUpdateDB) {
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
             final int countX = profile.numColumns;
@@ -1668,38 +1771,130 @@ public class LauncherModel extends BroadcastReceiver
                 return true;
             }
 
-            if (!occupied.containsKey(item.screenId)) {
-                ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
-                occupied.put(item.screenId, items);
+            // If the current item's position lies outside of the bounds
+            // of the current grid size, attempt to place it in the next
+            // available position.
+            if (item.cellX < 0 || item.cellY < 0 || item.cellX + item.spanX > countX
+                    || item.cellY + item.spanY > countY) {
+                // If we won't be resizing the grid, then just return, this item does not fit.
+                if (!shouldResizeAndUpdateDB) {
+                    Log.e(TAG, "Error loading shortcut " + item
+                            + " into cell (" + containerIndex + "-" + item.screenId + ":"
+                            + item.cellX + "," + item.cellY
+                            + ") out of screen bounds ( " + countX + "x" + countY + ")");
+                    return false;
+                }
+
+                if (item.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+                    // Place the item at 0 0 of screen 1
+                    // if items overlap here, they will be moved later on
+                    item.cellX = 0;
+                    item.cellY = 0;
+                    item.screenId = 1;
+                    item.wasMovedDueToReducedSpace = true;
+                    item.requiresDbUpdate = true;
+                } else {
+                    // see if widget can be shrunk to fit a screen, if not, just remove it
+                    if (item.minSpanX > countX || item.minSpanY > countY) {
+                        return false;
+                    }
+                    // if the widget is larger than the grid, shrink it down
+                    if (item.cellX + item.spanX > countX) {
+                        item.cellX = 0;
+                        item.spanY = (item.spanY / 2) > 0 ? item.spanY / 2 : 1;
+                        item.spanX = item.minSpanX;
+                        item.requiresDbUpdate = true;
+                        item.wasMovedDueToReducedSpace = true;
+                    }
+                    if (item.cellY + item.spanY > countY) {
+                        item.cellY = 0;
+                        item.spanY = countY;
+                        item.requiresDbUpdate = true;
+                        item.wasMovedDueToReducedSpace = true;
+                    }
+                    if (item.cellY + item.spanY == countY && item.cellX + item.spanX == countX) {
+                        // if the widget is the size of the grid, make a screen all it's own.
+                        item.screenId = sBgWorkspaceScreens.size() + 1;
+                    }
+                }
+            } else {
+                item.wasMovedDueToReducedSpace = false;
+                item.requiresDbUpdate = true;
             }
 
-            final ItemInfo[][] screens = occupied.get(item.screenId);
-            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
-                    item.cellX < 0 || item.cellY < 0 ||
-                    item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
-                Log.e(TAG, "Error loading shortcut " + item
-                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
-                        + item.cellX + "," + item.cellY
-                        + ") out of screen bounds ( " + countX + "x" + countY + ")");
-                return false;
+            if (!occupied.containsKey(item.screenId)) {
+                ItemInfo[][] items = new ItemInfo[countX][countY];
+                occupied.put(item.screenId, items);
             }
+            ItemInfo[][] screens = occupied.get(item.screenId);
 
             // Check if any workspace icons overlap with each other
             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
                     if (screens[x][y] != null) {
-                        Log.e(TAG, "Error loading shortcut " + item
-                            + " into cell (" + containerIndex + "-" + item.screenId + ":"
-                            + x + "," + y
-                            + ") occupied by "
-                            + screens[x][y]);
-                        return false;
+                        if (!shouldResizeAndUpdateDB) {
+                            Log.e(TAG, "Error loading shortcut " + item
+                                    + " into cell (" + containerIndex + "-" + item.screenId + ":"
+                                    + x + "," + y
+                                    + ") occupied by "
+                                    + screens[x][y]);
+                            return false;
+                        }
+                        ItemInfo occupiedItem = screens[x][y];
+                        // If an item is overlapping another because one of them
+                        // was moved due to the size of the grid changing,
+                        // move the current item to a free spot past this one.
+                        if (occupiedItem.wasMovedDueToReducedSpace
+                                || item.wasMovedDueToReducedSpace) {
+                            // overlapping icon exists here
+                            // we must find a free space.
+                            boolean freeFound = false;
+                            int nextX = 0;
+                            int nextY = 0;
+                            while (!freeFound) {
+                                if (screens[nextX][nextY] == null) {
+                                    item.cellX = nextX;
+                                    item.cellY = nextY;
+                                    freeFound = true;
+                                } else {
+                                    if (nextX + item.spanX == countX) {
+                                        if (nextY + item.spanY == countY) {
+                                            // If we've reached the bottom of the page and are still
+                                            // searching, add a new page to place this item.
+                                            item.screenId += 1;
+                                            nextY = 0;
+                                            nextX = 0;
+                                            if (!occupied.containsKey(item.screenId)) {
+                                                ItemInfo[][] items = new ItemInfo[countX][countY];
+                                                occupied.put(item.screenId, items);
+                                            }
+                                            screens = occupied.get(item.screenId);
+                                        } else {
+                                            nextX = 0;
+                                            nextY++;
+                                        }
+                                    } else {
+                                        nextX++;
+                                    }
+                                }
+                            }
+                        }
                     }
                 }
             }
             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
                     screens[x][y] = item;
+                    if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+                            && shouldResizeAndUpdateDB) {
+                        // fill up the entire grid where the widget technically is
+                        for (int spanX = x; spanX < item.spanX; spanX++) {
+                            screens[spanX][y] = item;
+                            for (int spanY = y; spanY < item.spanX; spanY++) {
+                                screens[spanX][spanY] = item;
+                            }
+                        }
+                    }
                 }
             }
 
@@ -1733,6 +1928,8 @@ public class LauncherModel extends BroadcastReceiver
             int countX = profile.numColumns;
             int countY = profile.numRows;
 
+            boolean shouldResize = ((mFlags & LOADER_FLAG_RESIZE_GRID) != 0);
+
             if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
                 long migrationStartTime = System.currentTimeMillis();
                 Log.v(TAG, "Starting workspace migration after restore");
@@ -1816,6 +2013,9 @@ public class LauncherModel extends BroadcastReceiver
                             LauncherSettings.Favorites.PROFILE_ID);
                     final int optionsIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.OPTIONS);
+                    final int hiddenIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.HIDDEN);
+                    final int subTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SUBTYPE);
                     final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
 
                     final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
@@ -2044,7 +2244,8 @@ public class LauncherModel extends BroadcastReceiver
                                     }
 
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
+                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens,
+                                            shouldResize)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2093,9 +2294,12 @@ public class LauncherModel extends BroadcastReceiver
                                 folderInfo.spanX = 1;
                                 folderInfo.spanY = 1;
                                 folderInfo.options = c.getInt(optionsIndex);
+                                folderInfo.hidden = c.getInt(hiddenIndex) > 0;
+                                folderInfo.subType = c.getInt(subTypeIndex);
 
                                 // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
+                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens,
+                                        shouldResize)) {
                                     itemsToRemove.add(id);
                                     break;
                                 }
@@ -2222,7 +2426,8 @@ public class LauncherModel extends BroadcastReceiver
 
                                     appWidgetInfo.container = container;
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
+                                    if (!checkItemPlacement(occupied, appWidgetInfo,
+                                            sBgWorkspaceScreens, shouldResize)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2285,13 +2490,43 @@ public class LauncherModel extends BroadcastReceiver
                 for (FolderInfo folder : sBgFolders) {
                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                     int pos = 0;
+                    int needIndexing = 0;
                     for (ShortcutInfo info : folder.contents) {
-                        if (info.usingLowResIcon) {
+                        if (info.usingLowResIcon && pos < FolderIcon.NUM_ITEMS_IN_PREVIEW) {
                             info.updateIcon(mIconCache, false);
                         }
                         pos ++;
-                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
-                            break;
+                        needIndexing += info.screenId + info.cellX + info.cellY;
+                    }
+                    // If all screenId, cellX, and cellY are 0, then we assume they were all null.
+                    if (needIndexing == 0) {
+                        synchronized (sBgLock) {
+                            int curX = 0;
+                            int curY = 0;
+                            int folderScreenId = 0;
+                            int folderCount = folder.contents.size();
+                            Point point = Utilities
+                                    .caluclateFolderContentDimensions(folderCount, countX, countY);
+                            int maxX = point.x;
+                            int maxY = point.y;
+                            for (ShortcutInfo info : folder.contents) {
+                                ItemInfo itemInfo = sBgItemsIdMap.get(info.id);
+                                if (curY == maxY) { // New screen
+                                    curX = 0;
+                                    curY = 0;
+                                    folderScreenId++;
+                                }
+                                itemInfo.screenId = folderScreenId;
+                                itemInfo.cellX = curX;
+                                itemInfo.cellY = curY;
+                                LauncherModel.updateItemInDatabase(context, itemInfo);
+                                if (curX == maxX - 1) {
+                                    curX = 0;
+                                    curY++;
+                                } else {
+                                    curX++;
+                                }
+                            }
                         }
                     }
                 }
@@ -2327,6 +2562,17 @@ public class LauncherModel extends BroadcastReceiver
                     updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
                 }
 
+                // If any items have been shifted and require a DB update, update them in the DB.
+                if (shouldResize) {
+                    for (ItemInfo info : sBgWorkspaceItems) {
+                        if (info != null && info.requiresDbUpdate) {
+                            info.requiresDbUpdate = false;
+                            LauncherModel.modifyItemInDatabase(mContext, info, info.container,
+                                    info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
+                        }
+                    }
+                }
+
                 if (DEBUG_LOADERS) {
                     Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
                     Log.d(TAG, "workspace layout: ");
@@ -2488,6 +2734,111 @@ public class LauncherModel extends BroadcastReceiver
             runOnMainThread(r);
         }
 
+        private void removeHiddenAppsWorkspaceItems(
+                final ArrayList<ItemInfo> workspaceItems,
+                final ArrayList<LauncherAppWidgetInfo> appWidgets,
+                final LongArrayMap<FolderInfo> folders) {
+
+            // Get hidden apps
+            ArrayList<ComponentName> mHiddenApps = new ArrayList<ComponentName>();
+            ArrayList<String> mHiddenAppsPackages = new ArrayList<String>();
+            Context context = mApp.getContext();
+            String protectedComponents = CMSettings.Secure.getString(context.getContentResolver(),
+                    CMSettings.Secure.PROTECTED_COMPONENTS);
+            protectedComponents = protectedComponents == null ? "" : protectedComponents;
+            String[] flattened = protectedComponents.split("\\|");
+
+            for (String flat : flattened) {
+                ComponentName cmp = ComponentName.unflattenFromString(flat);
+                if (cmp != null) {
+                    mHiddenApps.add(cmp);
+                    mHiddenAppsPackages.add(cmp.getPackageName());
+                }
+            }
+
+            // Shortcuts
+            int N = workspaceItems.size() - 1;
+            for (int i = N; i >= 0; i--) {
+                final ItemInfo item = workspaceItems.get(i);
+                if (item instanceof ShortcutInfo) {
+                    ShortcutInfo shortcut = (ShortcutInfo)item;
+                    if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+                        if (mHiddenApps.contains(shortcut.intent.getComponent())) {
+                            LauncherModel.deleteItemFromDatabase(mContext, shortcut);
+                            workspaceItems.remove(i);
+                        }
+                    }
+                } else {
+                    // Only remove items from folders that aren't hidden
+                    final FolderInfo folder = (FolderInfo)item;
+                    List<ShortcutInfo> shortcuts = folder.contents;
+
+                    int NN = shortcuts.size() - 1;
+                    for (int j = NN; j >= 0; j--) {
+                        final ShortcutInfo sci = shortcuts.get(j);
+                        if (sci.intent != null && sci.intent.getComponent() != null) {
+                            if (!folder.hidden){
+                                if (mHiddenApps.contains(sci.intent.getComponent())) {
+                                    LauncherModel.deleteItemFromDatabase(mContext, sci);
+                                    Runnable r = new Runnable() {
+                                        public void run() {
+                                            folder.remove(sci);
+                                        }
+                                    };
+                                    runOnMainThread(r);
+                                }
+                            } else {
+                                if (!mHiddenApps.contains(sci.intent.getComponent())) {
+                                    LauncherModel.deleteItemFromDatabase(mContext, sci);
+                                    Runnable r = new Runnable() {
+                                        public void run() {
+                                            folder.remove(sci);
+                                        }
+                                    };
+                                    runOnMainThread(r);
+                                }
+                            }
+
+                        }
+                    }
+
+                    if (folder.contents.size() == 1 && !folder.hidden) {
+                        ShortcutInfo finalItem = folder.contents.get(0);
+                        finalItem.container = folder.container;
+                        LauncherModel.deleteItemFromDatabase(mContext, folder);
+                        // only replace this item back on the workspace if it's not protected
+                        // and not a remote folder.
+                        if (!mHiddenApps.contains(finalItem.intent.getComponent()) &&
+                                !folder.isRemote()) {
+                            LauncherModel.addOrMoveItemInDatabase(mContext, finalItem,
+                                    folder.container, folder.screenId, folder.cellX, folder.cellY);
+                            workspaceItems.add(finalItem);
+                        }
+                        workspaceItems.remove(i);
+                        folders.remove(Long.valueOf(item.id));
+
+                    // Remote folders are always empty on bind.
+                    } else if (folder.contents.size() == 0 && !folder.isRemote()) {
+                        LauncherModel.deleteFolderContentsFromDatabase(mContext, folder);
+                        workspaceItems.remove(i);
+                        folders.remove(Long.valueOf(item.id));
+                    }
+                }
+            }
+
+            // AppWidgets
+            N = appWidgets.size() - 1;
+            for (int i = N; i >= 0; i--) {
+                final LauncherAppWidgetInfo item = appWidgets.get(i);
+                if (item.providerName != null) {
+                    if (mHiddenAppsPackages.contains(item.providerName.getPackageName())) {
+                        LauncherModel.deleteItemFromDatabase(mContext, item);
+                        appWidgets.remove(i);
+                    }
+                }
+            }
+        }
+
         private void bindWorkspaceItems(final Callbacks oldCallbacks,
                 final ArrayList<ItemInfo> workspaceItems,
                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
@@ -2496,6 +2847,8 @@ public class LauncherModel extends BroadcastReceiver
 
             final boolean postOnMainThread = (deferredBindRunnables != null);
 
+            removeHiddenAppsWorkspaceItems(workspaceItems, appWidgets, folders);
+
             // Bind the workspace items
             int N = workspaceItems.size();
             for (int i = 0; i < N; i += ITEMS_CHUNK) {
@@ -3010,6 +3363,7 @@ public class LauncherModel extends BroadcastReceiver
 
             final String[] packages = mPackages;
             final int N = packages.length;
+            final ArrayList<String> unavailable = new ArrayList<String>();
             switch (mOp) {
                 case OP_ADD: {
                     for (int i=0; i<N; i++) {
@@ -3048,6 +3402,9 @@ public class LauncherModel extends BroadcastReceiver
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
                         mBgAllAppsList.removePackage(packages[i], mUser);
                         mApp.getWidgetCache().removePackage(packages[i], mUser);
+                        if (mOp == OP_UNAVAILABLE) {
+                            unavailable.add(packages[i]);
+                        }
                     }
                     break;
             }
@@ -3079,10 +3436,21 @@ public class LauncherModel extends BroadcastReceiver
                     new HashMap<ComponentName, AppInfo>();
 
             if (added != null) {
+                final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
                 addAppsToAllApps(context, added);
                 for (AppInfo ai : added) {
                     addedOrUpdatedApps.put(ai.componentName, ai);
                 }
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                        if (callbacks == cb && cb != null) {
+                            if (DEBUG_LOADERS) Log.d(TAG, "bindComponentsAvailable: " +
+                                    addedInfos.size());
+                            callbacks.bindComponentsAvailable(addedInfos);
+                        }
+                    }
+                });
             }
 
             if (modified != null) {
@@ -3256,6 +3624,17 @@ public class LauncherModel extends BroadcastReceiver
                 final int removeReason;
                 if (mOp == OP_UNAVAILABLE) {
                     removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
+                    // Call the packages-unavailable callback
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            if (callbacks == cb && cb != null) {
+                                if (DEBUG_LOADERS) Log.d(TAG, "bindComponentsUnavailable: " +
+                                        removedApps.size());
+                                callbacks.bindComponentsUnavailable(unavailable, removedApps);
+                            }
+                        }
+                    });
                 } else {
                     // Remove all the components associated with this package
                     for (String pn : removedPackageNames) {
@@ -3271,16 +3650,28 @@ public class LauncherModel extends BroadcastReceiver
 
                 // Remove any queued items from the install queue
                 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
-                // Call the components-removed callback
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindComponentsRemoved(
+                if (mOp == OP_UNAVAILABLE) {
+                    // Call the packages-unavailable callback
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                            Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+                            if (callbacks == cb && cb != null) {
+                                 callbacks.bindComponentsUnavailable(unavailable, removedApps);
+                            }
+                        }
+                    });
+                } else {
+                    // Call the components-removed callback
+                    mHandler.post(new Runnable() {
+                        public void run() {
+                             Callbacks cb = getCallback();
+                             if (callbacks == cb && cb != null) {
+                                 callbacks.bindComponentsRemoved(
                                     removedPackageNames, removedApps, mUser, removeReason);
+                             }
                         }
-                    }
-                });
+                    });
+                }
             }
 
             // Update widgets
@@ -3305,9 +3696,12 @@ public class LauncherModel extends BroadcastReceiver
                     // Refresh widget list, if there is any newly added widget
                     PackageManager pm = context.getPackageManager();
                     for (String pkg : mPackages) {
-                        needToRefresh |= !pm.queryBroadcastReceivers(
+                        List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(
                                 new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
-                                    .setPackage(pkg), 0).isEmpty();
+                                        .setPackage(pkg), 0);
+                        if (resolveInfos != null) {
+                            needToRefresh |= !resolveInfos.isEmpty();
+                        }
                     }
                 }