Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import android.app.SearchManager;
     20 import android.appwidget.AppWidgetProviderInfo;
     21 import android.content.BroadcastReceiver;
     22 import android.content.ComponentName;
     23 import android.content.ContentProviderOperation;
     24 import android.content.ContentResolver;
     25 import android.content.ContentValues;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.Intent.ShortcutIconResource;
     29 import android.content.IntentFilter;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.ProviderInfo;
     32 import android.content.pm.ResolveInfo;
     33 import android.database.Cursor;
     34 import android.graphics.Bitmap;
     35 import android.net.Uri;
     36 import android.os.Handler;
     37 import android.os.HandlerThread;
     38 import android.os.Looper;
     39 import android.os.Parcelable;
     40 import android.os.Process;
     41 import android.os.SystemClock;
     42 import android.provider.BaseColumns;
     43 import android.text.TextUtils;
     44 import android.util.Log;
     45 import android.util.LongSparseArray;
     46 import android.util.Pair;
     47 
     48 import com.android.launcher3.compat.AppWidgetManagerCompat;
     49 import com.android.launcher3.compat.LauncherActivityInfoCompat;
     50 import com.android.launcher3.compat.LauncherAppsCompat;
     51 import com.android.launcher3.compat.PackageInstallerCompat;
     52 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
     53 import com.android.launcher3.compat.UserHandleCompat;
     54 import com.android.launcher3.compat.UserManagerCompat;
     55 import com.android.launcher3.model.GridSizeMigrationTask;
     56 import com.android.launcher3.model.WidgetsModel;
     57 import com.android.launcher3.util.ComponentKey;
     58 import com.android.launcher3.util.CursorIconInfo;
     59 import com.android.launcher3.util.FlagOp;
     60 import com.android.launcher3.util.LongArrayMap;
     61 import com.android.launcher3.util.ManagedProfileHeuristic;
     62 import com.android.launcher3.util.PackageManagerHelper;
     63 import com.android.launcher3.util.StringFilter;
     64 import com.android.launcher3.util.Thunk;
     65 
     66 import java.lang.ref.WeakReference;
     67 import java.net.URISyntaxException;
     68 import java.security.InvalidParameterException;
     69 import java.util.ArrayList;
     70 import java.util.Arrays;
     71 import java.util.Collections;
     72 import java.util.Comparator;
     73 import java.util.HashMap;
     74 import java.util.HashSet;
     75 import java.util.Iterator;
     76 import java.util.List;
     77 import java.util.Map.Entry;
     78 import java.util.Set;
     79 
     80 /**
     81  * Maintains in-memory state of the Launcher. It is expected that there should be only one
     82  * LauncherModel object held in a static. Also provide APIs for updating the database state
     83  * for the Launcher.
     84  */
     85 public class LauncherModel extends BroadcastReceiver
     86         implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
     87     static final boolean DEBUG_LOADERS = false;
     88     private static final boolean DEBUG_RECEIVER = false;
     89     private static final boolean REMOVE_UNRESTORED_ICONS = true;
     90 
     91     static final String TAG = "Launcher.Model";
     92 
     93     public static final int LOADER_FLAG_NONE = 0;
     94     public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
     95     public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
     96 
     97     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
     98     private static final long INVALID_SCREEN_ID = -1L;
     99 
    100     private final boolean mOldContentProviderExists;
    101 
    102     @Thunk final LauncherAppState mApp;
    103     @Thunk final Object mLock = new Object();
    104     @Thunk DeferredHandler mHandler = new DeferredHandler();
    105     @Thunk LoaderTask mLoaderTask;
    106     @Thunk boolean mIsLoaderTaskRunning;
    107     @Thunk boolean mHasLoaderCompletedOnce;
    108 
    109     private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
    110 
    111     @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    112     static {
    113         sWorkerThread.start();
    114     }
    115     @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
    116 
    117     // We start off with everything not loaded.  After that, we assume that
    118     // our monitoring of the package manager provides all updates and we never
    119     // need to do a requery.  These are only ever touched from the loader thread.
    120     @Thunk boolean mWorkspaceLoaded;
    121     @Thunk boolean mAllAppsLoaded;
    122 
    123     // When we are loading pages synchronously, we can't just post the binding of items on the side
    124     // pages as this delays the rotation process.  Instead, we wait for a callback from the first
    125     // draw (in Workspace) to initiate the binding of the remaining side pages.  Any time we start
    126     // a normal load, we also clear this set of Runnables.
    127     static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
    128 
    129     /**
    130      * Set of runnables to be called on the background thread after the workspace binding
    131      * is complete.
    132      */
    133     static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
    134 
    135     @Thunk WeakReference<Callbacks> mCallbacks;
    136 
    137     // < only access in worker thread >
    138     private final AllAppsList mBgAllAppsList;
    139     // Entire list of widgets.
    140     private final WidgetsModel mBgWidgetsModel;
    141 
    142     // The lock that must be acquired before referencing any static bg data structures.  Unlike
    143     // other locks, this one can generally be held long-term because we never expect any of these
    144     // static data structures to be referenced outside of the worker thread except on the first
    145     // load after configuration change.
    146     static final Object sBgLock = new Object();
    147 
    148     // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
    149     // LauncherModel to their ids
    150     static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
    151 
    152     // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
    153     //       created by LauncherModel that are directly on the home screen (however, no widgets or
    154     //       shortcuts within folders).
    155     static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
    156 
    157     // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
    158     static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
    159         new ArrayList<LauncherAppWidgetInfo>();
    160 
    161     // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
    162     static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
    163 
    164     // sBgWorkspaceScreens is the ordered set of workspace screens.
    165     static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
    166 
    167     // sPendingPackages is a set of packages which could be on sdcard and are not available yet
    168     static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
    169             new HashMap<UserHandleCompat, HashSet<String>>();
    170 
    171     // </ only access in worker thread >
    172 
    173     @Thunk IconCache mIconCache;
    174 
    175     @Thunk final LauncherAppsCompat mLauncherApps;
    176     @Thunk final UserManagerCompat mUserManager;
    177 
    178     public interface Callbacks {
    179         public boolean setLoadOnResume();
    180         public int getCurrentWorkspaceScreen();
    181         public void startBinding();
    182         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
    183                               boolean forceAnimateIcons);
    184         public void bindScreens(ArrayList<Long> orderedScreenIds);
    185         public void bindAddScreens(ArrayList<Long> orderedScreenIds);
    186         public void bindFolders(LongArrayMap<FolderInfo> folders);
    187         public void finishBindingItems();
    188         public void bindAppWidget(LauncherAppWidgetInfo info);
    189         public void bindAllApplications(ArrayList<AppInfo> apps);
    190         public void bindAppsAdded(ArrayList<Long> newScreens,
    191                                   ArrayList<ItemInfo> addNotAnimated,
    192                                   ArrayList<ItemInfo> addAnimated,
    193                                   ArrayList<AppInfo> addedApps);
    194         public void bindAppsUpdated(ArrayList<AppInfo> apps);
    195         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
    196                 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
    197         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
    198         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
    199         public void bindWorkspaceComponentsRemoved(
    200                 HashSet<String> packageNames, HashSet<ComponentName> components,
    201                 UserHandleCompat user);
    202         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
    203         public void notifyWidgetProvidersChanged();
    204         public void bindWidgetsModel(WidgetsModel model);
    205         public void bindSearchProviderChanged();
    206         public boolean isAllAppsButtonRank(int rank);
    207         public void onPageBoundSynchronously(int page);
    208         public void dumpLogsToLocalData();
    209     }
    210 
    211     public interface ItemInfoFilter {
    212         public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
    213     }
    214 
    215     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
    216         Context context = app.getContext();
    217 
    218         String oldProvider = context.getString(R.string.old_launcher_provider_uri);
    219         // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
    220         // resource string.
    221         String redirectAuthority = Uri.parse(oldProvider).getAuthority();
    222         ProviderInfo providerInfo =
    223                 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
    224         ProviderInfo redirectProvider =
    225                 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
    226 
    227         Log.d(TAG, "Old launcher provider: " + oldProvider);
    228         mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
    229 
    230         if (mOldContentProviderExists) {
    231             Log.d(TAG, "Old launcher provider exists.");
    232         } else {
    233             Log.d(TAG, "Old launcher provider does not exist.");
    234         }
    235 
    236         mApp = app;
    237         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
    238         mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
    239         mIconCache = iconCache;
    240 
    241         mLauncherApps = LauncherAppsCompat.getInstance(context);
    242         mUserManager = UserManagerCompat.getInstance(context);
    243     }
    244 
    245     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
    246      * posted on the main thread handler. */
    247     @Thunk void runOnMainThread(Runnable r) {
    248         if (sWorkerThread.getThreadId() == Process.myTid()) {
    249             // If we are on the worker thread, post onto the main handler
    250             mHandler.post(r);
    251         } else {
    252             r.run();
    253         }
    254     }
    255 
    256     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
    257      * posted on the worker thread handler. */
    258     @Thunk static void runOnWorkerThread(Runnable r) {
    259         if (sWorkerThread.getThreadId() == Process.myTid()) {
    260             r.run();
    261         } else {
    262             // If we are not on the worker thread, then post to the worker handler
    263             sWorker.post(r);
    264         }
    265     }
    266 
    267     boolean canMigrateFromOldLauncherDb(Launcher launcher) {
    268         return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
    269     }
    270 
    271     public void setPackageState(final PackageInstallInfo installInfo) {
    272         Runnable updateRunnable = new Runnable() {
    273 
    274             @Override
    275             public void run() {
    276                 synchronized (sBgLock) {
    277                     final HashSet<ItemInfo> updates = new HashSet<>();
    278 
    279                     if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
    280                         // Ignore install success events as they are handled by Package add events.
    281                         return;
    282                     }
    283 
    284                     for (ItemInfo info : sBgItemsIdMap) {
    285                         if (info instanceof ShortcutInfo) {
    286                             ShortcutInfo si = (ShortcutInfo) info;
    287                             ComponentName cn = si.getTargetComponent();
    288                             if (si.isPromise() && (cn != null)
    289                                     && installInfo.packageName.equals(cn.getPackageName())) {
    290                                 si.setInstallProgress(installInfo.progress);
    291 
    292                                 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
    293                                     // Mark this info as broken.
    294                                     si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
    295                                 }
    296                                 updates.add(si);
    297                             }
    298                         }
    299                     }
    300 
    301                     for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
    302                         if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
    303                             widget.installProgress = installInfo.progress;
    304                             updates.add(widget);
    305                         }
    306                     }
    307 
    308                     if (!updates.isEmpty()) {
    309                         // Push changes to the callback.
    310                         Runnable r = new Runnable() {
    311                             public void run() {
    312                                 Callbacks callbacks = getCallback();
    313                                 if (callbacks != null) {
    314                                     callbacks.bindRestoreItemsChange(updates);
    315                                 }
    316                             }
    317                         };
    318                         mHandler.post(r);
    319                     }
    320                 }
    321             }
    322         };
    323         runOnWorkerThread(updateRunnable);
    324     }
    325 
    326     /**
    327      * Updates the icons and label of all pending icons for the provided package name.
    328      */
    329     public void updateSessionDisplayInfo(final String packageName) {
    330         Runnable updateRunnable = new Runnable() {
    331 
    332             @Override
    333             public void run() {
    334                 synchronized (sBgLock) {
    335                     final ArrayList<ShortcutInfo> updates = new ArrayList<>();
    336                     final UserHandleCompat user = UserHandleCompat.myUserHandle();
    337 
    338                     for (ItemInfo info : sBgItemsIdMap) {
    339                         if (info instanceof ShortcutInfo) {
    340                             ShortcutInfo si = (ShortcutInfo) info;
    341                             ComponentName cn = si.getTargetComponent();
    342                             if (si.isPromise() && (cn != null)
    343                                     && packageName.equals(cn.getPackageName())) {
    344                                 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
    345                                     // For auto install apps update the icon as well as label.
    346                                     mIconCache.getTitleAndIcon(si,
    347                                             si.promisedIntent, user,
    348                                             si.shouldUseLowResIcon());
    349                                 } else {
    350                                     // Only update the icon for restored apps.
    351                                     si.updateIcon(mIconCache);
    352                                 }
    353                                 updates.add(si);
    354                             }
    355                         }
    356                     }
    357 
    358                     if (!updates.isEmpty()) {
    359                         // Push changes to the callback.
    360                         Runnable r = new Runnable() {
    361                             public void run() {
    362                                 Callbacks callbacks = getCallback();
    363                                 if (callbacks != null) {
    364                                     callbacks.bindShortcutsChanged(updates,
    365                                             new ArrayList<ShortcutInfo>(), user);
    366                                 }
    367                             }
    368                         };
    369                         mHandler.post(r);
    370                     }
    371                 }
    372             }
    373         };
    374         runOnWorkerThread(updateRunnable);
    375     }
    376 
    377     public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
    378         final Callbacks callbacks = getCallback();
    379 
    380         if (allAppsApps == null) {
    381             throw new RuntimeException("allAppsApps must not be null");
    382         }
    383         if (allAppsApps.isEmpty()) {
    384             return;
    385         }
    386 
    387         // Process the newly added applications and add them to the database first
    388         Runnable r = new Runnable() {
    389             public void run() {
    390                 runOnMainThread(new Runnable() {
    391                     public void run() {
    392                         Callbacks cb = getCallback();
    393                         if (callbacks == cb && cb != null) {
    394                             callbacks.bindAppsAdded(null, null, null, allAppsApps);
    395                         }
    396                     }
    397                 });
    398             }
    399         };
    400         runOnWorkerThread(r);
    401     }
    402 
    403     private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
    404             int[] xy, int spanX, int spanY) {
    405         LauncherAppState app = LauncherAppState.getInstance();
    406         InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
    407         final int xCount = (int) profile.numColumns;
    408         final int yCount = (int) profile.numRows;
    409         boolean[][] occupied = new boolean[xCount][yCount];
    410         if (occupiedPos != null) {
    411             for (ItemInfo r : occupiedPos) {
    412                 int right = r.cellX + r.spanX;
    413                 int bottom = r.cellY + r.spanY;
    414                 for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
    415                     for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
    416                         occupied[x][y] = true;
    417                     }
    418                 }
    419             }
    420         }
    421         return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
    422     }
    423 
    424     /**
    425      * Find a position on the screen for the given size or adds a new screen.
    426      * @return screenId and the coordinates for the item.
    427      */
    428     @Thunk Pair<Long, int[]> findSpaceForItem(
    429             Context context,
    430             ArrayList<Long> workspaceScreens,
    431             ArrayList<Long> addedWorkspaceScreensFinal,
    432             int spanX, int spanY) {
    433         LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
    434 
    435         // Use sBgItemsIdMap as all the items are already loaded.
    436         assertWorkspaceLoaded();
    437         synchronized (sBgLock) {
    438             for (ItemInfo info : sBgItemsIdMap) {
    439                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    440                     ArrayList<ItemInfo> items = screenItems.get(info.screenId);
    441                     if (items == null) {
    442                         items = new ArrayList<>();
    443                         screenItems.put(info.screenId, items);
    444                     }
    445                     items.add(info);
    446                 }
    447             }
    448         }
    449 
    450         // Find appropriate space for the item.
    451         long screenId = 0;
    452         int[] cordinates = new int[2];
    453         boolean found = false;
    454 
    455         int screenCount = workspaceScreens.size();
    456         // First check the preferred screen.
    457         int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
    458         if (preferredScreenIndex < screenCount) {
    459             screenId = workspaceScreens.get(preferredScreenIndex);
    460             found = findNextAvailableIconSpaceInScreen(
    461                     screenItems.get(screenId), cordinates, spanX, spanY);
    462         }
    463 
    464         if (!found) {
    465             // Search on any of the screens starting from the first screen.
    466             for (int screen = 1; screen < screenCount; screen++) {
    467                 screenId = workspaceScreens.get(screen);
    468                 if (findNextAvailableIconSpaceInScreen(
    469                         screenItems.get(screenId), cordinates, spanX, spanY)) {
    470                     // We found a space for it
    471                     found = true;
    472                     break;
    473                 }
    474             }
    475         }
    476 
    477         if (!found) {
    478             // Still no position found. Add a new screen to the end.
    479             screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
    480 
    481             // Save the screen id for binding in the workspace
    482             workspaceScreens.add(screenId);
    483             addedWorkspaceScreensFinal.add(screenId);
    484 
    485             // If we still can't find an empty space, then God help us all!!!
    486             if (!findNextAvailableIconSpaceInScreen(
    487                     screenItems.get(screenId), cordinates, spanX, spanY)) {
    488                 throw new RuntimeException("Can't find space to add the item");
    489             }
    490         }
    491         return Pair.create(screenId, cordinates);
    492     }
    493 
    494     /**
    495      * Adds the provided items to the workspace.
    496      */
    497     public void addAndBindAddedWorkspaceItems(final Context context,
    498             final ArrayList<? extends ItemInfo> workspaceApps) {
    499         final Callbacks callbacks = getCallback();
    500         if (workspaceApps.isEmpty()) {
    501             return;
    502         }
    503         // Process the newly added applications and add them to the database first
    504         Runnable r = new Runnable() {
    505             public void run() {
    506                 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
    507                 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
    508 
    509                 // Get the list of workspace screens.  We need to append to this list and
    510                 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
    511                 // called.
    512                 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
    513                 synchronized(sBgLock) {
    514                     for (ItemInfo item : workspaceApps) {
    515                         if (item instanceof ShortcutInfo) {
    516                             // Short-circuit this logic if the icon exists somewhere on the workspace
    517                             if (shortcutExists(context, item.getIntent(), item.user)) {
    518                                 continue;
    519                             }
    520                         }
    521 
    522                         // Find appropriate space for the item.
    523                         Pair<Long, int[]> coords = findSpaceForItem(context,
    524                                 workspaceScreens, addedWorkspaceScreensFinal,
    525                                 1, 1);
    526                         long screenId = coords.first;
    527                         int[] cordinates = coords.second;
    528 
    529                         ItemInfo itemInfo;
    530                         if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
    531                             itemInfo = item;
    532                         } else if (item instanceof AppInfo) {
    533                             itemInfo = ((AppInfo) item).makeShortcut();
    534                         } else {
    535                             throw new RuntimeException("Unexpected info type");
    536                         }
    537 
    538                         // Add the shortcut to the db
    539                         addItemToDatabase(context, itemInfo,
    540                                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
    541                                 screenId, cordinates[0], cordinates[1]);
    542                         // Save the ShortcutInfo for binding in the workspace
    543                         addedShortcutsFinal.add(itemInfo);
    544                     }
    545                 }
    546 
    547                 // Update the workspace screens
    548                 updateWorkspaceScreenOrder(context, workspaceScreens);
    549 
    550                 if (!addedShortcutsFinal.isEmpty()) {
    551                     runOnMainThread(new Runnable() {
    552                         public void run() {
    553                             Callbacks cb = getCallback();
    554                             if (callbacks == cb && cb != null) {
    555                                 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
    556                                 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
    557                                 if (!addedShortcutsFinal.isEmpty()) {
    558                                     ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
    559                                     long lastScreenId = info.screenId;
    560                                     for (ItemInfo i : addedShortcutsFinal) {
    561                                         if (i.screenId == lastScreenId) {
    562                                             addAnimated.add(i);
    563                                         } else {
    564                                             addNotAnimated.add(i);
    565                                         }
    566                                     }
    567                                 }
    568                                 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
    569                                         addNotAnimated, addAnimated, null);
    570                             }
    571                         }
    572                     });
    573                 }
    574             }
    575         };
    576         runOnWorkerThread(r);
    577     }
    578 
    579     private void unbindItemInfosAndClearQueuedBindRunnables() {
    580         if (sWorkerThread.getThreadId() == Process.myTid()) {
    581             throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
    582                     "main thread");
    583         }
    584 
    585         // Clear any deferred bind runnables
    586         synchronized (mDeferredBindRunnables) {
    587             mDeferredBindRunnables.clear();
    588         }
    589 
    590         // Remove any queued UI runnables
    591         mHandler.cancelAll();
    592         // Unbind all the workspace items
    593         unbindWorkspaceItemsOnMainThread();
    594     }
    595 
    596     /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
    597     void unbindWorkspaceItemsOnMainThread() {
    598         // Ensure that we don't use the same workspace items data structure on the main thread
    599         // by making a copy of workspace items first.
    600         final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
    601         synchronized (sBgLock) {
    602             tmpItems.addAll(sBgWorkspaceItems);
    603             tmpItems.addAll(sBgAppWidgets);
    604         }
    605         Runnable r = new Runnable() {
    606                 @Override
    607                 public void run() {
    608                    for (ItemInfo item : tmpItems) {
    609                        item.unbind();
    610                    }
    611                 }
    612             };
    613         runOnMainThread(r);
    614     }
    615 
    616     /**
    617      * Adds an item to the DB if it was not created previously, or move it to a new
    618      * <container, screen, cellX, cellY>
    619      */
    620     static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
    621             long screenId, int cellX, int cellY) {
    622         if (item.container == ItemInfo.NO_ID) {
    623             // From all apps
    624             addItemToDatabase(context, item, container, screenId, cellX, cellY);
    625         } else {
    626             // From somewhere else
    627             moveItemInDatabase(context, item, container, screenId, cellX, cellY);
    628         }
    629     }
    630 
    631     static void checkItemInfoLocked(
    632             final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
    633         ItemInfo modelItem = sBgItemsIdMap.get(itemId);
    634         if (modelItem != null && item != modelItem) {
    635             // check all the data is consistent
    636             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
    637                 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
    638                 ShortcutInfo shortcut = (ShortcutInfo) item;
    639                 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
    640                         modelShortcut.intent.filterEquals(shortcut.intent) &&
    641                         modelShortcut.id == shortcut.id &&
    642                         modelShortcut.itemType == shortcut.itemType &&
    643                         modelShortcut.container == shortcut.container &&
    644                         modelShortcut.screenId == shortcut.screenId &&
    645                         modelShortcut.cellX == shortcut.cellX &&
    646                         modelShortcut.cellY == shortcut.cellY &&
    647                         modelShortcut.spanX == shortcut.spanX &&
    648                         modelShortcut.spanY == shortcut.spanY &&
    649                         ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
    650                         (modelShortcut.dropPos != null &&
    651                                 shortcut.dropPos != null &&
    652                                 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
    653                         modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
    654                     // For all intents and purposes, this is the same object
    655                     return;
    656                 }
    657             }
    658 
    659             // the modelItem needs to match up perfectly with item if our model is
    660             // to be consistent with the database-- for now, just require
    661             // modelItem == item or the equality check above
    662             String msg = "item: " + ((item != null) ? item.toString() : "null") +
    663                     "modelItem: " +
    664                     ((modelItem != null) ? modelItem.toString() : "null") +
    665                     "Error: ItemInfo passed to checkItemInfo doesn't match original";
    666             RuntimeException e = new RuntimeException(msg);
    667             if (stackTrace != null) {
    668                 e.setStackTrace(stackTrace);
    669             }
    670             throw e;
    671         }
    672     }
    673 
    674     static void checkItemInfo(final ItemInfo item) {
    675         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    676         final long itemId = item.id;
    677         Runnable r = new Runnable() {
    678             public void run() {
    679                 synchronized (sBgLock) {
    680                     checkItemInfoLocked(itemId, item, stackTrace);
    681                 }
    682             }
    683         };
    684         runOnWorkerThread(r);
    685     }
    686 
    687     static void updateItemInDatabaseHelper(Context context, final ContentValues values,
    688             final ItemInfo item, final String callingFunction) {
    689         final long itemId = item.id;
    690         final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
    691         final ContentResolver cr = context.getContentResolver();
    692 
    693         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    694         Runnable r = new Runnable() {
    695             public void run() {
    696                 cr.update(uri, values, null, null);
    697                 updateItemArrays(item, itemId, stackTrace);
    698             }
    699         };
    700         runOnWorkerThread(r);
    701     }
    702 
    703     static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
    704             final ArrayList<ItemInfo> items, final String callingFunction) {
    705         final ContentResolver cr = context.getContentResolver();
    706 
    707         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    708         Runnable r = new Runnable() {
    709             public void run() {
    710                 ArrayList<ContentProviderOperation> ops =
    711                         new ArrayList<ContentProviderOperation>();
    712                 int count = items.size();
    713                 for (int i = 0; i < count; i++) {
    714                     ItemInfo item = items.get(i);
    715                     final long itemId = item.id;
    716                     final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
    717                     ContentValues values = valuesList.get(i);
    718 
    719                     ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
    720                     updateItemArrays(item, itemId, stackTrace);
    721 
    722                 }
    723                 try {
    724                     cr.applyBatch(LauncherProvider.AUTHORITY, ops);
    725                 } catch (Exception e) {
    726                     e.printStackTrace();
    727                 }
    728             }
    729         };
    730         runOnWorkerThread(r);
    731     }
    732 
    733     static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
    734         // Lock on mBgLock *after* the db operation
    735         synchronized (sBgLock) {
    736             checkItemInfoLocked(itemId, item, stackTrace);
    737 
    738             if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
    739                     item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    740                 // Item is in a folder, make sure this folder exists
    741                 if (!sBgFolders.containsKey(item.container)) {
    742                     // An items container is being set to a that of an item which is not in
    743                     // the list of Folders.
    744                     String msg = "item: " + item + " container being set to: " +
    745                             item.container + ", not in the list of folders";
    746                     Log.e(TAG, msg);
    747                 }
    748             }
    749 
    750             // Items are added/removed from the corresponding FolderInfo elsewhere, such
    751             // as in Workspace.onDrop. Here, we just add/remove them from the list of items
    752             // that are on the desktop, as appropriate
    753             ItemInfo modelItem = sBgItemsIdMap.get(itemId);
    754             if (modelItem != null &&
    755                     (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
    756                      modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
    757                 switch (modelItem.itemType) {
    758                     case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
    759                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
    760                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
    761                         if (!sBgWorkspaceItems.contains(modelItem)) {
    762                             sBgWorkspaceItems.add(modelItem);
    763                         }
    764                         break;
    765                     default:
    766                         break;
    767                 }
    768             } else {
    769                 sBgWorkspaceItems.remove(modelItem);
    770             }
    771         }
    772     }
    773 
    774     /**
    775      * Move an item in the DB to a new <container, screen, cellX, cellY>
    776      */
    777     public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
    778             final long screenId, final int cellX, final int cellY) {
    779         item.container = container;
    780         item.cellX = cellX;
    781         item.cellY = cellY;
    782 
    783         // We store hotseat items in canonical form which is this orientation invariant position
    784         // in the hotseat
    785         if (context instanceof Launcher && screenId < 0 &&
    786                 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    787             item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
    788         } else {
    789             item.screenId = screenId;
    790         }
    791 
    792         final ContentValues values = new ContentValues();
    793         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
    794         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
    795         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
    796         values.put(LauncherSettings.Favorites.RANK, item.rank);
    797         values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
    798 
    799         updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
    800     }
    801 
    802     /**
    803      * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
    804      * cellX, cellY have already been updated on the ItemInfos.
    805      */
    806     static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
    807             final long container, final int screen) {
    808 
    809         ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
    810         int count = items.size();
    811 
    812         for (int i = 0; i < count; i++) {
    813             ItemInfo item = items.get(i);
    814             item.container = container;
    815 
    816             // We store hotseat items in canonical form which is this orientation invariant position
    817             // in the hotseat
    818             if (context instanceof Launcher && screen < 0 &&
    819                     container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    820                 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
    821                         item.cellY);
    822             } else {
    823                 item.screenId = screen;
    824             }
    825 
    826             final ContentValues values = new ContentValues();
    827             values.put(LauncherSettings.Favorites.CONTAINER, item.container);
    828             values.put(LauncherSettings.Favorites.CELLX, item.cellX);
    829             values.put(LauncherSettings.Favorites.CELLY, item.cellY);
    830             values.put(LauncherSettings.Favorites.RANK, item.rank);
    831             values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
    832 
    833             contentValues.add(values);
    834         }
    835         updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
    836     }
    837 
    838     /**
    839      * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
    840      */
    841     static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
    842             final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
    843         item.container = container;
    844         item.cellX = cellX;
    845         item.cellY = cellY;
    846         item.spanX = spanX;
    847         item.spanY = spanY;
    848 
    849         // We store hotseat items in canonical form which is this orientation invariant position
    850         // in the hotseat
    851         if (context instanceof Launcher && screenId < 0 &&
    852                 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    853             item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
    854         } else {
    855             item.screenId = screenId;
    856         }
    857 
    858         final ContentValues values = new ContentValues();
    859         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
    860         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
    861         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
    862         values.put(LauncherSettings.Favorites.RANK, item.rank);
    863         values.put(LauncherSettings.Favorites.SPANX, item.spanX);
    864         values.put(LauncherSettings.Favorites.SPANY, item.spanY);
    865         values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
    866 
    867         updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
    868     }
    869 
    870     /**
    871      * Update an item to the database in a specified container.
    872      */
    873     public static void updateItemInDatabase(Context context, final ItemInfo item) {
    874         final ContentValues values = new ContentValues();
    875         item.onAddToDatabase(context, values);
    876         updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
    877     }
    878 
    879     private void assertWorkspaceLoaded() {
    880         if (LauncherAppState.isDogfoodBuild()) {
    881             synchronized (mLock) {
    882                 if (!mHasLoaderCompletedOnce ||
    883                         (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
    884                     throw new RuntimeException("Trying to add shortcut while loader is running");
    885                 }
    886             }
    887         }
    888     }
    889 
    890     /**
    891      * Returns true if the shortcuts already exists on the workspace. This must be called after
    892      * the workspace has been loaded. We identify a shortcut by its intent.
    893      */
    894     @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
    895         assertWorkspaceLoaded();
    896         final String intentWithPkg, intentWithoutPkg;
    897         if (intent.getComponent() != null) {
    898             // If component is not null, an intent with null package will produce
    899             // the same result and should also be a match.
    900             String packageName = intent.getComponent().getPackageName();
    901             if (intent.getPackage() != null) {
    902                 intentWithPkg = intent.toUri(0);
    903                 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
    904             } else {
    905                 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
    906                 intentWithoutPkg = intent.toUri(0);
    907             }
    908         } else {
    909             intentWithPkg = intent.toUri(0);
    910             intentWithoutPkg = intent.toUri(0);
    911         }
    912 
    913         synchronized (sBgLock) {
    914             for (ItemInfo item : sBgItemsIdMap) {
    915                 if (item instanceof ShortcutInfo) {
    916                     ShortcutInfo info = (ShortcutInfo) item;
    917                     Intent targetIntent = info.promisedIntent == null
    918                             ? info.intent : info.promisedIntent;
    919                     if (targetIntent != null && info.user.equals(user)) {
    920                         String s = targetIntent.toUri(0);
    921                         if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
    922                             return true;
    923                         }
    924                     }
    925                 }
    926             }
    927         }
    928         return false;
    929     }
    930 
    931     /**
    932      * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
    933      */
    934     FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
    935         final ContentResolver cr = context.getContentResolver();
    936         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
    937                 "_id=? and (itemType=? or itemType=?)",
    938                 new String[] { String.valueOf(id),
    939                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
    940 
    941         try {
    942             if (c.moveToFirst()) {
    943                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
    944                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
    945                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
    946                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
    947                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
    948                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
    949                 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
    950 
    951                 FolderInfo folderInfo = null;
    952                 switch (c.getInt(itemTypeIndex)) {
    953                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
    954                         folderInfo = findOrMakeFolder(folderList, id);
    955                         break;
    956                 }
    957 
    958                 // Do not trim the folder label, as is was set by the user.
    959                 folderInfo.title = c.getString(titleIndex);
    960                 folderInfo.id = id;
    961                 folderInfo.container = c.getInt(containerIndex);
    962                 folderInfo.screenId = c.getInt(screenIndex);
    963                 folderInfo.cellX = c.getInt(cellXIndex);
    964                 folderInfo.cellY = c.getInt(cellYIndex);
    965                 folderInfo.options = c.getInt(optionsIndex);
    966 
    967                 return folderInfo;
    968             }
    969         } finally {
    970             c.close();
    971         }
    972 
    973         return null;
    974     }
    975 
    976     /**
    977      * Add an item to the database in a specified container. Sets the container, screen, cellX and
    978      * cellY fields of the item. Also assigns an ID to the item.
    979      */
    980     public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
    981             final long screenId, final int cellX, final int cellY) {
    982         item.container = container;
    983         item.cellX = cellX;
    984         item.cellY = cellY;
    985         // We store hotseat items in canonical form which is this orientation invariant position
    986         // in the hotseat
    987         if (context instanceof Launcher && screenId < 0 &&
    988                 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    989             item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
    990         } else {
    991             item.screenId = screenId;
    992         }
    993 
    994         final ContentValues values = new ContentValues();
    995         final ContentResolver cr = context.getContentResolver();
    996         item.onAddToDatabase(context, values);
    997 
    998         item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
    999         values.put(LauncherSettings.Favorites._ID, item.id);
   1000 
   1001         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
   1002         Runnable r = new Runnable() {
   1003             public void run() {
   1004                 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
   1005 
   1006                 // Lock on mBgLock *after* the db operation
   1007                 synchronized (sBgLock) {
   1008                     checkItemInfoLocked(item.id, item, stackTrace);
   1009                     sBgItemsIdMap.put(item.id, item);
   1010                     switch (item.itemType) {
   1011                         case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
   1012                             sBgFolders.put(item.id, (FolderInfo) item);
   1013                             // Fall through
   1014                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
   1015                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
   1016                             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
   1017                                     item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   1018                                 sBgWorkspaceItems.add(item);
   1019                             } else {
   1020                                 if (!sBgFolders.containsKey(item.container)) {
   1021                                     // Adding an item to a folder that doesn't exist.
   1022                                     String msg = "adding item: " + item + " to a folder that " +
   1023                                             " doesn't exist";
   1024                                     Log.e(TAG, msg);
   1025                                 }
   1026                             }
   1027                             break;
   1028                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
   1029                             sBgAppWidgets.add((LauncherAppWidgetInfo) item);
   1030                             break;
   1031                     }
   1032                 }
   1033             }
   1034         };
   1035         runOnWorkerThread(r);
   1036     }
   1037 
   1038     /**
   1039      * Creates a new unique child id, for a given cell span across all layouts.
   1040      */
   1041     static int getCellLayoutChildId(
   1042             long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
   1043         return (((int) container & 0xFF) << 24)
   1044                 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
   1045     }
   1046 
   1047     private static ArrayList<ItemInfo> getItemsByPackageName(
   1048             final String pn, final UserHandleCompat user) {
   1049         ItemInfoFilter filter  = new ItemInfoFilter() {
   1050             @Override
   1051             public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
   1052                 return cn.getPackageName().equals(pn) && info.user.equals(user);
   1053             }
   1054         };
   1055         return filterItemInfos(sBgItemsIdMap, filter);
   1056     }
   1057 
   1058     /**
   1059      * Removes all the items from the database corresponding to the specified package.
   1060      */
   1061     static void deletePackageFromDatabase(Context context, final String pn,
   1062             final UserHandleCompat user) {
   1063         deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
   1064     }
   1065 
   1066     /**
   1067      * Removes the specified item from the database
   1068      */
   1069     public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
   1070         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
   1071         items.add(item);
   1072         deleteItemsFromDatabase(context, items);
   1073     }
   1074 
   1075     /**
   1076      * Removes the specified items from the database
   1077      */
   1078     static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
   1079         final ContentResolver cr = context.getContentResolver();
   1080         Runnable r = new Runnable() {
   1081             public void run() {
   1082                 for (ItemInfo item : items) {
   1083                     final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
   1084                     cr.delete(uri, null, null);
   1085 
   1086                     // Lock on mBgLock *after* the db operation
   1087                     synchronized (sBgLock) {
   1088                         switch (item.itemType) {
   1089                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
   1090                                 sBgFolders.remove(item.id);
   1091                                 for (ItemInfo info: sBgItemsIdMap) {
   1092                                     if (info.container == item.id) {
   1093                                         // We are deleting a folder which still contains items that
   1094                                         // think they are contained by that folder.
   1095                                         String msg = "deleting a folder (" + item + ") which still " +
   1096                                                 "contains items (" + info + ")";
   1097                                         Log.e(TAG, msg);
   1098                                     }
   1099                                 }
   1100                                 sBgWorkspaceItems.remove(item);
   1101                                 break;
   1102                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
   1103                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
   1104                                 sBgWorkspaceItems.remove(item);
   1105                                 break;
   1106                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
   1107                                 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
   1108                                 break;
   1109                         }
   1110                         sBgItemsIdMap.remove(item.id);
   1111                     }
   1112                 }
   1113             }
   1114         };
   1115         runOnWorkerThread(r);
   1116     }
   1117 
   1118     /**
   1119      * Update the order of the workspace screens in the database. The array list contains
   1120      * a list of screen ids in the order that they should appear.
   1121      */
   1122     public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
   1123         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
   1124         final ContentResolver cr = context.getContentResolver();
   1125         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
   1126 
   1127         // Remove any negative screen ids -- these aren't persisted
   1128         Iterator<Long> iter = screensCopy.iterator();
   1129         while (iter.hasNext()) {
   1130             long id = iter.next();
   1131             if (id < 0) {
   1132                 iter.remove();
   1133             }
   1134         }
   1135 
   1136         Runnable r = new Runnable() {
   1137             @Override
   1138             public void run() {
   1139                 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
   1140                 // Clear the table
   1141                 ops.add(ContentProviderOperation.newDelete(uri).build());
   1142                 int count = screensCopy.size();
   1143                 for (int i = 0; i < count; i++) {
   1144                     ContentValues v = new ContentValues();
   1145                     long screenId = screensCopy.get(i);
   1146                     v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
   1147                     v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
   1148                     ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
   1149                 }
   1150 
   1151                 try {
   1152                     cr.applyBatch(LauncherProvider.AUTHORITY, ops);
   1153                 } catch (Exception ex) {
   1154                     throw new RuntimeException(ex);
   1155                 }
   1156 
   1157                 synchronized (sBgLock) {
   1158                     sBgWorkspaceScreens.clear();
   1159                     sBgWorkspaceScreens.addAll(screensCopy);
   1160                 }
   1161             }
   1162         };
   1163         runOnWorkerThread(r);
   1164     }
   1165 
   1166     /**
   1167      * Remove the specified folder and all its contents from the database.
   1168      */
   1169     public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
   1170         final ContentResolver cr = context.getContentResolver();
   1171 
   1172         Runnable r = new Runnable() {
   1173             public void run() {
   1174                 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
   1175                 // Lock on mBgLock *after* the db operation
   1176                 synchronized (sBgLock) {
   1177                     sBgItemsIdMap.remove(info.id);
   1178                     sBgFolders.remove(info.id);
   1179                     sBgWorkspaceItems.remove(info);
   1180                 }
   1181 
   1182                 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
   1183                         LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
   1184                 // Lock on mBgLock *after* the db operation
   1185                 synchronized (sBgLock) {
   1186                     for (ItemInfo childInfo : info.contents) {
   1187                         sBgItemsIdMap.remove(childInfo.id);
   1188                     }
   1189                 }
   1190             }
   1191         };
   1192         runOnWorkerThread(r);
   1193     }
   1194 
   1195     /**
   1196      * Set this as the current Launcher activity object for the loader.
   1197      */
   1198     public void initialize(Callbacks callbacks) {
   1199         synchronized (mLock) {
   1200             // Disconnect any of the callbacks and drawables associated with ItemInfos on the
   1201             // workspace to prevent leaking Launcher activities on orientation change.
   1202             unbindItemInfosAndClearQueuedBindRunnables();
   1203             mCallbacks = new WeakReference<Callbacks>(callbacks);
   1204         }
   1205     }
   1206 
   1207     @Override
   1208     public void onPackageChanged(String packageName, UserHandleCompat user) {
   1209         int op = PackageUpdatedTask.OP_UPDATE;
   1210         enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
   1211                 user));
   1212     }
   1213 
   1214     @Override
   1215     public void onPackageRemoved(String packageName, UserHandleCompat user) {
   1216         int op = PackageUpdatedTask.OP_REMOVE;
   1217         enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
   1218                 user));
   1219     }
   1220 
   1221     @Override
   1222     public void onPackageAdded(String packageName, UserHandleCompat user) {
   1223         int op = PackageUpdatedTask.OP_ADD;
   1224         enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
   1225                 user));
   1226     }
   1227 
   1228     @Override
   1229     public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
   1230             boolean replacing) {
   1231         enqueuePackageUpdated(
   1232                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
   1233     }
   1234 
   1235     @Override
   1236     public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
   1237             boolean replacing) {
   1238         if (!replacing) {
   1239             enqueuePackageUpdated(new PackageUpdatedTask(
   1240                     PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
   1241                     user));
   1242         }
   1243     }
   1244 
   1245     @Override
   1246     public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
   1247         enqueuePackageUpdated(new PackageUpdatedTask(
   1248                 PackageUpdatedTask.OP_SUSPEND, packageNames,
   1249                 user));
   1250     }
   1251 
   1252     @Override
   1253     public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
   1254         enqueuePackageUpdated(new PackageUpdatedTask(
   1255                 PackageUpdatedTask.OP_UNSUSPEND, packageNames,
   1256                 user));
   1257     }
   1258 
   1259     /**
   1260      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
   1261      * ACTION_PACKAGE_CHANGED.
   1262      */
   1263     @Override
   1264     public void onReceive(Context context, Intent intent) {
   1265         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
   1266 
   1267         final String action = intent.getAction();
   1268         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
   1269             // If we have changed locale we need to clear out the labels in all apps/workspace.
   1270             forceReload();
   1271         } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
   1272             Callbacks callbacks = getCallback();
   1273             if (callbacks != null) {
   1274                 callbacks.bindSearchProviderChanged();
   1275             }
   1276         } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
   1277                 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
   1278             UserManagerCompat.getInstance(context).enableAndResetCache();
   1279             forceReload();
   1280         } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
   1281                 LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
   1282             UserHandleCompat user = UserHandleCompat.fromIntent(intent);
   1283             if (user != null) {
   1284                 enqueuePackageUpdated(new PackageUpdatedTask(
   1285                         PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
   1286                         new String[0], user));
   1287             }
   1288         }
   1289     }
   1290 
   1291     void forceReload() {
   1292         resetLoadedState(true, true);
   1293 
   1294         // Do this here because if the launcher activity is running it will be restarted.
   1295         // If it's not running startLoaderFromBackground will merely tell it that it needs
   1296         // to reload.
   1297         startLoaderFromBackground();
   1298     }
   1299 
   1300     public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
   1301         synchronized (mLock) {
   1302             // Stop any existing loaders first, so they don't set mAllAppsLoaded or
   1303             // mWorkspaceLoaded to true later
   1304             stopLoaderLocked();
   1305             if (resetAllAppsLoaded) mAllAppsLoaded = false;
   1306             if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
   1307         }
   1308     }
   1309 
   1310     /**
   1311      * When the launcher is in the background, it's possible for it to miss paired
   1312      * configuration changes.  So whenever we trigger the loader from the background
   1313      * tell the launcher that it needs to re-run the loader when it comes back instead
   1314      * of doing it now.
   1315      */
   1316     public void startLoaderFromBackground() {
   1317         boolean runLoader = false;
   1318         Callbacks callbacks = getCallback();
   1319         if (callbacks != null) {
   1320             // Only actually run the loader if they're not paused.
   1321             if (!callbacks.setLoadOnResume()) {
   1322                 runLoader = true;
   1323             }
   1324         }
   1325         if (runLoader) {
   1326             startLoader(PagedView.INVALID_RESTORE_PAGE);
   1327         }
   1328     }
   1329 
   1330     /**
   1331      * If there is already a loader task running, tell it to stop.
   1332      */
   1333     private void stopLoaderLocked() {
   1334         LoaderTask oldTask = mLoaderTask;
   1335         if (oldTask != null) {
   1336             oldTask.stopLocked();
   1337         }
   1338     }
   1339 
   1340     public boolean isCurrentCallbacks(Callbacks callbacks) {
   1341         return (mCallbacks != null && mCallbacks.get() == callbacks);
   1342     }
   1343 
   1344     public void startLoader(int synchronousBindPage) {
   1345         startLoader(synchronousBindPage, LOADER_FLAG_NONE);
   1346     }
   1347 
   1348     public void startLoader(int synchronousBindPage, int loadFlags) {
   1349         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
   1350         InstallShortcutReceiver.enableInstallQueue();
   1351         synchronized (mLock) {
   1352             // Clear any deferred bind-runnables from the synchronized load process
   1353             // We must do this before any loading/binding is scheduled below.
   1354             synchronized (mDeferredBindRunnables) {
   1355                 mDeferredBindRunnables.clear();
   1356             }
   1357 
   1358             // Don't bother to start the thread if we know it's not going to do anything
   1359             if (mCallbacks != null && mCallbacks.get() != null) {
   1360                 // If there is already one running, tell it to stop.
   1361                 stopLoaderLocked();
   1362                 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
   1363                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
   1364                         && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
   1365                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
   1366                 } else {
   1367                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
   1368                     sWorker.post(mLoaderTask);
   1369                 }
   1370             }
   1371         }
   1372     }
   1373 
   1374     void bindRemainingSynchronousPages() {
   1375         // Post the remaining side pages to be loaded
   1376         if (!mDeferredBindRunnables.isEmpty()) {
   1377             Runnable[] deferredBindRunnables = null;
   1378             synchronized (mDeferredBindRunnables) {
   1379                 deferredBindRunnables = mDeferredBindRunnables.toArray(
   1380                         new Runnable[mDeferredBindRunnables.size()]);
   1381                 mDeferredBindRunnables.clear();
   1382             }
   1383             for (final Runnable r : deferredBindRunnables) {
   1384                 mHandler.post(r);
   1385             }
   1386         }
   1387     }
   1388 
   1389     public void stopLoader() {
   1390         synchronized (mLock) {
   1391             if (mLoaderTask != null) {
   1392                 mLoaderTask.stopLocked();
   1393             }
   1394         }
   1395     }
   1396 
   1397     /**
   1398      * Loads the workspace screen ids in an ordered list.
   1399      */
   1400     public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
   1401         final ContentResolver contentResolver = context.getContentResolver();
   1402         final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
   1403 
   1404         // Get screens ordered by rank.
   1405         final Cursor sc = contentResolver.query(screensUri, null, null, null,
   1406                 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
   1407         ArrayList<Long> screenIds = new ArrayList<Long>();
   1408         try {
   1409             final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
   1410             while (sc.moveToNext()) {
   1411                 try {
   1412                     screenIds.add(sc.getLong(idIndex));
   1413                 } catch (Exception e) {
   1414                     Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
   1415                             + " - invalid screens: " + e, true);
   1416                 }
   1417             }
   1418         } finally {
   1419             sc.close();
   1420         }
   1421         return screenIds;
   1422     }
   1423 
   1424     public boolean isAllAppsLoaded() {
   1425         return mAllAppsLoaded;
   1426     }
   1427 
   1428     /**
   1429      * Runnable for the thread that loads the contents of the launcher:
   1430      *   - workspace icons
   1431      *   - widgets
   1432      *   - all apps icons
   1433      */
   1434     private class LoaderTask implements Runnable {
   1435         private Context mContext;
   1436         @Thunk boolean mIsLoadingAndBindingWorkspace;
   1437         private boolean mStopped;
   1438         @Thunk boolean mLoadAndBindStepFinished;
   1439         private int mFlags;
   1440 
   1441         LoaderTask(Context context, int flags) {
   1442             mContext = context;
   1443             mFlags = flags;
   1444         }
   1445 
   1446         private void loadAndBindWorkspace() {
   1447             mIsLoadingAndBindingWorkspace = true;
   1448 
   1449             // Load the workspace
   1450             if (DEBUG_LOADERS) {
   1451                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
   1452             }
   1453 
   1454             if (!mWorkspaceLoaded) {
   1455                 loadWorkspace();
   1456                 synchronized (LoaderTask.this) {
   1457                     if (mStopped) {
   1458                         return;
   1459                     }
   1460                     mWorkspaceLoaded = true;
   1461                 }
   1462             }
   1463 
   1464             // Bind the workspace
   1465             bindWorkspace(-1);
   1466         }
   1467 
   1468         private void waitForIdle() {
   1469             // Wait until the either we're stopped or the other threads are done.
   1470             // This way we don't start loading all apps until the workspace has settled
   1471             // down.
   1472             synchronized (LoaderTask.this) {
   1473                 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   1474 
   1475                 mHandler.postIdle(new Runnable() {
   1476                         public void run() {
   1477                             synchronized (LoaderTask.this) {
   1478                                 mLoadAndBindStepFinished = true;
   1479                                 if (DEBUG_LOADERS) {
   1480                                     Log.d(TAG, "done with previous binding step");
   1481                                 }
   1482                                 LoaderTask.this.notify();
   1483                             }
   1484                         }
   1485                     });
   1486 
   1487                 while (!mStopped && !mLoadAndBindStepFinished) {
   1488                     try {
   1489                         // Just in case mFlushingWorkerThread changes but we aren't woken up,
   1490                         // wait no longer than 1sec at a time
   1491                         this.wait(1000);
   1492                     } catch (InterruptedException ex) {
   1493                         // Ignore
   1494                     }
   1495                 }
   1496                 if (DEBUG_LOADERS) {
   1497                     Log.d(TAG, "waited "
   1498                             + (SystemClock.uptimeMillis()-workspaceWaitTime)
   1499                             + "ms for previous step to finish binding");
   1500                 }
   1501             }
   1502         }
   1503 
   1504         void runBindSynchronousPage(int synchronousBindPage) {
   1505             if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
   1506                 // Ensure that we have a valid page index to load synchronously
   1507                 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
   1508                         "valid page index");
   1509             }
   1510             if (!mAllAppsLoaded || !mWorkspaceLoaded) {
   1511                 // Ensure that we don't try and bind a specified page when the pages have not been
   1512                 // loaded already (we should load everything asynchronously in that case)
   1513                 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
   1514             }
   1515             synchronized (mLock) {
   1516                 if (mIsLoaderTaskRunning) {
   1517                     // Ensure that we are never running the background loading at this point since
   1518                     // we also touch the background collections
   1519                     throw new RuntimeException("Error! Background loading is already running");
   1520                 }
   1521             }
   1522 
   1523             // XXX: Throw an exception if we are already loading (since we touch the worker thread
   1524             //      data structures, we can't allow any other thread to touch that data, but because
   1525             //      this call is synchronous, we can get away with not locking).
   1526 
   1527             // The LauncherModel is static in the LauncherAppState and mHandler may have queued
   1528             // operations from the previous activity.  We need to ensure that all queued operations
   1529             // are executed before any synchronous binding work is done.
   1530             mHandler.flush();
   1531 
   1532             // Divide the set of loaded items into those that we are binding synchronously, and
   1533             // everything else that is to be bound normally (asynchronously).
   1534             bindWorkspace(synchronousBindPage);
   1535             // XXX: For now, continue posting the binding of AllApps as there are other issues that
   1536             //      arise from that.
   1537             onlyBindAllApps();
   1538         }
   1539 
   1540         public void run() {
   1541             synchronized (mLock) {
   1542                 if (mStopped) {
   1543                     return;
   1544                 }
   1545                 mIsLoaderTaskRunning = true;
   1546             }
   1547             // Optimize for end-user experience: if the Launcher is up and // running with the
   1548             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
   1549             // workspace first (default).
   1550             keep_running: {
   1551                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
   1552                 loadAndBindWorkspace();
   1553 
   1554                 if (mStopped) {
   1555                     break keep_running;
   1556                 }
   1557 
   1558                 waitForIdle();
   1559 
   1560                 // second step
   1561                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
   1562                 loadAndBindAllApps();
   1563             }
   1564 
   1565             // Clear out this reference, otherwise we end up holding it until all of the
   1566             // callback runnables are done.
   1567             mContext = null;
   1568 
   1569             synchronized (mLock) {
   1570                 // If we are still the last one to be scheduled, remove ourselves.
   1571                 if (mLoaderTask == this) {
   1572                     mLoaderTask = null;
   1573                 }
   1574                 mIsLoaderTaskRunning = false;
   1575                 mHasLoaderCompletedOnce = true;
   1576             }
   1577         }
   1578 
   1579         public void stopLocked() {
   1580             synchronized (LoaderTask.this) {
   1581                 mStopped = true;
   1582                 this.notify();
   1583             }
   1584         }
   1585 
   1586         /**
   1587          * Gets the callbacks object.  If we've been stopped, or if the launcher object
   1588          * has somehow been garbage collected, return null instead.  Pass in the Callbacks
   1589          * object that was around when the deferred message was scheduled, and if there's
   1590          * a new Callbacks object around then also return null.  This will save us from
   1591          * calling onto it with data that will be ignored.
   1592          */
   1593         Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
   1594             synchronized (mLock) {
   1595                 if (mStopped) {
   1596                     return null;
   1597                 }
   1598 
   1599                 if (mCallbacks == null) {
   1600                     return null;
   1601                 }
   1602 
   1603                 final Callbacks callbacks = mCallbacks.get();
   1604                 if (callbacks != oldCallbacks) {
   1605                     return null;
   1606                 }
   1607                 if (callbacks == null) {
   1608                     Log.w(TAG, "no mCallbacks");
   1609                     return null;
   1610                 }
   1611 
   1612                 return callbacks;
   1613             }
   1614         }
   1615 
   1616         // check & update map of what's occupied; used to discard overlapping/invalid items
   1617         private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
   1618                    ArrayList<Long> workspaceScreens) {
   1619             LauncherAppState app = LauncherAppState.getInstance();
   1620             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
   1621             final int countX = profile.numColumns;
   1622             final int countY = profile.numRows;
   1623 
   1624             long containerIndex = item.screenId;
   1625             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   1626                 // Return early if we detect that an item is under the hotseat button
   1627                 if (mCallbacks == null ||
   1628                         mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
   1629                     Log.e(TAG, "Error loading shortcut into hotseat " + item
   1630                             + " into position (" + item.screenId + ":" + item.cellX + ","
   1631                             + item.cellY + ") occupied by all apps");
   1632                     return false;
   1633                 }
   1634 
   1635                 final ItemInfo[][] hotseatItems =
   1636                         occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
   1637 
   1638                 if (item.screenId >= profile.numHotseatIcons) {
   1639                     Log.e(TAG, "Error loading shortcut " + item
   1640                             + " into hotseat position " + item.screenId
   1641                             + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
   1642                             + ")");
   1643                     return false;
   1644                 }
   1645 
   1646                 if (hotseatItems != null) {
   1647                     if (hotseatItems[(int) item.screenId][0] != null) {
   1648                         Log.e(TAG, "Error loading shortcut into hotseat " + item
   1649                                 + " into position (" + item.screenId + ":" + item.cellX + ","
   1650                                 + item.cellY + ") occupied by "
   1651                                 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
   1652                                 [(int) item.screenId][0]);
   1653                             return false;
   1654                     } else {
   1655                         hotseatItems[(int) item.screenId][0] = item;
   1656                         return true;
   1657                     }
   1658                 } else {
   1659                     final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
   1660                     items[(int) item.screenId][0] = item;
   1661                     occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
   1662                     return true;
   1663                 }
   1664             } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
   1665                 if (!workspaceScreens.contains((Long) item.screenId)) {
   1666                     // The item has an invalid screen id.
   1667                     return false;
   1668                 }
   1669             } else {
   1670                 // Skip further checking if it is not the hotseat or workspace container
   1671                 return true;
   1672             }
   1673 
   1674             if (!occupied.containsKey(item.screenId)) {
   1675                 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
   1676                 occupied.put(item.screenId, items);
   1677             }
   1678 
   1679             final ItemInfo[][] screens = occupied.get(item.screenId);
   1680             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
   1681                     item.cellX < 0 || item.cellY < 0 ||
   1682                     item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
   1683                 Log.e(TAG, "Error loading shortcut " + item
   1684                         + " into cell (" + containerIndex + "-" + item.screenId + ":"
   1685                         + item.cellX + "," + item.cellY
   1686                         + ") out of screen bounds ( " + countX + "x" + countY + ")");
   1687                 return false;
   1688             }
   1689 
   1690             // Check if any workspace icons overlap with each other
   1691             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
   1692                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
   1693                     if (screens[x][y] != null) {
   1694                         Log.e(TAG, "Error loading shortcut " + item
   1695                             + " into cell (" + containerIndex + "-" + item.screenId + ":"
   1696                             + x + "," + y
   1697                             + ") occupied by "
   1698                             + screens[x][y]);
   1699                         return false;
   1700                     }
   1701                 }
   1702             }
   1703             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
   1704                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
   1705                     screens[x][y] = item;
   1706                 }
   1707             }
   1708 
   1709             return true;
   1710         }
   1711 
   1712         /** Clears all the sBg data structures */
   1713         private void clearSBgDataStructures() {
   1714             synchronized (sBgLock) {
   1715                 sBgWorkspaceItems.clear();
   1716                 sBgAppWidgets.clear();
   1717                 sBgFolders.clear();
   1718                 sBgItemsIdMap.clear();
   1719                 sBgWorkspaceScreens.clear();
   1720             }
   1721         }
   1722 
   1723         private void loadWorkspace() {
   1724             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   1725 
   1726             final Context context = mContext;
   1727             final ContentResolver contentResolver = context.getContentResolver();
   1728             final PackageManager manager = context.getPackageManager();
   1729             final boolean isSafeMode = manager.isSafeMode();
   1730             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
   1731             final boolean isSdCardReady = context.registerReceiver(null,
   1732                     new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
   1733 
   1734             LauncherAppState app = LauncherAppState.getInstance();
   1735             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
   1736             int countX = profile.numColumns;
   1737             int countY = profile.numRows;
   1738 
   1739             if (GridSizeMigrationTask.ENABLED &&
   1740                     !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
   1741                 // Migration failed. Clear workspace.
   1742                 mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
   1743             }
   1744 
   1745             if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
   1746                 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
   1747                 LauncherAppState.getLauncherProvider().deleteDatabase();
   1748             }
   1749 
   1750             if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
   1751                 // append the user's Launcher2 shortcuts
   1752                 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
   1753                 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
   1754             } else {
   1755                 // Make sure the default workspace is loaded
   1756                 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
   1757                 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
   1758             }
   1759 
   1760             synchronized (sBgLock) {
   1761                 clearSBgDataStructures();
   1762                 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
   1763                         .getInstance(mContext).updateAndGetActiveSessionCache();
   1764                 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
   1765 
   1766                 final ArrayList<Long> itemsToRemove = new ArrayList<>();
   1767                 final ArrayList<Long> restoredRows = new ArrayList<>();
   1768                 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
   1769                 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
   1770                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
   1771 
   1772                 // +1 for the hotseat (it can be larger than the workspace)
   1773                 // Load workspace in reverse order to ensure that latest items are loaded first (and
   1774                 // before any earlier duplicates)
   1775                 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
   1776                 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
   1777 
   1778                 try {
   1779                     final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
   1780                     final int intentIndex = c.getColumnIndexOrThrow
   1781                             (LauncherSettings.Favorites.INTENT);
   1782                     final int titleIndex = c.getColumnIndexOrThrow
   1783                             (LauncherSettings.Favorites.TITLE);
   1784                     final int containerIndex = c.getColumnIndexOrThrow(
   1785                             LauncherSettings.Favorites.CONTAINER);
   1786                     final int itemTypeIndex = c.getColumnIndexOrThrow(
   1787                             LauncherSettings.Favorites.ITEM_TYPE);
   1788                     final int appWidgetIdIndex = c.getColumnIndexOrThrow(
   1789                             LauncherSettings.Favorites.APPWIDGET_ID);
   1790                     final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
   1791                             LauncherSettings.Favorites.APPWIDGET_PROVIDER);
   1792                     final int screenIndex = c.getColumnIndexOrThrow(
   1793                             LauncherSettings.Favorites.SCREEN);
   1794                     final int cellXIndex = c.getColumnIndexOrThrow
   1795                             (LauncherSettings.Favorites.CELLX);
   1796                     final int cellYIndex = c.getColumnIndexOrThrow
   1797                             (LauncherSettings.Favorites.CELLY);
   1798                     final int spanXIndex = c.getColumnIndexOrThrow
   1799                             (LauncherSettings.Favorites.SPANX);
   1800                     final int spanYIndex = c.getColumnIndexOrThrow(
   1801                             LauncherSettings.Favorites.SPANY);
   1802                     final int rankIndex = c.getColumnIndexOrThrow(
   1803                             LauncherSettings.Favorites.RANK);
   1804                     final int restoredIndex = c.getColumnIndexOrThrow(
   1805                             LauncherSettings.Favorites.RESTORED);
   1806                     final int profileIdIndex = c.getColumnIndexOrThrow(
   1807                             LauncherSettings.Favorites.PROFILE_ID);
   1808                     final int optionsIndex = c.getColumnIndexOrThrow(
   1809                             LauncherSettings.Favorites.OPTIONS);
   1810                     final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
   1811 
   1812                     final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
   1813                     final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
   1814                     for (UserHandleCompat user : mUserManager.getUserProfiles()) {
   1815                         long serialNo = mUserManager.getSerialNumberForUser(user);
   1816                         allUsers.put(serialNo, user);
   1817                         quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
   1818                     }
   1819 
   1820                     ShortcutInfo info;
   1821                     String intentDescription;
   1822                     LauncherAppWidgetInfo appWidgetInfo;
   1823                     int container;
   1824                     long id;
   1825                     long serialNumber;
   1826                     Intent intent;
   1827                     UserHandleCompat user;
   1828                     String targetPackage;
   1829 
   1830                     while (!mStopped && c.moveToNext()) {
   1831                         try {
   1832                             int itemType = c.getInt(itemTypeIndex);
   1833                             boolean restored = 0 != c.getInt(restoredIndex);
   1834                             boolean allowMissingTarget = false;
   1835                             container = c.getInt(containerIndex);
   1836 
   1837                             switch (itemType) {
   1838                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
   1839                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
   1840                                 id = c.getLong(idIndex);
   1841                                 intentDescription = c.getString(intentIndex);
   1842                                 serialNumber = c.getInt(profileIdIndex);
   1843                                 user = allUsers.get(serialNumber);
   1844                                 int promiseType = c.getInt(restoredIndex);
   1845                                 int disabledState = 0;
   1846                                 boolean itemReplaced = false;
   1847                                 targetPackage = null;
   1848                                 if (user == null) {
   1849                                     // User has been deleted remove the item.
   1850                                     itemsToRemove.add(id);
   1851                                     continue;
   1852                                 }
   1853                                 try {
   1854                                     intent = Intent.parseUri(intentDescription, 0);
   1855                                     ComponentName cn = intent.getComponent();
   1856                                     if (cn != null && cn.getPackageName() != null) {
   1857                                         boolean validPkg = launcherApps.isPackageEnabledForProfile(
   1858                                                 cn.getPackageName(), user);
   1859                                         boolean validComponent = validPkg &&
   1860                                                 launcherApps.isActivityEnabledForProfile(cn, user);
   1861                                         if (validPkg) {
   1862                                             targetPackage = cn.getPackageName();
   1863                                         }
   1864 
   1865                                         if (validComponent) {
   1866                                             if (restored) {
   1867                                                 // no special handling necessary for this item
   1868                                                 restoredRows.add(id);
   1869                                                 restored = false;
   1870                                             }
   1871                                             if (quietMode.get(serialNumber)) {
   1872                                                 disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
   1873                                             }
   1874                                         } else if (validPkg) {
   1875                                             intent = null;
   1876                                             if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
   1877                                                 // We allow auto install apps to have their intent
   1878                                                 // updated after an install.
   1879                                                 intent = manager.getLaunchIntentForPackage(
   1880                                                         cn.getPackageName());
   1881                                                 if (intent != null) {
   1882                                                     ContentValues values = new ContentValues();
   1883                                                     values.put(LauncherSettings.Favorites.INTENT,
   1884                                                             intent.toUri(0));
   1885                                                     updateItem(id, values);
   1886                                                 }
   1887                                             }
   1888 
   1889                                             if (intent == null) {
   1890                                                 // The app is installed but the component is no
   1891                                                 // longer available.
   1892                                                 Launcher.addDumpLog(TAG,
   1893                                                         "Invalid component removed: " + cn, true);
   1894                                                 itemsToRemove.add(id);
   1895                                                 continue;
   1896                                             } else {
   1897                                                 // no special handling necessary for this item
   1898                                                 restoredRows.add(id);
   1899                                                 restored = false;
   1900                                             }
   1901                                         } else if (restored) {
   1902                                             // Package is not yet available but might be
   1903                                             // installed later.
   1904                                             Launcher.addDumpLog(TAG,
   1905                                                     "package not yet restored: " + cn, true);
   1906 
   1907                                             if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
   1908                                                 // Restore has started once.
   1909                                             } else if (installingPkgs.containsKey(cn.getPackageName())) {
   1910                                                 // App restore has started. Update the flag
   1911                                                 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
   1912                                                 ContentValues values = new ContentValues();
   1913                                                 values.put(LauncherSettings.Favorites.RESTORED,
   1914                                                         promiseType);
   1915                                                 updateItem(id, values);
   1916                                             } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
   1917                                                 // This is a common app. Try to replace this.
   1918                                                 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
   1919                                                 CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
   1920                                                 if (parser.findDefaultApp()) {
   1921                                                     // Default app found. Replace it.
   1922                                                     intent = parser.parsedIntent;
   1923                                                     cn = intent.getComponent();
   1924                                                     ContentValues values = parser.parsedValues;
   1925                                                     values.put(LauncherSettings.Favorites.RESTORED, 0);
   1926                                                     updateItem(id, values);
   1927                                                     restored = false;
   1928                                                     itemReplaced = true;
   1929 
   1930                                                 } else if (REMOVE_UNRESTORED_ICONS) {
   1931                                                     Launcher.addDumpLog(TAG,
   1932                                                             "Unrestored package removed: " + cn, true);
   1933                                                     itemsToRemove.add(id);
   1934                                                     continue;
   1935                                                 }
   1936                                             } else if (REMOVE_UNRESTORED_ICONS) {
   1937                                                 Launcher.addDumpLog(TAG,
   1938                                                         "Unrestored package removed: " + cn, true);
   1939                                                 itemsToRemove.add(id);
   1940                                                 continue;
   1941                                             }
   1942                                         } else if (PackageManagerHelper.isAppOnSdcard(
   1943                                                 manager, cn.getPackageName())) {
   1944                                             // Package is present but not available.
   1945                                             allowMissingTarget = true;
   1946                                             disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
   1947                                         } else if (!isSdCardReady) {
   1948                                             // SdCard is not ready yet. Package might get available,
   1949                                             // once it is ready.
   1950                                             Launcher.addDumpLog(TAG, "Invalid package: " + cn
   1951                                                     + " (check again later)", true);
   1952                                             HashSet<String> pkgs = sPendingPackages.get(user);
   1953                                             if (pkgs == null) {
   1954                                                 pkgs = new HashSet<String>();
   1955                                                 sPendingPackages.put(user, pkgs);
   1956                                             }
   1957                                             pkgs.add(cn.getPackageName());
   1958                                             allowMissingTarget = true;
   1959                                             // Add the icon on the workspace anyway.
   1960 
   1961                                         } else {
   1962                                             // Do not wait for external media load anymore.
   1963                                             // Log the invalid package, and remove it
   1964                                             Launcher.addDumpLog(TAG,
   1965                                                     "Invalid package removed: " + cn, true);
   1966                                             itemsToRemove.add(id);
   1967                                             continue;
   1968                                         }
   1969                                     } else if (cn == null) {
   1970                                         // For shortcuts with no component, keep them as they are
   1971                                         restoredRows.add(id);
   1972                                         restored = false;
   1973                                     }
   1974                                 } catch (URISyntaxException e) {
   1975                                     Launcher.addDumpLog(TAG,
   1976                                             "Invalid uri: " + intentDescription, true);
   1977                                     itemsToRemove.add(id);
   1978                                     continue;
   1979                                 }
   1980 
   1981                                 boolean useLowResIcon = container >= 0 &&
   1982                                         c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
   1983 
   1984                                 if (itemReplaced) {
   1985                                     if (user.equals(UserHandleCompat.myUserHandle())) {
   1986                                         info = getAppShortcutInfo(intent, user, context, null,
   1987                                                 cursorIconInfo.iconIndex, titleIndex,
   1988                                                 false, useLowResIcon);
   1989                                     } else {
   1990                                         // Don't replace items for other profiles.
   1991                                         itemsToRemove.add(id);
   1992                                         continue;
   1993                                     }
   1994                                 } else if (restored) {
   1995                                     if (user.equals(UserHandleCompat.myUserHandle())) {
   1996                                         Launcher.addDumpLog(TAG,
   1997                                                 "constructing info for partially restored package",
   1998                                                 true);
   1999                                         info = getRestoredItemInfo(c, titleIndex, intent,
   2000                                                 promiseType, itemType, cursorIconInfo, context);
   2001                                         intent = getRestoredItemIntent(c, context, intent);
   2002                                     } else {
   2003                                         // Don't restore items for other profiles.
   2004                                         itemsToRemove.add(id);
   2005                                         continue;
   2006                                     }
   2007                                 } else if (itemType ==
   2008                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
   2009                                     info = getAppShortcutInfo(intent, user, context, c,
   2010                                             cursorIconInfo.iconIndex, titleIndex,
   2011                                             allowMissingTarget, useLowResIcon);
   2012                                 } else {
   2013                                     info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
   2014 
   2015                                     // Shortcuts are only available on the primary profile
   2016                                     if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
   2017                                         disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
   2018                                     }
   2019 
   2020                                     // App shortcuts that used to be automatically added to Launcher
   2021                                     // didn't always have the correct intent flags set, so do that
   2022                                     // here
   2023                                     if (intent.getAction() != null &&
   2024                                         intent.getCategories() != null &&
   2025                                         intent.getAction().equals(Intent.ACTION_MAIN) &&
   2026                                         intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
   2027                                         intent.addFlags(
   2028                                             Intent.FLAG_ACTIVITY_NEW_TASK |
   2029                                             Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
   2030                                     }
   2031                                 }
   2032 
   2033                                 if (info != null) {
   2034                                     info.id = id;
   2035                                     info.intent = intent;
   2036                                     info.container = container;
   2037                                     info.screenId = c.getInt(screenIndex);
   2038                                     info.cellX = c.getInt(cellXIndex);
   2039                                     info.cellY = c.getInt(cellYIndex);
   2040                                     info.rank = c.getInt(rankIndex);
   2041                                     info.spanX = 1;
   2042                                     info.spanY = 1;
   2043                                     info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
   2044                                     if (info.promisedIntent != null) {
   2045                                         info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
   2046                                     }
   2047                                     info.isDisabled |= disabledState;
   2048                                     if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
   2049                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
   2050                                     }
   2051 
   2052                                     // check & update map of what's occupied
   2053                                     if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
   2054                                         itemsToRemove.add(id);
   2055                                         break;
   2056                                     }
   2057 
   2058                                     if (restored) {
   2059                                         ComponentName cn = info.getTargetComponent();
   2060                                         if (cn != null) {
   2061                                             Integer progress = installingPkgs.get(cn.getPackageName());
   2062                                             if (progress != null) {
   2063                                                 info.setInstallProgress(progress);
   2064                                             } else {
   2065                                                 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
   2066                                             }
   2067                                         }
   2068                                     }
   2069 
   2070                                     switch (container) {
   2071                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
   2072                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
   2073                                         sBgWorkspaceItems.add(info);
   2074                                         break;
   2075                                     default:
   2076                                         // Item is in a user folder
   2077                                         FolderInfo folderInfo =
   2078                                                 findOrMakeFolder(sBgFolders, container);
   2079                                         folderInfo.add(info);
   2080                                         break;
   2081                                     }
   2082                                     sBgItemsIdMap.put(info.id, info);
   2083                                 } else {
   2084                                     throw new RuntimeException("Unexpected null ShortcutInfo");
   2085                                 }
   2086                                 break;
   2087 
   2088                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
   2089                                 id = c.getLong(idIndex);
   2090                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
   2091 
   2092                                 // Do not trim the folder label, as is was set by the user.
   2093                                 folderInfo.title = c.getString(titleIndex);
   2094                                 folderInfo.id = id;
   2095                                 folderInfo.container = container;
   2096                                 folderInfo.screenId = c.getInt(screenIndex);
   2097                                 folderInfo.cellX = c.getInt(cellXIndex);
   2098                                 folderInfo.cellY = c.getInt(cellYIndex);
   2099                                 folderInfo.spanX = 1;
   2100                                 folderInfo.spanY = 1;
   2101                                 folderInfo.options = c.getInt(optionsIndex);
   2102 
   2103                                 // check & update map of what's occupied
   2104                                 if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
   2105                                     itemsToRemove.add(id);
   2106                                     break;
   2107                                 }
   2108 
   2109                                 switch (container) {
   2110                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
   2111                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
   2112                                         sBgWorkspaceItems.add(folderInfo);
   2113                                         break;
   2114                                 }
   2115 
   2116                                 if (restored) {
   2117                                     // no special handling required for restored folders
   2118                                     restoredRows.add(id);
   2119                                 }
   2120 
   2121                                 sBgItemsIdMap.put(folderInfo.id, folderInfo);
   2122                                 sBgFolders.put(folderInfo.id, folderInfo);
   2123                                 break;
   2124 
   2125                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
   2126                             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
   2127                                 // Read all Launcher-specific widget details
   2128                                 boolean customWidget = itemType ==
   2129                                     LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
   2130 
   2131                                 int appWidgetId = c.getInt(appWidgetIdIndex);
   2132                                 serialNumber = c.getLong(profileIdIndex);
   2133                                 String savedProvider = c.getString(appWidgetProviderIndex);
   2134                                 id = c.getLong(idIndex);
   2135                                 user = allUsers.get(serialNumber);
   2136                                 if (user == null) {
   2137                                     itemsToRemove.add(id);
   2138                                     continue;
   2139                                 }
   2140 
   2141                                 final ComponentName component =
   2142                                         ComponentName.unflattenFromString(savedProvider);
   2143 
   2144                                 final int restoreStatus = c.getInt(restoredIndex);
   2145                                 final boolean isIdValid = (restoreStatus &
   2146                                         LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
   2147                                 final boolean wasProviderReady = (restoreStatus &
   2148                                         LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
   2149 
   2150                                 if (widgetProvidersMap == null) {
   2151                                     widgetProvidersMap = AppWidgetManagerCompat
   2152                                             .getInstance(mContext).getAllProvidersMap();
   2153                                 }
   2154                                 final AppWidgetProviderInfo provider = widgetProvidersMap.get(
   2155                                         new ComponentKey(
   2156                                                 ComponentName.unflattenFromString(savedProvider),
   2157                                                 user));
   2158 
   2159                                 final boolean isProviderReady = isValidProvider(provider);
   2160                                 if (!isSafeMode && !customWidget &&
   2161                                         wasProviderReady && !isProviderReady) {
   2162                                     String log = "Deleting widget that isn't installed anymore: "
   2163                                             + "id=" + id + " appWidgetId=" + appWidgetId;
   2164 
   2165                                     Log.e(TAG, log);
   2166                                     Launcher.addDumpLog(TAG, log, false);
   2167                                     itemsToRemove.add(id);
   2168                                 } else {
   2169                                     if (isProviderReady) {
   2170                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
   2171                                                 provider.provider);
   2172 
   2173                                         // The provider is available. So the widget is either
   2174                                         // available or not available. We do not need to track
   2175                                         // any future restore updates.
   2176                                         int status = restoreStatus &
   2177                                                 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
   2178                                         if (!wasProviderReady) {
   2179                                             // If provider was not previously ready, update the
   2180                                             // status and UI flag.
   2181 
   2182                                             // Id would be valid only if the widget restore broadcast was received.
   2183                                             if (isIdValid) {
   2184                                                 status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
   2185                                             } else {
   2186                                                 status &= ~LauncherAppWidgetInfo
   2187                                                         .FLAG_PROVIDER_NOT_READY;
   2188                                             }
   2189                                         }
   2190                                         appWidgetInfo.restoreStatus = status;
   2191                                     } else {
   2192                                         Log.v(TAG, "Widget restore pending id=" + id
   2193                                                 + " appWidgetId=" + appWidgetId
   2194                                                 + " status =" + restoreStatus);
   2195                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
   2196                                                 component);
   2197                                         appWidgetInfo.restoreStatus = restoreStatus;
   2198                                         Integer installProgress = installingPkgs.get(component.getPackageName());
   2199 
   2200                                         if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
   2201                                             // Restore has started once.
   2202                                         } else if (installProgress != null) {
   2203                                             // App restore has started. Update the flag
   2204                                             appWidgetInfo.restoreStatus |=
   2205                                                     LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
   2206                                         } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
   2207                                             Launcher.addDumpLog(TAG,
   2208                                                     "Unrestored widget removed: " + component, true);
   2209                                             itemsToRemove.add(id);
   2210                                             continue;
   2211                                         }
   2212 
   2213                                         appWidgetInfo.installProgress =
   2214                                                 installProgress == null ? 0 : installProgress;
   2215                                     }
   2216 
   2217                                     appWidgetInfo.id = id;
   2218                                     appWidgetInfo.screenId = c.getInt(screenIndex);
   2219                                     appWidgetInfo.cellX = c.getInt(cellXIndex);
   2220                                     appWidgetInfo.cellY = c.getInt(cellYIndex);
   2221                                     appWidgetInfo.spanX = c.getInt(spanXIndex);
   2222                                     appWidgetInfo.spanY = c.getInt(spanYIndex);
   2223                                     appWidgetInfo.user = user;
   2224 
   2225                                     if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
   2226                                         container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   2227                                         Log.e(TAG, "Widget found where container != " +
   2228                                                 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
   2229                                         itemsToRemove.add(id);
   2230                                         continue;
   2231                                     }
   2232 
   2233                                     appWidgetInfo.container = container;
   2234                                     // check & update map of what's occupied
   2235                                     if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
   2236                                         itemsToRemove.add(id);
   2237                                         break;
   2238                                     }
   2239 
   2240                                     if (!customWidget) {
   2241                                         String providerName =
   2242                                                 appWidgetInfo.providerName.flattenToString();
   2243                                         if (!providerName.equals(savedProvider) ||
   2244                                                 (appWidgetInfo.restoreStatus != restoreStatus)) {
   2245                                             ContentValues values = new ContentValues();
   2246                                             values.put(
   2247                                                     LauncherSettings.Favorites.APPWIDGET_PROVIDER,
   2248                                                     providerName);
   2249                                             values.put(LauncherSettings.Favorites.RESTORED,
   2250                                                     appWidgetInfo.restoreStatus);
   2251                                             updateItem(id, values);
   2252                                         }
   2253                                     }
   2254                                     sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
   2255                                     sBgAppWidgets.add(appWidgetInfo);
   2256                                 }
   2257                                 break;
   2258                             }
   2259                         } catch (Exception e) {
   2260                             Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
   2261                         }
   2262                     }
   2263                 } finally {
   2264                     if (c != null) {
   2265                         c.close();
   2266                     }
   2267                 }
   2268 
   2269                 // Break early if we've stopped loading
   2270                 if (mStopped) {
   2271                     clearSBgDataStructures();
   2272                     return;
   2273                 }
   2274 
   2275                 if (itemsToRemove.size() > 0) {
   2276                     // Remove dead items
   2277                     contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
   2278                             Utilities.createDbSelectionQuery(
   2279                                     LauncherSettings.Favorites._ID, itemsToRemove), null);
   2280                     if (DEBUG_LOADERS) {
   2281                         Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
   2282                                 LauncherSettings.Favorites._ID, itemsToRemove));
   2283                     }
   2284 
   2285                     // Remove any empty folder
   2286                     for (long folderId : LauncherAppState.getLauncherProvider()
   2287                             .deleteEmptyFolders()) {
   2288                         sBgWorkspaceItems.remove(sBgFolders.get(folderId));
   2289                         sBgFolders.remove(folderId);
   2290                         sBgItemsIdMap.remove(folderId);
   2291                     }
   2292                 }
   2293 
   2294                 // Sort all the folder items and make sure the first 3 items are high resolution.
   2295                 for (FolderInfo folder : sBgFolders) {
   2296                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
   2297                     int pos = 0;
   2298                     for (ShortcutInfo info : folder.contents) {
   2299                         if (info.usingLowResIcon) {
   2300                             info.updateIcon(mIconCache, false);
   2301                         }
   2302                         pos ++;
   2303                         if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
   2304                             break;
   2305                         }
   2306                     }
   2307                 }
   2308 
   2309                 if (restoredRows.size() > 0) {
   2310                     // Update restored items that no longer require special handling
   2311                     ContentValues values = new ContentValues();
   2312                     values.put(LauncherSettings.Favorites.RESTORED, 0);
   2313                     contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
   2314                             Utilities.createDbSelectionQuery(
   2315                                     LauncherSettings.Favorites._ID, restoredRows), null);
   2316                 }
   2317 
   2318                 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
   2319                     context.registerReceiver(new AppsAvailabilityCheck(),
   2320                             new IntentFilter(StartupReceiver.SYSTEM_READY),
   2321                             null, sWorker);
   2322                 }
   2323 
   2324                 // Remove any empty screens
   2325                 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
   2326                 for (ItemInfo item: sBgItemsIdMap) {
   2327                     long screenId = item.screenId;
   2328                     if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
   2329                             unusedScreens.contains(screenId)) {
   2330                         unusedScreens.remove(screenId);
   2331                     }
   2332                 }
   2333 
   2334                 // If there are any empty screens remove them, and update.
   2335                 if (unusedScreens.size() != 0) {
   2336                     sBgWorkspaceScreens.removeAll(unusedScreens);
   2337                     updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
   2338                 }
   2339 
   2340                 if (DEBUG_LOADERS) {
   2341                     Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
   2342                     Log.d(TAG, "workspace layout: ");
   2343                     int nScreens = occupied.size();
   2344                     for (int y = 0; y < countY; y++) {
   2345                         String line = "";
   2346 
   2347                         for (int i = 0; i < nScreens; i++) {
   2348                             long screenId = occupied.keyAt(i);
   2349                             if (screenId > 0) {
   2350                                 line += " | ";
   2351                             }
   2352                             ItemInfo[][] screen = occupied.valueAt(i);
   2353                             for (int x = 0; x < countX; x++) {
   2354                                 if (x < screen.length && y < screen[x].length) {
   2355                                     line += (screen[x][y] != null) ? "#" : ".";
   2356                                 } else {
   2357                                     line += "!";
   2358                                 }
   2359                             }
   2360                         }
   2361                         Log.d(TAG, "[ " + line + " ]");
   2362                     }
   2363                 }
   2364             }
   2365         }
   2366 
   2367         /**
   2368          * Partially updates the item without any notification. Must be called on the worker thread.
   2369          */
   2370         private void updateItem(long itemId, ContentValues update) {
   2371             mContext.getContentResolver().update(
   2372                     LauncherSettings.Favorites.CONTENT_URI,
   2373                     update,
   2374                     BaseColumns._ID + "= ?",
   2375                     new String[]{Long.toString(itemId)});
   2376         }
   2377 
   2378         /** Filters the set of items who are directly or indirectly (via another container) on the
   2379          * specified screen. */
   2380         private void filterCurrentWorkspaceItems(long currentScreenId,
   2381                 ArrayList<ItemInfo> allWorkspaceItems,
   2382                 ArrayList<ItemInfo> currentScreenItems,
   2383                 ArrayList<ItemInfo> otherScreenItems) {
   2384             // Purge any null ItemInfos
   2385             Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
   2386             while (iter.hasNext()) {
   2387                 ItemInfo i = iter.next();
   2388                 if (i == null) {
   2389                     iter.remove();
   2390                 }
   2391             }
   2392 
   2393             // Order the set of items by their containers first, this allows use to walk through the
   2394             // list sequentially, build up a list of containers that are in the specified screen,
   2395             // as well as all items in those containers.
   2396             Set<Long> itemsOnScreen = new HashSet<Long>();
   2397             Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
   2398                 @Override
   2399                 public int compare(ItemInfo lhs, ItemInfo rhs) {
   2400                     return Utilities.longCompare(lhs.container, rhs.container);
   2401                 }
   2402             });
   2403             for (ItemInfo info : allWorkspaceItems) {
   2404                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
   2405                     if (info.screenId == currentScreenId) {
   2406                         currentScreenItems.add(info);
   2407                         itemsOnScreen.add(info.id);
   2408                     } else {
   2409                         otherScreenItems.add(info);
   2410                     }
   2411                 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   2412                     currentScreenItems.add(info);
   2413                     itemsOnScreen.add(info.id);
   2414                 } else {
   2415                     if (itemsOnScreen.contains(info.container)) {
   2416                         currentScreenItems.add(info);
   2417                         itemsOnScreen.add(info.id);
   2418                     } else {
   2419                         otherScreenItems.add(info);
   2420                     }
   2421                 }
   2422             }
   2423         }
   2424 
   2425         /** Filters the set of widgets which are on the specified screen. */
   2426         private void filterCurrentAppWidgets(long currentScreenId,
   2427                 ArrayList<LauncherAppWidgetInfo> appWidgets,
   2428                 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
   2429                 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
   2430 
   2431             for (LauncherAppWidgetInfo widget : appWidgets) {
   2432                 if (widget == null) continue;
   2433                 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
   2434                         widget.screenId == currentScreenId) {
   2435                     currentScreenWidgets.add(widget);
   2436                 } else {
   2437                     otherScreenWidgets.add(widget);
   2438                 }
   2439             }
   2440         }
   2441 
   2442         /** Filters the set of folders which are on the specified screen. */
   2443         private void filterCurrentFolders(long currentScreenId,
   2444                 LongArrayMap<ItemInfo> itemsIdMap,
   2445                 LongArrayMap<FolderInfo> folders,
   2446                 LongArrayMap<FolderInfo> currentScreenFolders,
   2447                 LongArrayMap<FolderInfo> otherScreenFolders) {
   2448 
   2449             int total = folders.size();
   2450             for (int i = 0; i < total; i++) {
   2451                 long id = folders.keyAt(i);
   2452                 FolderInfo folder = folders.valueAt(i);
   2453 
   2454                 ItemInfo info = itemsIdMap.get(id);
   2455                 if (info == null || folder == null) continue;
   2456                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
   2457                         info.screenId == currentScreenId) {
   2458                     currentScreenFolders.put(id, folder);
   2459                 } else {
   2460                     otherScreenFolders.put(id, folder);
   2461                 }
   2462             }
   2463         }
   2464 
   2465         /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
   2466          * right) */
   2467         private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
   2468             final LauncherAppState app = LauncherAppState.getInstance();
   2469             final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
   2470             // XXX: review this
   2471             Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
   2472                 @Override
   2473                 public int compare(ItemInfo lhs, ItemInfo rhs) {
   2474                     int cellCountX = (int) profile.numColumns;
   2475                     int cellCountY = (int) profile.numRows;
   2476                     int screenOffset = cellCountX * cellCountY;
   2477                     int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
   2478                     long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
   2479                             lhs.cellY * cellCountX + lhs.cellX);
   2480                     long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
   2481                             rhs.cellY * cellCountX + rhs.cellX);
   2482                     return Utilities.longCompare(lr, rr);
   2483                 }
   2484             });
   2485         }
   2486 
   2487         private void bindWorkspaceScreens(final Callbacks oldCallbacks,
   2488                 final ArrayList<Long> orderedScreens) {
   2489             final Runnable r = new Runnable() {
   2490                 @Override
   2491                 public void run() {
   2492                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2493                     if (callbacks != null) {
   2494                         callbacks.bindScreens(orderedScreens);
   2495                     }
   2496                 }
   2497             };
   2498             runOnMainThread(r);
   2499         }
   2500 
   2501         private void bindWorkspaceItems(final Callbacks oldCallbacks,
   2502                 final ArrayList<ItemInfo> workspaceItems,
   2503                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
   2504                 final LongArrayMap<FolderInfo> folders,
   2505                 ArrayList<Runnable> deferredBindRunnables) {
   2506 
   2507             final boolean postOnMainThread = (deferredBindRunnables != null);
   2508 
   2509             // Bind the workspace items
   2510             int N = workspaceItems.size();
   2511             for (int i = 0; i < N; i += ITEMS_CHUNK) {
   2512                 final int start = i;
   2513                 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
   2514                 final Runnable r = new Runnable() {
   2515                     @Override
   2516                     public void run() {
   2517                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2518                         if (callbacks != null) {
   2519                             callbacks.bindItems(workspaceItems, start, start+chunkSize,
   2520                                     false);
   2521                         }
   2522                     }
   2523                 };
   2524                 if (postOnMainThread) {
   2525                     synchronized (deferredBindRunnables) {
   2526                         deferredBindRunnables.add(r);
   2527                     }
   2528                 } else {
   2529                     runOnMainThread(r);
   2530                 }
   2531             }
   2532 
   2533             // Bind the folders
   2534             if (!folders.isEmpty()) {
   2535                 final Runnable r = new Runnable() {
   2536                     public void run() {
   2537                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2538                         if (callbacks != null) {
   2539                             callbacks.bindFolders(folders);
   2540                         }
   2541                     }
   2542                 };
   2543                 if (postOnMainThread) {
   2544                     synchronized (deferredBindRunnables) {
   2545                         deferredBindRunnables.add(r);
   2546                     }
   2547                 } else {
   2548                     runOnMainThread(r);
   2549                 }
   2550             }
   2551 
   2552             // Bind the widgets, one at a time
   2553             N = appWidgets.size();
   2554             for (int i = 0; i < N; i++) {
   2555                 final LauncherAppWidgetInfo widget = appWidgets.get(i);
   2556                 final Runnable r = new Runnable() {
   2557                     public void run() {
   2558                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2559                         if (callbacks != null) {
   2560                             callbacks.bindAppWidget(widget);
   2561                         }
   2562                     }
   2563                 };
   2564                 if (postOnMainThread) {
   2565                     deferredBindRunnables.add(r);
   2566                 } else {
   2567                     runOnMainThread(r);
   2568                 }
   2569             }
   2570         }
   2571 
   2572         /**
   2573          * Binds all loaded data to actual views on the main thread.
   2574          */
   2575         private void bindWorkspace(int synchronizeBindPage) {
   2576             final long t = SystemClock.uptimeMillis();
   2577             Runnable r;
   2578 
   2579             // Don't use these two variables in any of the callback runnables.
   2580             // Otherwise we hold a reference to them.
   2581             final Callbacks oldCallbacks = mCallbacks.get();
   2582             if (oldCallbacks == null) {
   2583                 // This launcher has exited and nobody bothered to tell us.  Just bail.
   2584                 Log.w(TAG, "LoaderTask running with no launcher");
   2585                 return;
   2586             }
   2587 
   2588             // Save a copy of all the bg-thread collections
   2589             ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
   2590             ArrayList<LauncherAppWidgetInfo> appWidgets =
   2591                     new ArrayList<LauncherAppWidgetInfo>();
   2592             ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
   2593 
   2594             final LongArrayMap<FolderInfo> folders;
   2595             final LongArrayMap<ItemInfo> itemsIdMap;
   2596 
   2597             synchronized (sBgLock) {
   2598                 workspaceItems.addAll(sBgWorkspaceItems);
   2599                 appWidgets.addAll(sBgAppWidgets);
   2600                 orderedScreenIds.addAll(sBgWorkspaceScreens);
   2601 
   2602                 folders = sBgFolders.clone();
   2603                 itemsIdMap = sBgItemsIdMap.clone();
   2604             }
   2605 
   2606             final boolean isLoadingSynchronously =
   2607                     synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
   2608             int currScreen = isLoadingSynchronously ? synchronizeBindPage :
   2609                 oldCallbacks.getCurrentWorkspaceScreen();
   2610             if (currScreen >= orderedScreenIds.size()) {
   2611                 // There may be no workspace screens (just hotseat items and an empty page).
   2612                 currScreen = PagedView.INVALID_RESTORE_PAGE;
   2613             }
   2614             final int currentScreen = currScreen;
   2615             final long currentScreenId = currentScreen < 0
   2616                     ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
   2617 
   2618             // Load all the items that are on the current page first (and in the process, unbind
   2619             // all the existing workspace items before we call startBinding() below.
   2620             unbindWorkspaceItemsOnMainThread();
   2621 
   2622             // Separate the items that are on the current screen, and all the other remaining items
   2623             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
   2624             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
   2625             ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
   2626                     new ArrayList<LauncherAppWidgetInfo>();
   2627             ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
   2628                     new ArrayList<LauncherAppWidgetInfo>();
   2629             LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
   2630             LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
   2631 
   2632             filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
   2633                     otherWorkspaceItems);
   2634             filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
   2635                     otherAppWidgets);
   2636             filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
   2637                     otherFolders);
   2638             sortWorkspaceItemsSpatially(currentWorkspaceItems);
   2639             sortWorkspaceItemsSpatially(otherWorkspaceItems);
   2640 
   2641             // Tell the workspace that we're about to start binding items
   2642             r = new Runnable() {
   2643                 public void run() {
   2644                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2645                     if (callbacks != null) {
   2646                         callbacks.startBinding();
   2647                     }
   2648                 }
   2649             };
   2650             runOnMainThread(r);
   2651 
   2652             bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
   2653 
   2654             // Load items on the current page
   2655             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
   2656                     currentFolders, null);
   2657             if (isLoadingSynchronously) {
   2658                 r = new Runnable() {
   2659                     public void run() {
   2660                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2661                         if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
   2662                             callbacks.onPageBoundSynchronously(currentScreen);
   2663                         }
   2664                     }
   2665                 };
   2666                 runOnMainThread(r);
   2667             }
   2668 
   2669             // Load all the remaining pages (if we are loading synchronously, we want to defer this
   2670             // work until after the first render)
   2671             synchronized (mDeferredBindRunnables) {
   2672                 mDeferredBindRunnables.clear();
   2673             }
   2674             bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
   2675                     (isLoadingSynchronously ? mDeferredBindRunnables : null));
   2676 
   2677             // Tell the workspace that we're done binding items
   2678             r = new Runnable() {
   2679                 public void run() {
   2680                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2681                     if (callbacks != null) {
   2682                         callbacks.finishBindingItems();
   2683                     }
   2684 
   2685                     mIsLoadingAndBindingWorkspace = false;
   2686 
   2687                     // Run all the bind complete runnables after workspace is bound.
   2688                     if (!mBindCompleteRunnables.isEmpty()) {
   2689                         synchronized (mBindCompleteRunnables) {
   2690                             for (final Runnable r : mBindCompleteRunnables) {
   2691                                 runOnWorkerThread(r);
   2692                             }
   2693                             mBindCompleteRunnables.clear();
   2694                         }
   2695                     }
   2696 
   2697                     // If we're profiling, ensure this is the last thing in the queue.
   2698                     if (DEBUG_LOADERS) {
   2699                         Log.d(TAG, "bound workspace in "
   2700                             + (SystemClock.uptimeMillis()-t) + "ms");
   2701                     }
   2702 
   2703                 }
   2704             };
   2705             if (isLoadingSynchronously) {
   2706                 synchronized (mDeferredBindRunnables) {
   2707                     mDeferredBindRunnables.add(r);
   2708                 }
   2709             } else {
   2710                 runOnMainThread(r);
   2711             }
   2712         }
   2713 
   2714         private void loadAndBindAllApps() {
   2715             if (DEBUG_LOADERS) {
   2716                 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
   2717             }
   2718             if (!mAllAppsLoaded) {
   2719                 loadAllApps();
   2720                 synchronized (LoaderTask.this) {
   2721                     if (mStopped) {
   2722                         return;
   2723                     }
   2724                 }
   2725                 updateIconCache();
   2726                 synchronized (LoaderTask.this) {
   2727                     if (mStopped) {
   2728                         return;
   2729                     }
   2730                     mAllAppsLoaded = true;
   2731                 }
   2732             } else {
   2733                 onlyBindAllApps();
   2734             }
   2735         }
   2736 
   2737         private void updateIconCache() {
   2738             // Ignore packages which have a promise icon.
   2739             HashSet<String> packagesToIgnore = new HashSet<>();
   2740             synchronized (sBgLock) {
   2741                 for (ItemInfo info : sBgItemsIdMap) {
   2742                     if (info instanceof ShortcutInfo) {
   2743                         ShortcutInfo si = (ShortcutInfo) info;
   2744                         if (si.isPromise() && si.getTargetComponent() != null) {
   2745                             packagesToIgnore.add(si.getTargetComponent().getPackageName());
   2746                         }
   2747                     } else if (info instanceof LauncherAppWidgetInfo) {
   2748                         LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
   2749                         if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
   2750                             packagesToIgnore.add(lawi.providerName.getPackageName());
   2751                         }
   2752                     }
   2753                 }
   2754             }
   2755             mIconCache.updateDbIcons(packagesToIgnore);
   2756         }
   2757 
   2758         private void onlyBindAllApps() {
   2759             final Callbacks oldCallbacks = mCallbacks.get();
   2760             if (oldCallbacks == null) {
   2761                 // This launcher has exited and nobody bothered to tell us.  Just bail.
   2762                 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
   2763                 return;
   2764             }
   2765 
   2766             // shallow copy
   2767             @SuppressWarnings("unchecked")
   2768             final ArrayList<AppInfo> list
   2769                     = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
   2770             Runnable r = new Runnable() {
   2771                 public void run() {
   2772                     final long t = SystemClock.uptimeMillis();
   2773                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2774                     if (callbacks != null) {
   2775                         callbacks.bindAllApplications(list);
   2776                     }
   2777                     if (DEBUG_LOADERS) {
   2778                         Log.d(TAG, "bound all " + list.size() + " apps from cache in "
   2779                                 + (SystemClock.uptimeMillis() - t) + "ms");
   2780                     }
   2781                 }
   2782             };
   2783             boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
   2784             if (isRunningOnMainThread) {
   2785                 r.run();
   2786             } else {
   2787                 mHandler.post(r);
   2788             }
   2789         }
   2790 
   2791         private void loadAllApps() {
   2792             final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   2793 
   2794             final Callbacks oldCallbacks = mCallbacks.get();
   2795             if (oldCallbacks == null) {
   2796                 // This launcher has exited and nobody bothered to tell us.  Just bail.
   2797                 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
   2798                 return;
   2799             }
   2800 
   2801             final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
   2802 
   2803             // Clear the list of apps
   2804             mBgAllAppsList.clear();
   2805             for (UserHandleCompat user : profiles) {
   2806                 // Query for the set of apps
   2807                 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   2808                 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
   2809                 if (DEBUG_LOADERS) {
   2810                     Log.d(TAG, "getActivityList took "
   2811                             + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
   2812                     Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
   2813                 }
   2814                 // Fail if we don't have any apps
   2815                 // TODO: Fix this. Only fail for the current user.
   2816                 if (apps == null || apps.isEmpty()) {
   2817                     return;
   2818                 }
   2819                 boolean quietMode = mUserManager.isQuietModeEnabled(user);
   2820                 // Create the ApplicationInfos
   2821                 for (int i = 0; i < apps.size(); i++) {
   2822                     LauncherActivityInfoCompat app = apps.get(i);
   2823                     // This builds the icon bitmaps.
   2824                     mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
   2825                 }
   2826 
   2827                 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
   2828                 if (heuristic != null) {
   2829                     final Runnable r = new Runnable() {
   2830 
   2831                         @Override
   2832                         public void run() {
   2833                             heuristic.processUserApps(apps);
   2834                         }
   2835                     };
   2836                     runOnMainThread(new Runnable() {
   2837 
   2838                         @Override
   2839                         public void run() {
   2840                             // Check isLoadingWorkspace on the UI thread, as it is updated on
   2841                             // the UI thread.
   2842                             if (mIsLoadingAndBindingWorkspace) {
   2843                                 synchronized (mBindCompleteRunnables) {
   2844                                     mBindCompleteRunnables.add(r);
   2845                                 }
   2846                             } else {
   2847                                 runOnWorkerThread(r);
   2848                             }
   2849                         }
   2850                     });
   2851                 }
   2852             }
   2853             // Huh? Shouldn't this be inside the Runnable below?
   2854             final ArrayList<AppInfo> added = mBgAllAppsList.added;
   2855             mBgAllAppsList.added = new ArrayList<AppInfo>();
   2856 
   2857             // Post callback on main thread
   2858             mHandler.post(new Runnable() {
   2859                 public void run() {
   2860 
   2861                     final long bindTime = SystemClock.uptimeMillis();
   2862                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   2863                     if (callbacks != null) {
   2864                         callbacks.bindAllApplications(added);
   2865                         if (DEBUG_LOADERS) {
   2866                             Log.d(TAG, "bound " + added.size() + " apps in "
   2867                                     + (SystemClock.uptimeMillis() - bindTime) + "ms");
   2868                         }
   2869                     } else {
   2870                         Log.i(TAG, "not binding apps: no Launcher activity");
   2871                     }
   2872                 }
   2873             });
   2874             // Cleanup any data stored for a deleted user.
   2875             ManagedProfileHeuristic.processAllUsers(profiles, mContext);
   2876             if (DEBUG_LOADERS) {
   2877                 Log.d(TAG, "Icons processed in "
   2878                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
   2879             }
   2880         }
   2881 
   2882         public void dumpState() {
   2883             synchronized (sBgLock) {
   2884                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
   2885                 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
   2886                 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
   2887                 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
   2888             }
   2889         }
   2890     }
   2891 
   2892     /**
   2893      * Called when the icons for packages have been updated in the icon cache.
   2894      */
   2895     public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
   2896         final Callbacks callbacks = getCallback();
   2897         final ArrayList<AppInfo> updatedApps = new ArrayList<>();
   2898         final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
   2899 
   2900         // If any package icon has changed (app was updated while launcher was dead),
   2901         // update the corresponding shortcuts.
   2902         synchronized (sBgLock) {
   2903             for (ItemInfo info : sBgItemsIdMap) {
   2904                 if (info instanceof ShortcutInfo && user.equals(info.user)
   2905                         && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
   2906                     ShortcutInfo si = (ShortcutInfo) info;
   2907                     ComponentName cn = si.getTargetComponent();
   2908                     if (cn != null && updatedPackages.contains(cn.getPackageName())) {
   2909                         si.updateIcon(mIconCache);
   2910                         updatedShortcuts.add(si);
   2911                     }
   2912                 }
   2913             }
   2914             mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
   2915         }
   2916 
   2917         if (!updatedShortcuts.isEmpty()) {
   2918             final UserHandleCompat userFinal = user;
   2919             mHandler.post(new Runnable() {
   2920 
   2921                 public void run() {
   2922                     Callbacks cb = getCallback();
   2923                     if (cb != null && callbacks == cb) {
   2924                         cb.bindShortcutsChanged(updatedShortcuts,
   2925                                 new ArrayList<ShortcutInfo>(), userFinal);
   2926                     }
   2927                 }
   2928             });
   2929         }
   2930 
   2931         if (!updatedApps.isEmpty()) {
   2932             mHandler.post(new Runnable() {
   2933 
   2934                 public void run() {
   2935                     Callbacks cb = getCallback();
   2936                     if (cb != null && callbacks == cb) {
   2937                         cb.bindAppsUpdated(updatedApps);
   2938                     }
   2939                 }
   2940             });
   2941         }
   2942     }
   2943 
   2944     void enqueuePackageUpdated(PackageUpdatedTask task) {
   2945         sWorker.post(task);
   2946     }
   2947 
   2948     @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
   2949 
   2950         @Override
   2951         public void onReceive(Context context, Intent intent) {
   2952             synchronized (sBgLock) {
   2953                 final LauncherAppsCompat launcherApps = LauncherAppsCompat
   2954                         .getInstance(mApp.getContext());
   2955                 final PackageManager manager = context.getPackageManager();
   2956                 final ArrayList<String> packagesRemoved = new ArrayList<String>();
   2957                 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
   2958                 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
   2959                     UserHandleCompat user = entry.getKey();
   2960                     packagesRemoved.clear();
   2961                     packagesUnavailable.clear();
   2962                     for (String pkg : entry.getValue()) {
   2963                         if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
   2964                             if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
   2965                                 packagesUnavailable.add(pkg);
   2966                             } else {
   2967                                 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
   2968                                 packagesRemoved.add(pkg);
   2969                             }
   2970                         }
   2971                     }
   2972                     if (!packagesRemoved.isEmpty()) {
   2973                         enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
   2974                                 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
   2975                     }
   2976                     if (!packagesUnavailable.isEmpty()) {
   2977                         enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
   2978                                 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
   2979                     }
   2980                 }
   2981                 sPendingPackages.clear();
   2982             }
   2983         }
   2984     }
   2985 
   2986     private class PackageUpdatedTask implements Runnable {
   2987         int mOp;
   2988         String[] mPackages;
   2989         UserHandleCompat mUser;
   2990 
   2991         public static final int OP_NONE = 0;
   2992         public static final int OP_ADD = 1;
   2993         public static final int OP_UPDATE = 2;
   2994         public static final int OP_REMOVE = 3; // uninstlled
   2995         public static final int OP_UNAVAILABLE = 4; // external media unmounted
   2996         public static final int OP_SUSPEND = 5; // package suspended
   2997         public static final int OP_UNSUSPEND = 6; // package unsuspended
   2998         public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
   2999 
   3000         public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
   3001             mOp = op;
   3002             mPackages = packages;
   3003             mUser = user;
   3004         }
   3005 
   3006         public void run() {
   3007             if (!mHasLoaderCompletedOnce) {
   3008                 // Loader has not yet run.
   3009                 return;
   3010             }
   3011             final Context context = mApp.getContext();
   3012 
   3013             final String[] packages = mPackages;
   3014             final int N = packages.length;
   3015             FlagOp flagOp = FlagOp.NO_OP;
   3016             StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
   3017             switch (mOp) {
   3018                 case OP_ADD: {
   3019                     for (int i=0; i<N; i++) {
   3020                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
   3021                         mIconCache.updateIconsForPkg(packages[i], mUser);
   3022                         mBgAllAppsList.addPackage(context, packages[i], mUser);
   3023                     }
   3024 
   3025                     ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
   3026                     if (heuristic != null) {
   3027                         heuristic.processPackageAdd(mPackages);
   3028                     }
   3029                     break;
   3030                 }
   3031                 case OP_UPDATE:
   3032                     for (int i=0; i<N; i++) {
   3033                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
   3034                         mIconCache.updateIconsForPkg(packages[i], mUser);
   3035                         mBgAllAppsList.updatePackage(context, packages[i], mUser);
   3036                         mApp.getWidgetCache().removePackage(packages[i], mUser);
   3037                     }
   3038                     // Since package was just updated, the target must be available now.
   3039                     flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
   3040                     break;
   3041                 case OP_REMOVE: {
   3042                     ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
   3043                     if (heuristic != null) {
   3044                         heuristic.processPackageRemoved(mPackages);
   3045                     }
   3046                     for (int i=0; i<N; i++) {
   3047                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
   3048                         mIconCache.removeIconsForPkg(packages[i], mUser);
   3049                     }
   3050                     // Fall through
   3051                 }
   3052                 case OP_UNAVAILABLE:
   3053                     for (int i=0; i<N; i++) {
   3054                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
   3055                         mBgAllAppsList.removePackage(packages[i], mUser);
   3056                         mApp.getWidgetCache().removePackage(packages[i], mUser);
   3057                     }
   3058                     flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
   3059                     break;
   3060                 case OP_SUSPEND:
   3061                 case OP_UNSUSPEND:
   3062                     flagOp = mOp == OP_SUSPEND ?
   3063                             FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
   3064                                     FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
   3065                     if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
   3066                     mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
   3067                     break;
   3068                 case OP_USER_AVAILABILITY_CHANGE:
   3069                     flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
   3070                             ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
   3071                             : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
   3072                     // We want to update all packages for this user.
   3073                     pkgFilter = StringFilter.matchesAll();
   3074                     mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
   3075                     break;
   3076             }
   3077 
   3078             ArrayList<AppInfo> added = null;
   3079             ArrayList<AppInfo> modified = null;
   3080             final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
   3081 
   3082             if (mBgAllAppsList.added.size() > 0) {
   3083                 added = new ArrayList<>(mBgAllAppsList.added);
   3084                 mBgAllAppsList.added.clear();
   3085             }
   3086             if (mBgAllAppsList.modified.size() > 0) {
   3087                 modified = new ArrayList<>(mBgAllAppsList.modified);
   3088                 mBgAllAppsList.modified.clear();
   3089             }
   3090             if (mBgAllAppsList.removed.size() > 0) {
   3091                 removedApps.addAll(mBgAllAppsList.removed);
   3092                 mBgAllAppsList.removed.clear();
   3093             }
   3094 
   3095             final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
   3096 
   3097             if (added != null) {
   3098                 addAppsToAllApps(context, added);
   3099                 for (AppInfo ai : added) {
   3100                     addedOrUpdatedApps.put(ai.componentName, ai);
   3101                 }
   3102             }
   3103 
   3104             if (modified != null) {
   3105                 final Callbacks callbacks = getCallback();
   3106                 final ArrayList<AppInfo> modifiedFinal = modified;
   3107                 for (AppInfo ai : modified) {
   3108                     addedOrUpdatedApps.put(ai.componentName, ai);
   3109                 }
   3110 
   3111                 mHandler.post(new Runnable() {
   3112                     public void run() {
   3113                         Callbacks cb = getCallback();
   3114                         if (callbacks == cb && cb != null) {
   3115                             callbacks.bindAppsUpdated(modifiedFinal);
   3116                         }
   3117                     }
   3118                 });
   3119             }
   3120 
   3121             // Update shortcut infos
   3122             if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
   3123                 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
   3124                 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
   3125                 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
   3126 
   3127                 synchronized (sBgLock) {
   3128                     for (ItemInfo info : sBgItemsIdMap) {
   3129                         if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
   3130                             ShortcutInfo si = (ShortcutInfo) info;
   3131                             boolean infoUpdated = false;
   3132                             boolean shortcutUpdated = false;
   3133 
   3134                             // Update shortcuts which use iconResource.
   3135                             if ((si.iconResource != null)
   3136                                     && pkgFilter.matches(si.iconResource.packageName)) {
   3137                                 Bitmap icon = Utilities.createIconBitmap(
   3138                                         si.iconResource.packageName,
   3139                                         si.iconResource.resourceName, context);
   3140                                 if (icon != null) {
   3141                                     si.setIcon(icon);
   3142                                     si.usingFallbackIcon = false;
   3143                                     infoUpdated = true;
   3144                                 }
   3145                             }
   3146 
   3147                             ComponentName cn = si.getTargetComponent();
   3148                             if (cn != null && pkgFilter.matches(cn.getPackageName())) {
   3149                                 AppInfo appInfo = addedOrUpdatedApps.get(cn);
   3150 
   3151                                 if (si.isPromise()) {
   3152                                     if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
   3153                                         // Auto install icon
   3154                                         PackageManager pm = context.getPackageManager();
   3155                                         ResolveInfo matched = pm.resolveActivity(
   3156                                                 new Intent(Intent.ACTION_MAIN)
   3157                                                 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
   3158                                                 PackageManager.MATCH_DEFAULT_ONLY);
   3159                                         if (matched == null) {
   3160                                             // Try to find the best match activity.
   3161                                             Intent intent = pm.getLaunchIntentForPackage(
   3162                                                     cn.getPackageName());
   3163                                             if (intent != null) {
   3164                                                 cn = intent.getComponent();
   3165                                                 appInfo = addedOrUpdatedApps.get(cn);
   3166                                             }
   3167 
   3168                                             if ((intent == null) || (appInfo == null)) {
   3169                                                 removedShortcuts.add(si);
   3170                                                 continue;
   3171                                             }
   3172                                             si.promisedIntent = intent;
   3173                                         }
   3174                                     }
   3175 
   3176                                     // Restore the shortcut.
   3177                                     if (appInfo != null) {
   3178                                         si.flags = appInfo.flags;
   3179                                     }
   3180 
   3181                                     si.intent = si.promisedIntent;
   3182                                     si.promisedIntent = null;
   3183                                     si.status = ShortcutInfo.DEFAULT;
   3184                                     infoUpdated = true;
   3185                                     si.updateIcon(mIconCache);
   3186                                 }
   3187 
   3188                                 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
   3189                                         && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
   3190                                     si.updateIcon(mIconCache);
   3191                                     si.title = Utilities.trim(appInfo.title);
   3192                                     si.contentDescription = appInfo.contentDescription;
   3193                                     infoUpdated = true;
   3194                                 }
   3195 
   3196                                 int oldDisabledFlags = si.isDisabled;
   3197                                 si.isDisabled = flagOp.apply(si.isDisabled);
   3198                                 if (si.isDisabled != oldDisabledFlags) {
   3199                                     shortcutUpdated = true;
   3200                                 }
   3201                             }
   3202 
   3203                             if (infoUpdated || shortcutUpdated) {
   3204                                 updatedShortcuts.add(si);
   3205                             }
   3206                             if (infoUpdated) {
   3207                                 updateItemInDatabase(context, si);
   3208                             }
   3209                         } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
   3210                             LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
   3211                             if (mUser.equals(widgetInfo.user)
   3212                                     && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
   3213                                     && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
   3214                                 widgetInfo.restoreStatus &=
   3215                                         ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
   3216                                         ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
   3217 
   3218                                 // adding this flag ensures that launcher shows 'click to setup'
   3219                                 // if the widget has a config activity. In case there is no config
   3220                                 // activity, it will be marked as 'restored' during bind.
   3221                                 widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
   3222 
   3223                                 widgets.add(widgetInfo);
   3224                                 updateItemInDatabase(context, widgetInfo);
   3225                             }
   3226                         }
   3227                     }
   3228                 }
   3229 
   3230                 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
   3231                     final Callbacks callbacks = getCallback();
   3232                     mHandler.post(new Runnable() {
   3233 
   3234                         public void run() {
   3235                             Callbacks cb = getCallback();
   3236                             if (callbacks == cb && cb != null) {
   3237                                 callbacks.bindShortcutsChanged(
   3238                                         updatedShortcuts, removedShortcuts, mUser);
   3239                             }
   3240                         }
   3241                     });
   3242                     if (!removedShortcuts.isEmpty()) {
   3243                         deleteItemsFromDatabase(context, removedShortcuts);
   3244                     }
   3245                 }
   3246                 if (!widgets.isEmpty()) {
   3247                     final Callbacks callbacks = getCallback();
   3248                     mHandler.post(new Runnable() {
   3249                         public void run() {
   3250                             Callbacks cb = getCallback();
   3251                             if (callbacks == cb && cb != null) {
   3252                                 callbacks.bindWidgetsRestored(widgets);
   3253                             }
   3254                         }
   3255                     });
   3256                 }
   3257             }
   3258 
   3259             final HashSet<String> removedPackages = new HashSet<>();
   3260             final HashSet<ComponentName> removedComponents = new HashSet<>();
   3261             if (mOp == OP_REMOVE) {
   3262                 // Mark all packages in the broadcast to be removed
   3263                 Collections.addAll(removedPackages, packages);
   3264 
   3265                 // No need to update the removedComponents as
   3266                 // removedPackages is a super-set of removedComponents
   3267             } else if (mOp == OP_UPDATE) {
   3268                 // Mark disabled packages in the broadcast to be removed
   3269                 for (int i=0; i<N; i++) {
   3270                     if (isPackageDisabled(context, packages[i], mUser)) {
   3271                         removedPackages.add(packages[i]);
   3272                     }
   3273                 }
   3274 
   3275                 // Update removedComponents as some components can get removed during package update
   3276                 for (AppInfo info : removedApps) {
   3277                     removedComponents.add(info.componentName);
   3278                 }
   3279             }
   3280 
   3281             if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
   3282                 for (String pn : removedPackages) {
   3283                     deletePackageFromDatabase(context, pn, mUser);
   3284                 }
   3285                 for (ComponentName cn : removedComponents) {
   3286                     deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
   3287                 }
   3288 
   3289                 // Remove any queued items from the install queue
   3290                 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
   3291 
   3292                 // Call the components-removed callback
   3293                 final Callbacks callbacks = getCallback();
   3294                 mHandler.post(new Runnable() {
   3295                     public void run() {
   3296                         Callbacks cb = getCallback();
   3297                         if (callbacks == cb && cb != null) {
   3298                             callbacks.bindWorkspaceComponentsRemoved(
   3299                                     removedPackages, removedComponents, mUser);
   3300                         }
   3301                     }
   3302                 });
   3303             }
   3304 
   3305             if (!removedApps.isEmpty()) {
   3306                 // Remove corresponding apps from All-Apps
   3307                 final Callbacks callbacks = getCallback();
   3308                 mHandler.post(new Runnable() {
   3309                     public void run() {
   3310                         Callbacks cb = getCallback();
   3311                         if (callbacks == cb && cb != null) {
   3312                             callbacks.bindAppInfosRemoved(removedApps);
   3313                         }
   3314                     }
   3315                 });
   3316             }
   3317 
   3318             // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
   3319             // get widget update signals.
   3320             if (!Utilities.ATLEAST_MARSHMALLOW &&
   3321                     (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
   3322                 final Callbacks callbacks = getCallback();
   3323                 mHandler.post(new Runnable() {
   3324                     public void run() {
   3325                         Callbacks cb = getCallback();
   3326                         if (callbacks == cb && cb != null) {
   3327                             callbacks.notifyWidgetProvidersChanged();
   3328                         }
   3329                     }
   3330                 });
   3331             }
   3332         }
   3333     }
   3334 
   3335     private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
   3336         mHandler.post(new Runnable() {
   3337             @Override
   3338             public void run() {
   3339                 Callbacks cb = getCallback();
   3340                 if (callbacks == cb && cb != null) {
   3341                     callbacks.bindWidgetsModel(model);
   3342                 }
   3343             }
   3344         });
   3345     }
   3346 
   3347     public void refreshAndBindWidgetsAndShortcuts(
   3348             final Callbacks callbacks, final boolean bindFirst) {
   3349         runOnWorkerThread(new Runnable() {
   3350             @Override
   3351             public void run() {
   3352                 if (bindFirst && !mBgWidgetsModel.isEmpty()) {
   3353                     bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
   3354                 }
   3355                 final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
   3356                 bindWidgetsModel(callbacks, model);
   3357                 // update the Widget entries inside DB on the worker thread.
   3358                 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
   3359                         model.getRawList());
   3360             }
   3361         });
   3362     }
   3363 
   3364     @Thunk static boolean isPackageDisabled(Context context, String packageName,
   3365             UserHandleCompat user) {
   3366         final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
   3367         return !launcherApps.isPackageEnabledForProfile(packageName, user);
   3368     }
   3369 
   3370     public static boolean isValidPackageActivity(Context context, ComponentName cn,
   3371             UserHandleCompat user) {
   3372         if (cn == null) {
   3373             return false;
   3374         }
   3375         final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
   3376         if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
   3377             return false;
   3378         }
   3379         return launcherApps.isActivityEnabledForProfile(cn, user);
   3380     }
   3381 
   3382     public static boolean isValidPackage(Context context, String packageName,
   3383             UserHandleCompat user) {
   3384         if (packageName == null) {
   3385             return false;
   3386         }
   3387         final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
   3388         return launcherApps.isPackageEnabledForProfile(packageName, user);
   3389     }
   3390 
   3391     /**
   3392      * Make an ShortcutInfo object for a restored application or shortcut item that points
   3393      * to a package that is not yet installed on the system.
   3394      */
   3395     public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
   3396             int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
   3397         final ShortcutInfo info = new ShortcutInfo();
   3398         info.user = UserHandleCompat.myUserHandle();
   3399 
   3400         Bitmap icon = iconInfo.loadIcon(c, info, context);
   3401         // the fallback icon
   3402         if (icon == null) {
   3403             mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
   3404         } else {
   3405             info.setIcon(icon);
   3406         }
   3407 
   3408         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
   3409             String title = (c != null) ? c.getString(titleIndex) : null;
   3410             if (!TextUtils.isEmpty(title)) {
   3411                 info.title = Utilities.trim(title);
   3412             }
   3413         } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
   3414             if (TextUtils.isEmpty(info.title)) {
   3415                 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
   3416             }
   3417         } else {
   3418             throw new InvalidParameterException("Invalid restoreType " + promiseType);
   3419         }
   3420 
   3421         info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
   3422         info.itemType = itemType;
   3423         info.promisedIntent = intent;
   3424         info.status = promiseType;
   3425         return info;
   3426     }
   3427 
   3428     /**
   3429      * Make an Intent object for a restored application or shortcut item that points
   3430      * to the market page for the item.
   3431      */
   3432     @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
   3433         ComponentName componentName = intent.getComponent();
   3434         return getMarketIntent(componentName.getPackageName());
   3435     }
   3436 
   3437     static Intent getMarketIntent(String packageName) {
   3438         return new Intent(Intent.ACTION_VIEW)
   3439             .setData(new Uri.Builder()
   3440                 .scheme("market")
   3441                 .authority("details")
   3442                 .appendQueryParameter("id", packageName)
   3443                 .build());
   3444     }
   3445 
   3446     /**
   3447      * Make an ShortcutInfo object for a shortcut that is an application.
   3448      *
   3449      * If c is not null, then it will be used to fill in missing data like the title and icon.
   3450      */
   3451     public ShortcutInfo getAppShortcutInfo(Intent intent,
   3452             UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
   3453             boolean allowMissingTarget, boolean useLowResIcon) {
   3454         if (user == null) {
   3455             Log.d(TAG, "Null user found in getShortcutInfo");
   3456             return null;
   3457         }
   3458 
   3459         ComponentName componentName = intent.getComponent();
   3460         if (componentName == null) {
   3461             Log.d(TAG, "Missing component found in getShortcutInfo");
   3462             return null;
   3463         }
   3464 
   3465         Intent newIntent = new Intent(intent.getAction(), null);
   3466         newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
   3467         newIntent.setComponent(componentName);
   3468         LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
   3469         if ((lai == null) && !allowMissingTarget) {
   3470             Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
   3471             return null;
   3472         }
   3473 
   3474         final ShortcutInfo info = new ShortcutInfo();
   3475         mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
   3476         if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
   3477             Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
   3478             info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
   3479         }
   3480 
   3481         if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
   3482             info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
   3483         }
   3484 
   3485         // from the db
   3486         if (TextUtils.isEmpty(info.title) && c != null) {
   3487             info.title =  Utilities.trim(c.getString(titleIndex));
   3488         }
   3489 
   3490         // fall back to the class name of the activity
   3491         if (info.title == null) {
   3492             info.title = componentName.getClassName();
   3493         }
   3494 
   3495         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
   3496         info.user = user;
   3497         info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
   3498         if (lai != null) {
   3499             info.flags = AppInfo.initFlags(lai);
   3500         }
   3501         return info;
   3502     }
   3503 
   3504     static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
   3505             ItemInfoFilter f) {
   3506         HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
   3507         for (ItemInfo i : infos) {
   3508             if (i instanceof ShortcutInfo) {
   3509                 ShortcutInfo info = (ShortcutInfo) i;
   3510                 ComponentName cn = info.getTargetComponent();
   3511                 if (cn != null && f.filterItem(null, info, cn)) {
   3512                     filtered.add(info);
   3513                 }
   3514             } else if (i instanceof FolderInfo) {
   3515                 FolderInfo info = (FolderInfo) i;
   3516                 for (ShortcutInfo s : info.contents) {
   3517                     ComponentName cn = s.getTargetComponent();
   3518                     if (cn != null && f.filterItem(info, s, cn)) {
   3519                         filtered.add(s);
   3520                     }
   3521                 }
   3522             } else if (i instanceof LauncherAppWidgetInfo) {
   3523                 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
   3524                 ComponentName cn = info.providerName;
   3525                 if (cn != null && f.filterItem(null, info, cn)) {
   3526                     filtered.add(info);
   3527                 }
   3528             }
   3529         }
   3530         return new ArrayList<ItemInfo>(filtered);
   3531     }
   3532 
   3533     @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
   3534             final UserHandleCompat user) {
   3535         ItemInfoFilter filter  = new ItemInfoFilter() {
   3536             @Override
   3537             public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
   3538                 if (info.user == null) {
   3539                     return cn.equals(cname);
   3540                 } else {
   3541                     return cn.equals(cname) && info.user.equals(user);
   3542                 }
   3543             }
   3544         };
   3545         return filterItemInfos(sBgItemsIdMap, filter);
   3546     }
   3547 
   3548     /**
   3549      * Make an ShortcutInfo object for a shortcut that isn't an application.
   3550      */
   3551     @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
   3552             int titleIndex, CursorIconInfo iconInfo) {
   3553         final ShortcutInfo info = new ShortcutInfo();
   3554         // Non-app shortcuts are only supported for current user.
   3555         info.user = UserHandleCompat.myUserHandle();
   3556         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
   3557 
   3558         // TODO: If there's an explicit component and we can't install that, delete it.
   3559 
   3560         info.title = Utilities.trim(c.getString(titleIndex));
   3561 
   3562         Bitmap icon = iconInfo.loadIcon(c, info, context);
   3563         // the fallback icon
   3564         if (icon == null) {
   3565             icon = mIconCache.getDefaultIcon(info.user);
   3566             info.usingFallbackIcon = true;
   3567         }
   3568         info.setIcon(icon);
   3569         return info;
   3570     }
   3571 
   3572     ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
   3573         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
   3574         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
   3575         Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
   3576 
   3577         if (intent == null) {
   3578             // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
   3579             Log.e(TAG, "Can't construct ShorcutInfo with null intent");
   3580             return null;
   3581         }
   3582 
   3583         Bitmap icon = null;
   3584         boolean customIcon = false;
   3585         ShortcutIconResource iconResource = null;
   3586 
   3587         if (bitmap instanceof Bitmap) {
   3588             icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
   3589             customIcon = true;
   3590         } else {
   3591             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
   3592             if (extra instanceof ShortcutIconResource) {
   3593                 iconResource = (ShortcutIconResource) extra;
   3594                 icon = Utilities.createIconBitmap(iconResource.packageName,
   3595                         iconResource.resourceName, context);
   3596             }
   3597         }
   3598 
   3599         final ShortcutInfo info = new ShortcutInfo();
   3600 
   3601         // Only support intents for current user for now. Intents sent from other
   3602         // users wouldn't get here without intent forwarding anyway.
   3603         info.user = UserHandleCompat.myUserHandle();
   3604         if (icon == null) {
   3605             icon = mIconCache.getDefaultIcon(info.user);
   3606             info.usingFallbackIcon = true;
   3607         }
   3608         info.setIcon(icon);
   3609 
   3610         info.title = Utilities.trim(name);
   3611         info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
   3612         info.intent = intent;
   3613         info.customIcon = customIcon;
   3614         info.iconResource = iconResource;
   3615 
   3616         return info;
   3617     }
   3618 
   3619     /**
   3620      * Return an existing FolderInfo object if we have encountered this ID previously,
   3621      * or make a new one.
   3622      */
   3623     @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
   3624         // See if a placeholder was created for us already
   3625         FolderInfo folderInfo = folders.get(id);
   3626         if (folderInfo == null) {
   3627             // No placeholder -- create a new instance
   3628             folderInfo = new FolderInfo();
   3629             folders.put(id, folderInfo);
   3630         }
   3631         return folderInfo;
   3632     }
   3633 
   3634 
   3635     static boolean isValidProvider(AppWidgetProviderInfo provider) {
   3636         return (provider != null) && (provider.provider != null)
   3637                 && (provider.provider.getPackageName() != null);
   3638     }
   3639 
   3640     public void dumpState() {
   3641         Log.d(TAG, "mCallbacks=" + mCallbacks);
   3642         AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
   3643         AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
   3644         AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
   3645         AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
   3646         if (mLoaderTask != null) {
   3647             mLoaderTask.dumpState();
   3648         } else {
   3649             Log.d(TAG, "mLoaderTask=null");
   3650         }
   3651     }
   3652 
   3653     public Callbacks getCallback() {
   3654         return mCallbacks != null ? mCallbacks.get() : null;
   3655     }
   3656 
   3657     /**
   3658      * @return {@link FolderInfo} if its already loaded.
   3659      */
   3660     public FolderInfo findFolderById(Long folderId) {
   3661         synchronized (sBgLock) {
   3662             return sBgFolders.get(folderId);
   3663         }
   3664     }
   3665 
   3666     /**
   3667      * @return the looper for the worker thread which can be used to start background tasks.
   3668      */
   3669     public static Looper getWorkerLooper() {
   3670         return sWorkerThread.getLooper();
   3671     }
   3672 }
   3673