LauncherModel.java revision 81e44914500c7cc6204d25390f5999fa53559044
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 17package com.android.launcher3; 18 19import android.appwidget.AppWidgetProviderInfo; 20import android.content.BroadcastReceiver; 21import android.content.ComponentName; 22import android.content.ContentProviderOperation; 23import android.content.ContentResolver; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.LauncherActivityInfo; 29import android.content.pm.PackageManager; 30import android.net.Uri; 31import android.os.Handler; 32import android.os.HandlerThread; 33import android.os.Looper; 34import android.os.Process; 35import android.os.SystemClock; 36import android.os.Trace; 37import android.os.UserHandle; 38import android.text.TextUtils; 39import android.util.Log; 40import android.util.LongSparseArray; 41import android.util.MutableInt; 42 43import com.android.launcher3.compat.AppWidgetManagerCompat; 44import com.android.launcher3.compat.LauncherAppsCompat; 45import com.android.launcher3.compat.PackageInstallerCompat; 46import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; 47import com.android.launcher3.compat.UserManagerCompat; 48import com.android.launcher3.config.ProviderConfig; 49import com.android.launcher3.dynamicui.ExtractionUtils; 50import com.android.launcher3.folder.Folder; 51import com.android.launcher3.folder.FolderIcon; 52import com.android.launcher3.graphics.LauncherIcons; 53import com.android.launcher3.logging.FileLog; 54import com.android.launcher3.model.AddWorkspaceItemsTask; 55import com.android.launcher3.model.BgDataModel; 56import com.android.launcher3.model.CacheDataUpdatedTask; 57import com.android.launcher3.model.ExtendedModelTask; 58import com.android.launcher3.model.GridSizeMigrationTask; 59import com.android.launcher3.model.LoaderCursor; 60import com.android.launcher3.model.PackageInstallStateChangedTask; 61import com.android.launcher3.model.PackageItemInfo; 62import com.android.launcher3.model.PackageUpdatedTask; 63import com.android.launcher3.model.SdCardAvailableReceiver; 64import com.android.launcher3.model.ShortcutsChangedTask; 65import com.android.launcher3.model.UserLockStateChangedTask; 66import com.android.launcher3.model.WidgetItem; 67import com.android.launcher3.model.WidgetsModel; 68import com.android.launcher3.provider.ImportDataTask; 69import com.android.launcher3.provider.LauncherDbUtils; 70import com.android.launcher3.shortcuts.DeepShortcutManager; 71import com.android.launcher3.shortcuts.ShortcutInfoCompat; 72import com.android.launcher3.shortcuts.ShortcutKey; 73import com.android.launcher3.util.ComponentKey; 74import com.android.launcher3.util.ContentWriter; 75import com.android.launcher3.util.ItemInfoMatcher; 76import com.android.launcher3.util.ManagedProfileHeuristic; 77import com.android.launcher3.util.MultiHashMap; 78import com.android.launcher3.util.PackageManagerHelper; 79import com.android.launcher3.util.Preconditions; 80import com.android.launcher3.util.Provider; 81import com.android.launcher3.util.Thunk; 82import com.android.launcher3.util.ViewOnDrawExecutor; 83 84import java.lang.ref.WeakReference; 85import java.net.URISyntaxException; 86import java.util.ArrayList; 87import java.util.Collections; 88import java.util.Comparator; 89import java.util.HashMap; 90import java.util.HashSet; 91import java.util.Iterator; 92import java.util.List; 93import java.util.Map; 94import java.util.Set; 95import java.util.concurrent.Executor; 96 97/** 98 * Maintains in-memory state of the Launcher. It is expected that there should be only one 99 * LauncherModel object held in a static. Also provide APIs for updating the database state 100 * for the Launcher. 101 */ 102public class LauncherModel extends BroadcastReceiver 103 implements LauncherAppsCompat.OnAppsChangedCallbackCompat { 104 static final boolean DEBUG_LOADERS = false; 105 private static final boolean DEBUG_RECEIVER = false; 106 107 static final String TAG = "Launcher.Model"; 108 109 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 110 private static final long INVALID_SCREEN_ID = -1L; 111 112 @Thunk final LauncherAppState mApp; 113 @Thunk final Object mLock = new Object(); 114 @Thunk DeferredHandler mHandler = new DeferredHandler(); 115 @Thunk LoaderTask mLoaderTask; 116 @Thunk boolean mIsLoaderTaskRunning; 117 @Thunk boolean mHasLoaderCompletedOnce; 118 119 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); 120 static { 121 sWorkerThread.start(); 122 } 123 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper()); 124 125 // We start off with everything not loaded. After that, we assume that 126 // our monitoring of the package manager provides all updates and we never 127 // need to do a requery. These are only ever touched from the loader thread. 128 private boolean mWorkspaceLoaded; 129 private boolean mAllAppsLoaded; 130 private boolean mDeepShortcutsLoaded; 131 132 /** 133 * Set of runnables to be called on the background thread after the workspace binding 134 * is complete. 135 */ 136 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>(); 137 138 @Thunk WeakReference<Callbacks> mCallbacks; 139 140 // < only access in worker thread > 141 private final AllAppsList mBgAllAppsList; 142 // Entire list of widgets. 143 private final WidgetsModel mBgWidgetsModel; 144 145 private boolean mHasShortcutHostPermission; 146 // Runnable to check if the shortcuts permission has changed. 147 private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { 148 @Override 149 public void run() { 150 if (mDeepShortcutsLoaded) { 151 boolean hasShortcutHostPermission = 152 DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission(); 153 if (hasShortcutHostPermission != mHasShortcutHostPermission) { 154 mApp.reloadWorkspace(); 155 } 156 } 157 } 158 }; 159 160 /** 161 * All the static data should be accessed on the background thread, A lock should be acquired 162 * on this object when accessing any data from this model. 163 */ 164 static final BgDataModel sBgDataModel = new BgDataModel(); 165 166 // </ only access in worker thread > 167 168 private final IconCache mIconCache; 169 170 private final LauncherAppsCompat mLauncherApps; 171 private final UserManagerCompat mUserManager; 172 173 public interface Callbacks { 174 public boolean setLoadOnResume(); 175 public int getCurrentWorkspaceScreen(); 176 public void clearPendingBinds(); 177 public void startBinding(); 178 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, 179 boolean forceAnimateIcons); 180 public void bindScreens(ArrayList<Long> orderedScreenIds); 181 public void finishFirstPageBind(ViewOnDrawExecutor executor); 182 public void finishBindingItems(); 183 public void bindAppWidget(LauncherAppWidgetInfo info); 184 public void bindAllApplications(ArrayList<AppInfo> apps); 185 public void bindAppsAdded(ArrayList<Long> newScreens, 186 ArrayList<ItemInfo> addNotAnimated, 187 ArrayList<ItemInfo> addAnimated, 188 ArrayList<AppInfo> addedApps); 189 public void bindAppsUpdated(ArrayList<AppInfo> apps); 190 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, 191 ArrayList<ShortcutInfo> removed, UserHandle user); 192 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); 193 public void bindRestoreItemsChange(HashSet<ItemInfo> updates); 194 public void bindWorkspaceComponentsRemoved( 195 HashSet<String> packageNames, HashSet<ComponentName> components, 196 UserHandle user); 197 public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); 198 public void notifyWidgetProvidersChanged(); 199 public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets); 200 public void onPageBoundSynchronously(int page); 201 public void executeOnNextDraw(ViewOnDrawExecutor executor); 202 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap); 203 } 204 205 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { 206 Context context = app.getContext(); 207 mApp = app; 208 mBgAllAppsList = new AllAppsList(iconCache, appFilter); 209 mBgWidgetsModel = new WidgetsModel(iconCache, appFilter); 210 mIconCache = iconCache; 211 212 mLauncherApps = LauncherAppsCompat.getInstance(context); 213 mUserManager = UserManagerCompat.getInstance(context); 214 } 215 216 /** Runs the specified runnable immediately if called from the main thread, otherwise it is 217 * posted on the main thread handler. */ 218 private void runOnMainThread(Runnable r) { 219 if (sWorkerThread.getThreadId() == Process.myTid()) { 220 // If we are on the worker thread, post onto the main handler 221 mHandler.post(r); 222 } else { 223 r.run(); 224 } 225 } 226 227 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is 228 * posted on the worker thread handler. */ 229 private static void runOnWorkerThread(Runnable r) { 230 if (sWorkerThread.getThreadId() == Process.myTid()) { 231 r.run(); 232 } else { 233 // If we are not on the worker thread, then post to the worker handler 234 sWorker.post(r); 235 } 236 } 237 238 public void setPackageState(PackageInstallInfo installInfo) { 239 enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); 240 } 241 242 /** 243 * Updates the icons and label of all pending icons for the provided package name. 244 */ 245 public void updateSessionDisplayInfo(final String packageName) { 246 HashSet<String> packages = new HashSet<>(); 247 packages.add(packageName); 248 enqueueModelUpdateTask(new CacheDataUpdatedTask( 249 CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages)); 250 } 251 252 /** 253 * Adds the provided items to the workspace. 254 */ 255 public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) { 256 addAndBindAddedWorkspaceItems(Provider.of(workspaceApps)); 257 } 258 259 /** 260 * Adds the provided items to the workspace. 261 */ 262 public void addAndBindAddedWorkspaceItems( 263 Provider<List<ItemInfo>> appsProvider) { 264 enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider)); 265 } 266 267 /** 268 * Adds an item to the DB if it was not created previously, or move it to a new 269 * <container, screen, cellX, cellY> 270 */ 271 public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, 272 long screenId, int cellX, int cellY) { 273 if (item.container == ItemInfo.NO_ID) { 274 // From all apps 275 addItemToDatabase(context, item, container, screenId, cellX, cellY); 276 } else { 277 // From somewhere else 278 moveItemInDatabase(context, item, container, screenId, cellX, cellY); 279 } 280 } 281 282 static void checkItemInfoLocked( 283 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) { 284 ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); 285 if (modelItem != null && item != modelItem) { 286 // check all the data is consistent 287 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { 288 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem; 289 ShortcutInfo shortcut = (ShortcutInfo) item; 290 if (modelShortcut.title.toString().equals(shortcut.title.toString()) && 291 modelShortcut.intent.filterEquals(shortcut.intent) && 292 modelShortcut.id == shortcut.id && 293 modelShortcut.itemType == shortcut.itemType && 294 modelShortcut.container == shortcut.container && 295 modelShortcut.screenId == shortcut.screenId && 296 modelShortcut.cellX == shortcut.cellX && 297 modelShortcut.cellY == shortcut.cellY && 298 modelShortcut.spanX == shortcut.spanX && 299 modelShortcut.spanY == shortcut.spanY) { 300 // For all intents and purposes, this is the same object 301 return; 302 } 303 } 304 305 // the modelItem needs to match up perfectly with item if our model is 306 // to be consistent with the database-- for now, just require 307 // modelItem == item or the equality check above 308 String msg = "item: " + ((item != null) ? item.toString() : "null") + 309 "modelItem: " + 310 ((modelItem != null) ? modelItem.toString() : "null") + 311 "Error: ItemInfo passed to checkItemInfo doesn't match original"; 312 RuntimeException e = new RuntimeException(msg); 313 if (stackTrace != null) { 314 e.setStackTrace(stackTrace); 315 } 316 throw e; 317 } 318 } 319 320 static void checkItemInfo(final ItemInfo item) { 321 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 322 final long itemId = item.id; 323 Runnable r = new Runnable() { 324 public void run() { 325 synchronized (sBgDataModel) { 326 checkItemInfoLocked(itemId, item, stackTrace); 327 } 328 } 329 }; 330 runOnWorkerThread(r); 331 } 332 333 static void updateItemInDatabaseHelper(Context context, final ContentWriter writer, 334 final ItemInfo item, final String callingFunction) { 335 final long itemId = item.id; 336 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId); 337 final ContentResolver cr = context.getContentResolver(); 338 339 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 340 final Context appContext = context.getApplicationContext(); 341 Runnable r = new Runnable() { 342 public void run() { 343 cr.update(uri, writer.getValues(appContext), null, null); 344 updateItemArrays(item, itemId, stackTrace); 345 } 346 }; 347 runOnWorkerThread(r); 348 } 349 350 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList, 351 final ArrayList<ItemInfo> items, final String callingFunction) { 352 final ContentResolver cr = context.getContentResolver(); 353 354 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 355 Runnable r = new Runnable() { 356 public void run() { 357 ArrayList<ContentProviderOperation> ops = 358 new ArrayList<ContentProviderOperation>(); 359 int count = items.size(); 360 for (int i = 0; i < count; i++) { 361 ItemInfo item = items.get(i); 362 final long itemId = item.id; 363 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId); 364 ContentValues values = valuesList.get(i); 365 366 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); 367 updateItemArrays(item, itemId, stackTrace); 368 369 } 370 try { 371 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 372 } catch (Exception e) { 373 e.printStackTrace(); 374 } 375 } 376 }; 377 runOnWorkerThread(r); 378 } 379 380 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) { 381 // Lock on mBgLock *after* the db operation 382 synchronized (sBgDataModel) { 383 checkItemInfoLocked(itemId, item, stackTrace); 384 385 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 386 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 387 // Item is in a folder, make sure this folder exists 388 if (!sBgDataModel.folders.containsKey(item.container)) { 389 // An items container is being set to a that of an item which is not in 390 // the list of Folders. 391 String msg = "item: " + item + " container being set to: " + 392 item.container + ", not in the list of folders"; 393 Log.e(TAG, msg); 394 } 395 } 396 397 // Items are added/removed from the corresponding FolderInfo elsewhere, such 398 // as in Workspace.onDrop. Here, we just add/remove them from the list of items 399 // that are on the desktop, as appropriate 400 ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); 401 if (modelItem != null && 402 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 403 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) { 404 switch (modelItem.itemType) { 405 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 406 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 407 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 408 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 409 if (!sBgDataModel.workspaceItems.contains(modelItem)) { 410 sBgDataModel.workspaceItems.add(modelItem); 411 } 412 break; 413 default: 414 break; 415 } 416 } else { 417 sBgDataModel.workspaceItems.remove(modelItem); 418 } 419 } 420 } 421 422 /** 423 * Move an item in the DB to a new <container, screen, cellX, cellY> 424 */ 425 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container, 426 final long screenId, final int cellX, final int cellY) { 427 item.container = container; 428 item.cellX = cellX; 429 item.cellY = cellY; 430 431 // We store hotseat items in canonical form which is this orientation invariant position 432 // in the hotseat 433 if (context instanceof Launcher && screenId < 0 && 434 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 435 item.screenId = Launcher.getLauncher(context).getHotseat() 436 .getOrderInHotseat(cellX, cellY); 437 } else { 438 item.screenId = screenId; 439 } 440 441 final ContentWriter writer = new ContentWriter(context) 442 .put(LauncherSettings.Favorites.CONTAINER, item.container) 443 .put(LauncherSettings.Favorites.CELLX, item.cellX) 444 .put(LauncherSettings.Favorites.CELLY, item.cellY) 445 .put(LauncherSettings.Favorites.RANK, item.rank) 446 .put(LauncherSettings.Favorites.SCREEN, item.screenId); 447 448 updateItemInDatabaseHelper(context, writer, item, "moveItemInDatabase"); 449 } 450 451 /** 452 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the 453 * cellX, cellY have already been updated on the ItemInfos. 454 */ 455 public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items, 456 final long container, final int screen) { 457 458 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>(); 459 int count = items.size(); 460 461 for (int i = 0; i < count; i++) { 462 ItemInfo item = items.get(i); 463 item.container = container; 464 465 // We store hotseat items in canonical form which is this orientation invariant position 466 // in the hotseat 467 if (context instanceof Launcher && screen < 0 && 468 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 469 item.screenId = Launcher.getLauncher(context).getHotseat().getOrderInHotseat(item.cellX, 470 item.cellY); 471 } else { 472 item.screenId = screen; 473 } 474 475 final ContentValues values = new ContentValues(); 476 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 477 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 478 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 479 values.put(LauncherSettings.Favorites.RANK, item.rank); 480 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 481 482 contentValues.add(values); 483 } 484 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase"); 485 } 486 487 /** 488 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY> 489 */ 490 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container, 491 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) { 492 item.container = container; 493 item.cellX = cellX; 494 item.cellY = cellY; 495 item.spanX = spanX; 496 item.spanY = spanY; 497 498 // We store hotseat items in canonical form which is this orientation invariant position 499 // in the hotseat 500 if (context instanceof Launcher && screenId < 0 && 501 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 502 item.screenId = Launcher.getLauncher(context).getHotseat() 503 .getOrderInHotseat(cellX, cellY); 504 } else { 505 item.screenId = screenId; 506 } 507 508 final ContentWriter writer = new ContentWriter(context) 509 .put(LauncherSettings.Favorites.CONTAINER, item.container) 510 .put(LauncherSettings.Favorites.CELLX, item.cellX) 511 .put(LauncherSettings.Favorites.CELLY, item.cellY) 512 .put(LauncherSettings.Favorites.RANK, item.rank) 513 .put(LauncherSettings.Favorites.SPANX, item.spanX) 514 .put(LauncherSettings.Favorites.SPANY, item.spanY) 515 .put(LauncherSettings.Favorites.SCREEN, item.screenId); 516 517 updateItemInDatabaseHelper(context, writer, item, "modifyItemInDatabase"); 518 } 519 520 /** 521 * Update an item to the database in a specified container. 522 */ 523 public static void updateItemInDatabase(Context context, final ItemInfo item) { 524 ContentWriter writer = new ContentWriter(context); 525 item.onAddToDatabase(writer); 526 updateItemInDatabaseHelper(context, writer, item, "updateItemInDatabase"); 527 } 528 529 /** 530 * Add an item to the database in a specified container. Sets the container, screen, cellX and 531 * cellY fields of the item. Also assigns an ID to the item. 532 */ 533 public static void addItemToDatabase(Context context, final ItemInfo item, final long container, 534 final long screenId, final int cellX, final int cellY) { 535 item.container = container; 536 item.cellX = cellX; 537 item.cellY = cellY; 538 // We store hotseat items in canonical form which is this orientation invariant position 539 // in the hotseat 540 if (context instanceof Launcher && screenId < 0 && 541 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 542 item.screenId = Launcher.getLauncher(context).getHotseat() 543 .getOrderInHotseat(cellX, cellY); 544 } else { 545 item.screenId = screenId; 546 } 547 548 final ContentWriter writer = new ContentWriter(context); 549 final ContentResolver cr = context.getContentResolver(); 550 item.onAddToDatabase(writer); 551 552 item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) 553 .getLong(LauncherSettings.Settings.EXTRA_VALUE); 554 555 writer.put(LauncherSettings.Favorites._ID, item.id); 556 557 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 558 final Context appContext = context.getApplicationContext(); 559 Runnable r = new Runnable() { 560 public void run() { 561 cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(appContext)); 562 563 synchronized (sBgDataModel) { 564 checkItemInfoLocked(item.id, item, stackTrace); 565 sBgDataModel.addItem(appContext, item, true); 566 } 567 } 568 }; 569 runOnWorkerThread(r); 570 } 571 572 /** 573 * Removes the specified item from the database 574 */ 575 public static void deleteItemFromDatabase(Context context, final ItemInfo item) { 576 ArrayList<ItemInfo> items = new ArrayList<>(); 577 items.add(item); 578 deleteItemsFromDatabase(context, items); 579 } 580 581 /** 582 * Removes all the items from the database matching {@param matcher}. 583 */ 584 public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) { 585 deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap)); 586 } 587 588 /** 589 * Removes the specified items from the database 590 */ 591 public static void deleteItemsFromDatabase(Context context, 592 final Iterable<? extends ItemInfo> items) { 593 final ContentResolver cr = context.getContentResolver(); 594 final Context appContext = context.getApplicationContext(); 595 Runnable r = new Runnable() { 596 public void run() { 597 for (ItemInfo item : items) { 598 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id); 599 cr.delete(uri, null, null); 600 601 sBgDataModel.removeItem(appContext, item); 602 } 603 } 604 }; 605 runOnWorkerThread(r); 606 } 607 608 /** 609 * Update the order of the workspace screens in the database. The array list contains 610 * a list of screen ids in the order that they should appear. 611 */ 612 public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { 613 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); 614 final ContentResolver cr = context.getContentResolver(); 615 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 616 617 // Remove any negative screen ids -- these aren't persisted 618 Iterator<Long> iter = screensCopy.iterator(); 619 while (iter.hasNext()) { 620 long id = iter.next(); 621 if (id < 0) { 622 iter.remove(); 623 } 624 } 625 626 Runnable r = new Runnable() { 627 @Override 628 public void run() { 629 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 630 // Clear the table 631 ops.add(ContentProviderOperation.newDelete(uri).build()); 632 int count = screensCopy.size(); 633 for (int i = 0; i < count; i++) { 634 ContentValues v = new ContentValues(); 635 long screenId = screensCopy.get(i); 636 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 637 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 638 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); 639 } 640 641 try { 642 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 643 } catch (Exception ex) { 644 throw new RuntimeException(ex); 645 } 646 647 synchronized (sBgDataModel) { 648 sBgDataModel.workspaceScreens.clear(); 649 sBgDataModel.workspaceScreens.addAll(screensCopy); 650 } 651 } 652 }; 653 runOnWorkerThread(r); 654 } 655 656 /** 657 * Remove the specified folder and all its contents from the database. 658 */ 659 public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) { 660 final ContentResolver cr = context.getContentResolver(); 661 final Context appContext = context.getApplicationContext(); 662 663 Runnable r = new Runnable() { 664 public void run() { 665 cr.delete(LauncherSettings.Favorites.CONTENT_URI, 666 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); 667 sBgDataModel.removeItem(appContext, info.contents); 668 info.contents.clear(); 669 670 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null); 671 sBgDataModel.removeItem(appContext, info); 672 } 673 }; 674 runOnWorkerThread(r); 675 } 676 677 /** 678 * Set this as the current Launcher activity object for the loader. 679 */ 680 public void initialize(Callbacks callbacks) { 681 synchronized (mLock) { 682 Preconditions.assertUIThread(); 683 // Remove any queued UI runnables 684 mHandler.cancelAll(); 685 mCallbacks = new WeakReference<>(callbacks); 686 } 687 } 688 689 @Override 690 public void onPackageChanged(String packageName, UserHandle user) { 691 int op = PackageUpdatedTask.OP_UPDATE; 692 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); 693 } 694 695 @Override 696 public void onPackageRemoved(String packageName, UserHandle user) { 697 onPackagesRemoved(user, packageName); 698 } 699 700 public void onPackagesRemoved(UserHandle user, String... packages) { 701 int op = PackageUpdatedTask.OP_REMOVE; 702 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); 703 } 704 705 @Override 706 public void onPackageAdded(String packageName, UserHandle user) { 707 int op = PackageUpdatedTask.OP_ADD; 708 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); 709 } 710 711 @Override 712 public void onPackagesAvailable(String[] packageNames, UserHandle user, 713 boolean replacing) { 714 enqueueModelUpdateTask( 715 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames)); 716 } 717 718 @Override 719 public void onPackagesUnavailable(String[] packageNames, UserHandle user, 720 boolean replacing) { 721 if (!replacing) { 722 enqueueModelUpdateTask(new PackageUpdatedTask( 723 PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames)); 724 } 725 } 726 727 @Override 728 public void onPackagesSuspended(String[] packageNames, UserHandle user) { 729 enqueueModelUpdateTask(new PackageUpdatedTask( 730 PackageUpdatedTask.OP_SUSPEND, user, packageNames)); 731 } 732 733 @Override 734 public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { 735 enqueueModelUpdateTask(new PackageUpdatedTask( 736 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames)); 737 } 738 739 @Override 740 public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, 741 UserHandle user) { 742 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); 743 } 744 745 public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, 746 UserHandle user) { 747 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); 748 } 749 750 /** 751 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 752 * ACTION_PACKAGE_CHANGED. 753 */ 754 @Override 755 public void onReceive(Context context, Intent intent) { 756 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); 757 758 final String action = intent.getAction(); 759 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 760 // If we have changed locale we need to clear out the labels in all apps/workspace. 761 forceReload(); 762 } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) 763 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { 764 UserManagerCompat.getInstance(context).enableAndResetCache(); 765 forceReload(); 766 } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 767 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 768 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 769 UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); 770 if (user != null) { 771 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 772 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { 773 enqueueModelUpdateTask(new PackageUpdatedTask( 774 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); 775 } 776 777 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so 778 // we need to run the state change task again. 779 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 780 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 781 enqueueModelUpdateTask(new UserLockStateChangedTask(user)); 782 } 783 } 784 } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) { 785 ExtractionUtils.startColorExtractionServiceIfNecessary(context); 786 } 787 } 788 789 void forceReload() { 790 resetLoadedState(true, true); 791 792 // Do this here because if the launcher activity is running it will be restarted. 793 // If it's not running startLoaderFromBackground will merely tell it that it needs 794 // to reload. 795 startLoaderFromBackground(); 796 } 797 798 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) { 799 synchronized (mLock) { 800 // Stop any existing loaders first, so they don't set mAllAppsLoaded or 801 // mWorkspaceLoaded to true later 802 stopLoaderLocked(); 803 if (resetAllAppsLoaded) mAllAppsLoaded = false; 804 if (resetWorkspaceLoaded) mWorkspaceLoaded = false; 805 // Always reset deep shortcuts loaded. 806 // TODO: why? 807 mDeepShortcutsLoaded = false; 808 } 809 } 810 811 /** 812 * When the launcher is in the background, it's possible for it to miss paired 813 * configuration changes. So whenever we trigger the loader from the background 814 * tell the launcher that it needs to re-run the loader when it comes back instead 815 * of doing it now. 816 */ 817 public void startLoaderFromBackground() { 818 Callbacks callbacks = getCallback(); 819 if (callbacks != null) { 820 // Only actually run the loader if they're not paused. 821 if (!callbacks.setLoadOnResume()) { 822 startLoader(callbacks.getCurrentWorkspaceScreen()); 823 } 824 } 825 } 826 827 /** 828 * If there is already a loader task running, tell it to stop. 829 */ 830 private void stopLoaderLocked() { 831 LoaderTask oldTask = mLoaderTask; 832 if (oldTask != null) { 833 oldTask.stopLocked(); 834 } 835 } 836 837 public boolean isCurrentCallbacks(Callbacks callbacks) { 838 return (mCallbacks != null && mCallbacks.get() == callbacks); 839 } 840 841 /** 842 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. 843 * @return true if the page could be bound synchronously. 844 */ 845 public boolean startLoader(int synchronousBindPage) { 846 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems 847 InstallShortcutReceiver.enableInstallQueue(); 848 synchronized (mLock) { 849 // Don't bother to start the thread if we know it's not going to do anything 850 if (mCallbacks != null && mCallbacks.get() != null) { 851 final Callbacks oldCallbacks = mCallbacks.get(); 852 // Clear any pending bind-runnables from the synchronized load process. 853 runOnMainThread(new Runnable() { 854 public void run() { 855 oldCallbacks.clearPendingBinds(); 856 } 857 }); 858 859 // If there is already one running, tell it to stop. 860 stopLoaderLocked(); 861 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage); 862 // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind. 863 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded 864 && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) { 865 mLoaderTask.runBindSynchronousPage(synchronousBindPage); 866 return true; 867 } else { 868 sWorkerThread.setPriority(Thread.NORM_PRIORITY); 869 sWorker.post(mLoaderTask); 870 } 871 } 872 } 873 return false; 874 } 875 876 public void stopLoader() { 877 synchronized (mLock) { 878 if (mLoaderTask != null) { 879 mLoaderTask.stopLocked(); 880 } 881 } 882 } 883 884 /** 885 * Loads the workspace screen ids in an ordered list. 886 */ 887 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) { 888 final ContentResolver contentResolver = context.getContentResolver(); 889 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 890 891 // Get screens ordered by rank. 892 return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query( 893 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); 894 } 895 896 /** 897 * Runnable for the thread that loads the contents of the launcher: 898 * - workspace icons 899 * - widgets 900 * - all apps icons 901 * - deep shortcuts within apps 902 */ 903 private class LoaderTask implements Runnable { 904 private Context mContext; 905 private int mPageToBindFirst; 906 907 @Thunk boolean mIsLoadingAndBindingWorkspace; 908 private boolean mStopped; 909 @Thunk boolean mLoadAndBindStepFinished; 910 911 LoaderTask(Context context, int pageToBindFirst) { 912 mContext = context; 913 mPageToBindFirst = pageToBindFirst; 914 } 915 916 private void loadAndBindWorkspace() { 917 mIsLoadingAndBindingWorkspace = true; 918 919 // Load the workspace 920 if (DEBUG_LOADERS) { 921 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); 922 } 923 924 if (!mWorkspaceLoaded) { 925 loadWorkspace(); 926 synchronized (LoaderTask.this) { 927 if (mStopped) { 928 return; 929 } 930 mWorkspaceLoaded = true; 931 } 932 } 933 934 // Bind the workspace 935 bindWorkspace(mPageToBindFirst); 936 } 937 938 private void waitForIdle() { 939 // Wait until the either we're stopped or the other threads are done. 940 // This way we don't start loading all apps until the workspace has settled 941 // down. 942 synchronized (LoaderTask.this) { 943 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 944 945 mHandler.postIdle(new Runnable() { 946 public void run() { 947 synchronized (LoaderTask.this) { 948 mLoadAndBindStepFinished = true; 949 if (DEBUG_LOADERS) { 950 Log.d(TAG, "done with previous binding step"); 951 } 952 LoaderTask.this.notify(); 953 } 954 } 955 }); 956 957 while (!mStopped && !mLoadAndBindStepFinished) { 958 try { 959 // Just in case mFlushingWorkerThread changes but we aren't woken up, 960 // wait no longer than 1sec at a time 961 this.wait(1000); 962 } catch (InterruptedException ex) { 963 // Ignore 964 } 965 } 966 if (DEBUG_LOADERS) { 967 Log.d(TAG, "waited " 968 + (SystemClock.uptimeMillis()-workspaceWaitTime) 969 + "ms for previous step to finish binding"); 970 } 971 } 972 } 973 974 void runBindSynchronousPage(int synchronousBindPage) { 975 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) { 976 // Ensure that we have a valid page index to load synchronously 977 throw new RuntimeException("Should not call runBindSynchronousPage() without " + 978 "valid page index"); 979 } 980 if (!mAllAppsLoaded || !mWorkspaceLoaded) { 981 // Ensure that we don't try and bind a specified page when the pages have not been 982 // loaded already (we should load everything asynchronously in that case) 983 throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); 984 } 985 synchronized (mLock) { 986 if (mIsLoaderTaskRunning) { 987 // Ensure that we are never running the background loading at this point since 988 // we also touch the background collections 989 throw new RuntimeException("Error! Background loading is already running"); 990 } 991 } 992 993 // XXX: Throw an exception if we are already loading (since we touch the worker thread 994 // data structures, we can't allow any other thread to touch that data, but because 995 // this call is synchronous, we can get away with not locking). 996 997 // The LauncherModel is static in the LauncherAppState and mHandler may have queued 998 // operations from the previous activity. We need to ensure that all queued operations 999 // are executed before any synchronous binding work is done. 1000 mHandler.flush(); 1001 1002 // Divide the set of loaded items into those that we are binding synchronously, and 1003 // everything else that is to be bound normally (asynchronously). 1004 bindWorkspace(synchronousBindPage); 1005 // XXX: For now, continue posting the binding of AllApps as there are other issues that 1006 // arise from that. 1007 onlyBindAllApps(); 1008 1009 bindDeepShortcuts(); 1010 } 1011 1012 public void run() { 1013 synchronized (mLock) { 1014 if (mStopped) { 1015 return; 1016 } 1017 mIsLoaderTaskRunning = true; 1018 } 1019 // Optimize for end-user experience: if the Launcher is up and // running with the 1020 // All Apps interface in the foreground, load All Apps first. Otherwise, load the 1021 // workspace first (default). 1022 keep_running: { 1023 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 1024 loadAndBindWorkspace(); 1025 1026 if (mStopped) { 1027 break keep_running; 1028 } 1029 1030 waitForIdle(); 1031 1032 // second step 1033 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 1034 loadAndBindAllApps(); 1035 1036 waitForIdle(); 1037 1038 // third step 1039 if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts"); 1040 loadAndBindDeepShortcuts(); 1041 } 1042 1043 // Clear out this reference, otherwise we end up holding it until all of the 1044 // callback runnables are done. 1045 mContext = null; 1046 1047 synchronized (mLock) { 1048 // If we are still the last one to be scheduled, remove ourselves. 1049 if (mLoaderTask == this) { 1050 mLoaderTask = null; 1051 } 1052 mIsLoaderTaskRunning = false; 1053 mHasLoaderCompletedOnce = true; 1054 } 1055 } 1056 1057 public void stopLocked() { 1058 synchronized (LoaderTask.this) { 1059 mStopped = true; 1060 this.notify(); 1061 } 1062 } 1063 1064 /** 1065 * Gets the callbacks object. If we've been stopped, or if the launcher object 1066 * has somehow been garbage collected, return null instead. Pass in the Callbacks 1067 * object that was around when the deferred message was scheduled, and if there's 1068 * a new Callbacks object around then also return null. This will save us from 1069 * calling onto it with data that will be ignored. 1070 */ 1071 Callbacks tryGetCallbacks(Callbacks oldCallbacks) { 1072 synchronized (mLock) { 1073 if (mStopped) { 1074 return null; 1075 } 1076 1077 if (mCallbacks == null) { 1078 return null; 1079 } 1080 1081 final Callbacks callbacks = mCallbacks.get(); 1082 if (callbacks != oldCallbacks) { 1083 return null; 1084 } 1085 if (callbacks == null) { 1086 Log.w(TAG, "no mCallbacks"); 1087 return null; 1088 } 1089 1090 return callbacks; 1091 } 1092 } 1093 1094 private void loadWorkspace() { 1095 if (LauncherAppState.PROFILE_STARTUP) { 1096 Trace.beginSection("Loading Workspace"); 1097 } 1098 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1099 1100 final Context context = mContext; 1101 final ContentResolver contentResolver = context.getContentResolver(); 1102 final PackageManager manager = context.getPackageManager(); 1103 final boolean isSafeMode = manager.isSafeMode(); 1104 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 1105 final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context); 1106 final boolean isSdCardReady = Utilities.isBootCompleted(); 1107 final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>(); 1108 1109 boolean clearDb = false; 1110 try { 1111 ImportDataTask.performImportIfPossible(context); 1112 } catch (Exception e) { 1113 // Migration failed. Clear workspace. 1114 clearDb = true; 1115 } 1116 1117 if (!clearDb && GridSizeMigrationTask.ENABLED && 1118 !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) { 1119 // Migration failed. Clear workspace. 1120 clearDb = true; 1121 } 1122 1123 if (clearDb) { 1124 Log.d(TAG, "loadWorkspace: resetting launcher database"); 1125 LauncherSettings.Settings.call(contentResolver, 1126 LauncherSettings.Settings.METHOD_DELETE_DB); 1127 } 1128 1129 Log.d(TAG, "loadWorkspace: loading default favorites"); 1130 LauncherSettings.Settings.call(contentResolver, 1131 LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); 1132 1133 synchronized (sBgDataModel) { 1134 sBgDataModel.clear(); 1135 1136 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat 1137 .getInstance(mContext).updateAndGetActiveSessionCache(); 1138 sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); 1139 1140 Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>(); 1141 final LoaderCursor c = new LoaderCursor(contentResolver.query( 1142 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp); 1143 1144 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; 1145 1146 try { 1147 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 1148 LauncherSettings.Favorites.APPWIDGET_ID); 1149 final int appWidgetProviderIndex = c.getColumnIndexOrThrow( 1150 LauncherSettings.Favorites.APPWIDGET_PROVIDER); 1151 final int spanXIndex = c.getColumnIndexOrThrow 1152 (LauncherSettings.Favorites.SPANX); 1153 final int spanYIndex = c.getColumnIndexOrThrow( 1154 LauncherSettings.Favorites.SPANY); 1155 final int rankIndex = c.getColumnIndexOrThrow( 1156 LauncherSettings.Favorites.RANK); 1157 final int optionsIndex = c.getColumnIndexOrThrow( 1158 LauncherSettings.Favorites.OPTIONS); 1159 1160 final LongSparseArray<UserHandle> allUsers = c.allUsers; 1161 final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); 1162 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); 1163 for (UserHandle user : mUserManager.getUserProfiles()) { 1164 long serialNo = mUserManager.getSerialNumberForUser(user); 1165 allUsers.put(serialNo, user); 1166 quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user)); 1167 1168 boolean userUnlocked = mUserManager.isUserUnlocked(user); 1169 1170 // We can only query for shortcuts when the user is unlocked. 1171 if (userUnlocked) { 1172 List<ShortcutInfoCompat> pinnedShortcuts = 1173 shortcutManager.queryForPinnedShortcuts(null, user); 1174 if (shortcutManager.wasLastCallSuccess()) { 1175 for (ShortcutInfoCompat shortcut : pinnedShortcuts) { 1176 shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), 1177 shortcut); 1178 } 1179 } else { 1180 // Shortcut manager can fail due to some race condition when the 1181 // lock state changes too frequently. For the purpose of the loading 1182 // shortcuts, consider the user is still locked. 1183 userUnlocked = false; 1184 } 1185 } 1186 unlockedUsers.put(serialNo, userUnlocked); 1187 } 1188 1189 ShortcutInfo info; 1190 String intentDescription; 1191 LauncherAppWidgetInfo appWidgetInfo; 1192 Intent intent; 1193 String targetPkg; 1194 1195 while (!mStopped && c.moveToNext()) { 1196 try { 1197 if (c.user == null) { 1198 // User has been deleted, remove the item. 1199 c.markDeleted("User has been deleted"); 1200 continue; 1201 } 1202 1203 boolean allowMissingTarget = false; 1204 switch (c.itemType) { 1205 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1206 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1207 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 1208 intent = c.parseIntent(); 1209 if (intent == null) { 1210 c.markDeleted("Invalid or null intent"); 1211 continue; 1212 } 1213 1214 int disabledState = quietMode.get(c.serialNumber) ? 1215 ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0; 1216 ComponentName cn = intent.getComponent(); 1217 targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); 1218 1219 if (!Process.myUserHandle().equals(c.user)) { 1220 if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 1221 c.markDeleted("Legacy shortcuts are only allowed for default user"); 1222 continue; 1223 } else if (c.restoreFlag != 0) { 1224 // Don't restore items for other profiles. 1225 c.markDeleted("Restore from managed profile not supported"); 1226 continue; 1227 } 1228 } 1229 if (TextUtils.isEmpty(targetPkg) && 1230 c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 1231 c.markDeleted("Only legacy shortcuts can have null package"); 1232 continue; 1233 } 1234 1235 // If there is no target package, its an implicit intent 1236 // (legacy shortcut) which is always valid 1237 boolean validTarget = TextUtils.isEmpty(targetPkg) || 1238 launcherApps.isPackageEnabledForProfile(targetPkg, c.user); 1239 1240 if (cn != null && validTarget) { 1241 // If the apk is present and the shortcut points to a specific 1242 // component. 1243 1244 // If the component is already present 1245 if (launcherApps.isActivityEnabledForProfile(cn, c.user)) { 1246 // no special handling necessary for this item 1247 c.markRestored(); 1248 } else { 1249 if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { 1250 // We allow auto install apps to have their intent 1251 // updated after an install. 1252 intent = manager.getLaunchIntentForPackage(targetPkg); 1253 if (intent != null) { 1254 c.restoreFlag = 0; 1255 c.updater().put( 1256 LauncherSettings.Favorites.INTENT, 1257 intent.toUri(0)).commit(); 1258 cn = intent.getComponent(); 1259 } else { 1260 c.markDeleted("Unable to find a launch target"); 1261 continue; 1262 } 1263 } else { 1264 // The app is installed but the component is no 1265 // longer available. 1266 c.markDeleted("Invalid component removed: " + cn); 1267 continue; 1268 } 1269 } 1270 } 1271 // else if cn == null => can't infer much, leave it 1272 // else if !validPkg => could be restored icon or missing sd-card 1273 1274 if (!TextUtils.isEmpty(targetPkg) && !validTarget) { 1275 // Points to a valid app (superset of cn != null) but the apk 1276 // is not available. 1277 1278 if (c.restoreFlag != 0) { 1279 // Package is not yet available but might be 1280 // installed later. 1281 FileLog.d(TAG, "package not yet restored: " + targetPkg); 1282 1283 if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) { 1284 // Restore has started once. 1285 } else if (installingPkgs.containsKey(targetPkg)) { 1286 // App restore has started. Update the flag 1287 c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED; 1288 c.updater().commit(); 1289 } else { 1290 c.markDeleted("Unrestored app removed: " + targetPkg); 1291 continue; 1292 } 1293 } else if (PackageManagerHelper.isAppOnSdcard( 1294 manager, targetPkg)) { 1295 // Package is present but not available. 1296 disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; 1297 // Add the icon on the workspace anyway. 1298 allowMissingTarget = true; 1299 } else if (!isSdCardReady) { 1300 // SdCard is not ready yet. Package might get available, 1301 // once it is ready. 1302 Log.d(TAG, "Missing pkg, will check later: " + targetPkg); 1303 pendingPackages.addToList(c.user, targetPkg); 1304 // Add the icon on the workspace anyway. 1305 allowMissingTarget = true; 1306 } else { 1307 // Do not wait for external media load anymore. 1308 c.markDeleted("Invalid package removed: " + targetPkg); 1309 continue; 1310 } 1311 } 1312 1313 if (validTarget) { 1314 // The shortcut points to a valid target (either no target 1315 // or something which is ready to be used) 1316 c.markRestored(); 1317 } 1318 1319 boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && 1320 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; 1321 1322 if (c.restoreFlag != 0) { 1323 // Already verified above that user is same as default user 1324 info = c.getRestoredItemInfo(intent); 1325 } else if (c.itemType == 1326 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1327 info = c.getAppShortcutInfo( 1328 intent, allowMissingTarget, useLowResIcon); 1329 } else if (c.itemType == 1330 LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 1331 1332 ShortcutKey key = ShortcutKey.fromIntent(intent, c.user); 1333 if (unlockedUsers.get(c.serialNumber)) { 1334 ShortcutInfoCompat pinnedShortcut = 1335 shortcutKeyToPinnedShortcuts.get(key); 1336 if (pinnedShortcut == null) { 1337 // The shortcut is no longer valid. 1338 c.markDeleted("Pinned shortcut not found"); 1339 continue; 1340 } 1341 info = new ShortcutInfo(pinnedShortcut, context); 1342 info.iconBitmap = LauncherIcons 1343 .createShortcutIcon(pinnedShortcut, context); 1344 intent = info.intent; 1345 } else { 1346 // Create a shortcut info in disabled mode for now. 1347 info = c.loadSimpleShortcut(); 1348 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; 1349 } 1350 } else { // item type == ITEM_TYPE_SHORTCUT 1351 info = c.loadSimpleShortcut(); 1352 1353 // Shortcuts are only available on the primary profile 1354 if (PackageManagerHelper.isAppSuspended(manager, targetPkg)) { 1355 disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; 1356 } 1357 1358 // App shortcuts that used to be automatically added to Launcher 1359 // didn't always have the correct intent flags set, so do that 1360 // here 1361 if (intent.getAction() != null && 1362 intent.getCategories() != null && 1363 intent.getAction().equals(Intent.ACTION_MAIN) && 1364 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 1365 intent.addFlags( 1366 Intent.FLAG_ACTIVITY_NEW_TASK | 1367 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1368 } 1369 } 1370 1371 if (info != null) { 1372 c.applyCommonProperties(info); 1373 1374 info.intent = intent; 1375 info.rank = c.getInt(rankIndex); 1376 info.spanX = 1; 1377 info.spanY = 1; 1378 // TODO: Remove this extra. Instead we should be using 1379 // itemInfo#user. 1380 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, c.serialNumber); 1381 info.isDisabled |= disabledState; 1382 if (isSafeMode && !Utilities.isSystemApp(context, intent)) { 1383 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; 1384 } 1385 1386 if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) { 1387 Integer progress = installingPkgs.get(targetPkg); 1388 if (progress != null) { 1389 info.setInstallProgress(progress); 1390 } else { 1391 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; 1392 } 1393 } 1394 1395 c.checkAndAddItem(info, sBgDataModel); 1396 } else { 1397 throw new RuntimeException("Unexpected null ShortcutInfo"); 1398 } 1399 break; 1400 1401 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 1402 FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id); 1403 c.applyCommonProperties(folderInfo); 1404 1405 // Do not trim the folder label, as is was set by the user. 1406 folderInfo.title = c.getString(c.titleIndex); 1407 folderInfo.spanX = 1; 1408 folderInfo.spanY = 1; 1409 folderInfo.options = c.getInt(optionsIndex); 1410 1411 // no special handling required for restored folders 1412 c.markRestored(); 1413 1414 c.checkAndAddItem(folderInfo, sBgDataModel); 1415 break; 1416 1417 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1418 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 1419 // Read all Launcher-specific widget details 1420 boolean customWidget = c.itemType == 1421 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 1422 1423 int appWidgetId = c.getInt(appWidgetIdIndex); 1424 String savedProvider = c.getString(appWidgetProviderIndex); 1425 1426 final ComponentName component = 1427 ComponentName.unflattenFromString(savedProvider); 1428 1429 final boolean isIdValid = !c.hasRestoreFlag( 1430 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 1431 final boolean wasProviderReady = !c.hasRestoreFlag( 1432 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY); 1433 1434 if (widgetProvidersMap == null) { 1435 widgetProvidersMap = AppWidgetManagerCompat 1436 .getInstance(mContext).getAllProvidersMap(); 1437 } 1438 final AppWidgetProviderInfo provider = widgetProvidersMap.get( 1439 new ComponentKey( 1440 ComponentName.unflattenFromString(savedProvider), 1441 c.user)); 1442 1443 final boolean isProviderReady = isValidProvider(provider); 1444 if (!isSafeMode && !customWidget && 1445 wasProviderReady && !isProviderReady) { 1446 c.markDeleted( 1447 "Deleting widget that isn't installed anymore: " 1448 + provider); 1449 } else { 1450 if (isProviderReady) { 1451 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 1452 provider.provider); 1453 1454 // The provider is available. So the widget is either 1455 // available or not available. We do not need to track 1456 // any future restore updates. 1457 int status = c.restoreFlag & 1458 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 1459 if (!wasProviderReady) { 1460 // If provider was not previously ready, update the 1461 // status and UI flag. 1462 1463 // Id would be valid only if the widget restore broadcast was received. 1464 if (isIdValid) { 1465 status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 1466 } else { 1467 status &= ~LauncherAppWidgetInfo 1468 .FLAG_PROVIDER_NOT_READY; 1469 } 1470 } 1471 appWidgetInfo.restoreStatus = status; 1472 } else { 1473 Log.v(TAG, "Widget restore pending id=" + c.id 1474 + " appWidgetId=" + appWidgetId 1475 + " status =" + c.restoreFlag); 1476 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 1477 component); 1478 appWidgetInfo.restoreStatus = c.restoreFlag; 1479 Integer installProgress = installingPkgs.get(component.getPackageName()); 1480 1481 if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) { 1482 // Restore has started once. 1483 } else if (installProgress != null) { 1484 // App restore has started. Update the flag 1485 appWidgetInfo.restoreStatus |= 1486 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 1487 } else if (!isSafeMode) { 1488 c.markDeleted("Unrestored widget removed: " + component); 1489 continue; 1490 } 1491 1492 appWidgetInfo.installProgress = 1493 installProgress == null ? 0 : installProgress; 1494 } 1495 if (appWidgetInfo.hasRestoreFlag( 1496 LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { 1497 appWidgetInfo.bindOptions = c.parseIntent(); 1498 } 1499 1500 c.applyCommonProperties(appWidgetInfo); 1501 appWidgetInfo.spanX = c.getInt(spanXIndex); 1502 appWidgetInfo.spanY = c.getInt(spanYIndex); 1503 appWidgetInfo.user = c.user; 1504 1505 if (!c.isOnWorkspaceOrHotseat()) { 1506 c.markDeleted("Widget found where container != " + 1507 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 1508 continue; 1509 } 1510 1511 if (!customWidget) { 1512 String providerName = 1513 appWidgetInfo.providerName.flattenToString(); 1514 if (!providerName.equals(savedProvider) || 1515 (appWidgetInfo.restoreStatus != c.restoreFlag)) { 1516 c.updater() 1517 .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, 1518 providerName) 1519 .put(LauncherSettings.Favorites.RESTORED, 1520 appWidgetInfo.restoreStatus) 1521 .commit(); 1522 } 1523 } 1524 c.checkAndAddItem(appWidgetInfo, sBgDataModel); 1525 } 1526 break; 1527 } 1528 } catch (Exception e) { 1529 Log.e(TAG, "Desktop items loading interrupted", e); 1530 } 1531 } 1532 } finally { 1533 Utilities.closeSilently(c); 1534 } 1535 1536 // Break early if we've stopped loading 1537 if (mStopped) { 1538 sBgDataModel.clear(); 1539 return; 1540 } 1541 1542 // Remove dead items 1543 if (c.commitDeleted()) { 1544 // Remove any empty folder 1545 ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings 1546 .call(contentResolver, 1547 LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) 1548 .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); 1549 for (long folderId : deletedFolderIds) { 1550 sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId)); 1551 sBgDataModel.folders.remove(folderId); 1552 sBgDataModel.itemsIdMap.remove(folderId); 1553 } 1554 } 1555 1556 // Unpin shortcuts that don't exist on the workspace. 1557 HashSet<ShortcutKey> pendingShortcuts = 1558 InstallShortcutReceiver.getPendingShortcuts(context); 1559 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { 1560 MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key); 1561 if ((numTimesPinned == null || numTimesPinned.value == 0) 1562 && !pendingShortcuts.contains(key)) { 1563 // Shortcut is pinned but doesn't exist on the workspace; unpin it. 1564 shortcutManager.unpinShortcut(key); 1565 } 1566 } 1567 1568 // Sort all the folder items and make sure the first 3 items are high resolution. 1569 for (FolderInfo folder : sBgDataModel.folders) { 1570 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); 1571 int pos = 0; 1572 for (ShortcutInfo info : folder.contents) { 1573 if (info.usingLowResIcon && 1574 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1575 mIconCache.getTitleAndIcon(info, false); 1576 } 1577 pos ++; 1578 if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { 1579 break; 1580 } 1581 } 1582 } 1583 1584 c.commitRestoredItems(); 1585 if (!isSdCardReady && !pendingPackages.isEmpty()) { 1586 context.registerReceiver( 1587 new SdCardAvailableReceiver( 1588 LauncherModel.this, mContext, pendingPackages), 1589 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), 1590 null, 1591 sWorker); 1592 } 1593 1594 // Remove any empty screens 1595 ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens); 1596 for (ItemInfo item: sBgDataModel.itemsIdMap) { 1597 long screenId = item.screenId; 1598 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1599 unusedScreens.contains(screenId)) { 1600 unusedScreens.remove(screenId); 1601 } 1602 } 1603 1604 // If there are any empty screens remove them, and update. 1605 if (unusedScreens.size() != 0) { 1606 sBgDataModel.workspaceScreens.removeAll(unusedScreens); 1607 updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens); 1608 } 1609 } 1610 if (LauncherAppState.PROFILE_STARTUP) { 1611 Trace.endSection(); 1612 } 1613 } 1614 1615 /** Filters the set of items who are directly or indirectly (via another container) on the 1616 * specified screen. */ 1617 private void filterCurrentWorkspaceItems(long currentScreenId, 1618 ArrayList<ItemInfo> allWorkspaceItems, 1619 ArrayList<ItemInfo> currentScreenItems, 1620 ArrayList<ItemInfo> otherScreenItems) { 1621 // Purge any null ItemInfos 1622 Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); 1623 while (iter.hasNext()) { 1624 ItemInfo i = iter.next(); 1625 if (i == null) { 1626 iter.remove(); 1627 } 1628 } 1629 1630 // Order the set of items by their containers first, this allows use to walk through the 1631 // list sequentially, build up a list of containers that are in the specified screen, 1632 // as well as all items in those containers. 1633 Set<Long> itemsOnScreen = new HashSet<Long>(); 1634 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { 1635 @Override 1636 public int compare(ItemInfo lhs, ItemInfo rhs) { 1637 return Utilities.longCompare(lhs.container, rhs.container); 1638 } 1639 }); 1640 for (ItemInfo info : allWorkspaceItems) { 1641 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1642 if (info.screenId == currentScreenId) { 1643 currentScreenItems.add(info); 1644 itemsOnScreen.add(info.id); 1645 } else { 1646 otherScreenItems.add(info); 1647 } 1648 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1649 currentScreenItems.add(info); 1650 itemsOnScreen.add(info.id); 1651 } else { 1652 if (itemsOnScreen.contains(info.container)) { 1653 currentScreenItems.add(info); 1654 itemsOnScreen.add(info.id); 1655 } else { 1656 otherScreenItems.add(info); 1657 } 1658 } 1659 } 1660 } 1661 1662 /** Filters the set of widgets which are on the specified screen. */ 1663 private void filterCurrentAppWidgets(long currentScreenId, 1664 ArrayList<LauncherAppWidgetInfo> appWidgets, 1665 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, 1666 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { 1667 1668 for (LauncherAppWidgetInfo widget : appWidgets) { 1669 if (widget == null) continue; 1670 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1671 widget.screenId == currentScreenId) { 1672 currentScreenWidgets.add(widget); 1673 } else { 1674 otherScreenWidgets.add(widget); 1675 } 1676 } 1677 } 1678 1679 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 1680 * right) */ 1681 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 1682 final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); 1683 final int screenCols = profile.numColumns; 1684 final int screenCellCount = profile.numColumns * profile.numRows; 1685 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 1686 @Override 1687 public int compare(ItemInfo lhs, ItemInfo rhs) { 1688 if (lhs.container == rhs.container) { 1689 // Within containers, order by their spatial position in that container 1690 switch ((int) lhs.container) { 1691 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 1692 long lr = (lhs.screenId * screenCellCount + 1693 lhs.cellY * screenCols + lhs.cellX); 1694 long rr = (rhs.screenId * screenCellCount + 1695 rhs.cellY * screenCols + rhs.cellX); 1696 return Utilities.longCompare(lr, rr); 1697 } 1698 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 1699 // We currently use the screen id as the rank 1700 return Utilities.longCompare(lhs.screenId, rhs.screenId); 1701 } 1702 default: 1703 if (ProviderConfig.IS_DOGFOOD_BUILD) { 1704 throw new RuntimeException("Unexpected container type when " + 1705 "sorting workspace items."); 1706 } 1707 return 0; 1708 } 1709 } else { 1710 // Between containers, order by hotseat, desktop 1711 return Utilities.longCompare(lhs.container, rhs.container); 1712 } 1713 } 1714 }); 1715 } 1716 1717 private void bindWorkspaceScreens(final Callbacks oldCallbacks, 1718 final ArrayList<Long> orderedScreens) { 1719 final Runnable r = new Runnable() { 1720 @Override 1721 public void run() { 1722 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1723 if (callbacks != null) { 1724 callbacks.bindScreens(orderedScreens); 1725 } 1726 } 1727 }; 1728 runOnMainThread(r); 1729 } 1730 1731 private void bindWorkspaceItems(final Callbacks oldCallbacks, 1732 final ArrayList<ItemInfo> workspaceItems, 1733 final ArrayList<LauncherAppWidgetInfo> appWidgets, 1734 final Executor executor) { 1735 1736 // Bind the workspace items 1737 int N = workspaceItems.size(); 1738 for (int i = 0; i < N; i += ITEMS_CHUNK) { 1739 final int start = i; 1740 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 1741 final Runnable r = new Runnable() { 1742 @Override 1743 public void run() { 1744 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1745 if (callbacks != null) { 1746 callbacks.bindItems(workspaceItems, start, start+chunkSize, 1747 false); 1748 } 1749 } 1750 }; 1751 executor.execute(r); 1752 } 1753 1754 // Bind the widgets, one at a time 1755 N = appWidgets.size(); 1756 for (int i = 0; i < N; i++) { 1757 final LauncherAppWidgetInfo widget = appWidgets.get(i); 1758 final Runnable r = new Runnable() { 1759 public void run() { 1760 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1761 if (callbacks != null) { 1762 callbacks.bindAppWidget(widget); 1763 } 1764 } 1765 }; 1766 executor.execute(r); 1767 } 1768 } 1769 1770 /** 1771 * Binds all loaded data to actual views on the main thread. 1772 */ 1773 private void bindWorkspace(int synchronizeBindPage) { 1774 final long t = SystemClock.uptimeMillis(); 1775 Runnable r; 1776 1777 // Don't use these two variables in any of the callback runnables. 1778 // Otherwise we hold a reference to them. 1779 final Callbacks oldCallbacks = mCallbacks.get(); 1780 if (oldCallbacks == null) { 1781 // This launcher has exited and nobody bothered to tell us. Just bail. 1782 Log.w(TAG, "LoaderTask running with no launcher"); 1783 return; 1784 } 1785 1786 // Save a copy of all the bg-thread collections 1787 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 1788 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 1789 ArrayList<Long> orderedScreenIds = new ArrayList<>(); 1790 1791 synchronized (sBgDataModel) { 1792 workspaceItems.addAll(sBgDataModel.workspaceItems); 1793 appWidgets.addAll(sBgDataModel.appWidgets); 1794 orderedScreenIds.addAll(sBgDataModel.workspaceScreens); 1795 } 1796 1797 final int currentScreen; 1798 { 1799 int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE 1800 ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen(); 1801 if (currScreen >= orderedScreenIds.size()) { 1802 // There may be no workspace screens (just hotseat items and an empty page). 1803 currScreen = PagedView.INVALID_RESTORE_PAGE; 1804 } 1805 currentScreen = currScreen; 1806 } 1807 final boolean validFirstPage = currentScreen >= 0; 1808 final long currentScreenId = 1809 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 1810 1811 // Separate the items that are on the current screen, and all the other remaining items 1812 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 1813 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 1814 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 1815 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 1816 1817 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, 1818 otherWorkspaceItems); 1819 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, 1820 otherAppWidgets); 1821 sortWorkspaceItemsSpatially(currentWorkspaceItems); 1822 sortWorkspaceItemsSpatially(otherWorkspaceItems); 1823 1824 // Tell the workspace that we're about to start binding items 1825 r = new Runnable() { 1826 public void run() { 1827 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1828 if (callbacks != null) { 1829 callbacks.clearPendingBinds(); 1830 callbacks.startBinding(); 1831 } 1832 } 1833 }; 1834 runOnMainThread(r); 1835 1836 bindWorkspaceScreens(oldCallbacks, orderedScreenIds); 1837 1838 Executor mainExecutor = new DeferredMainThreadExecutor(); 1839 // Load items on the current page. 1840 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor); 1841 1842 // In case of validFirstPage, only bind the first screen, and defer binding the 1843 // remaining screens after first onDraw (and an optional the fade animation whichever 1844 // happens later). 1845 // This ensures that the first screen is immediately visible (eg. during rotation) 1846 // In case of !validFirstPage, bind all pages one after other. 1847 final Executor deferredExecutor = 1848 validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor; 1849 1850 mainExecutor.execute(new Runnable() { 1851 @Override 1852 public void run() { 1853 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1854 if (callbacks != null) { 1855 callbacks.finishFirstPageBind( 1856 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); 1857 } 1858 } 1859 }); 1860 1861 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor); 1862 1863 // Tell the workspace that we're done binding items 1864 r = new Runnable() { 1865 public void run() { 1866 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1867 if (callbacks != null) { 1868 callbacks.finishBindingItems(); 1869 } 1870 1871 mIsLoadingAndBindingWorkspace = false; 1872 1873 // Run all the bind complete runnables after workspace is bound. 1874 if (!mBindCompleteRunnables.isEmpty()) { 1875 synchronized (mBindCompleteRunnables) { 1876 for (final Runnable r : mBindCompleteRunnables) { 1877 runOnWorkerThread(r); 1878 } 1879 mBindCompleteRunnables.clear(); 1880 } 1881 } 1882 1883 // If we're profiling, ensure this is the last thing in the queue. 1884 if (DEBUG_LOADERS) { 1885 Log.d(TAG, "bound workspace in " 1886 + (SystemClock.uptimeMillis()-t) + "ms"); 1887 } 1888 1889 } 1890 }; 1891 deferredExecutor.execute(r); 1892 1893 if (validFirstPage) { 1894 r = new Runnable() { 1895 public void run() { 1896 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1897 if (callbacks != null) { 1898 // We are loading synchronously, which means, some of the pages will be 1899 // bound after first draw. Inform the callbacks that page binding is 1900 // not complete, and schedule the remaining pages. 1901 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { 1902 callbacks.onPageBoundSynchronously(currentScreen); 1903 } 1904 callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 1905 } 1906 } 1907 }; 1908 runOnMainThread(r); 1909 } 1910 } 1911 1912 private void loadAndBindAllApps() { 1913 if (DEBUG_LOADERS) { 1914 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); 1915 } 1916 if (!mAllAppsLoaded) { 1917 loadAllApps(); 1918 synchronized (LoaderTask.this) { 1919 if (mStopped) { 1920 return; 1921 } 1922 } 1923 updateIconCache(); 1924 synchronized (LoaderTask.this) { 1925 if (mStopped) { 1926 return; 1927 } 1928 mAllAppsLoaded = true; 1929 } 1930 } else { 1931 onlyBindAllApps(); 1932 } 1933 } 1934 1935 private void updateIconCache() { 1936 // Ignore packages which have a promise icon. 1937 HashSet<String> packagesToIgnore = new HashSet<>(); 1938 synchronized (sBgDataModel) { 1939 for (ItemInfo info : sBgDataModel.itemsIdMap) { 1940 if (info instanceof ShortcutInfo) { 1941 ShortcutInfo si = (ShortcutInfo) info; 1942 if (si.isPromise() && si.getTargetComponent() != null) { 1943 packagesToIgnore.add(si.getTargetComponent().getPackageName()); 1944 } 1945 } else if (info instanceof LauncherAppWidgetInfo) { 1946 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; 1947 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 1948 packagesToIgnore.add(lawi.providerName.getPackageName()); 1949 } 1950 } 1951 } 1952 } 1953 mIconCache.updateDbIcons(packagesToIgnore); 1954 } 1955 1956 private void onlyBindAllApps() { 1957 final Callbacks oldCallbacks = mCallbacks.get(); 1958 if (oldCallbacks == null) { 1959 // This launcher has exited and nobody bothered to tell us. Just bail. 1960 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 1961 return; 1962 } 1963 1964 // shallow copy 1965 @SuppressWarnings("unchecked") 1966 final ArrayList<AppInfo> list 1967 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); 1968 Runnable r = new Runnable() { 1969 public void run() { 1970 final long t = SystemClock.uptimeMillis(); 1971 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1972 if (callbacks != null) { 1973 callbacks.bindAllApplications(list); 1974 } 1975 if (DEBUG_LOADERS) { 1976 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 1977 + (SystemClock.uptimeMillis() - t) + "ms"); 1978 } 1979 } 1980 }; 1981 runOnMainThread(r); 1982 } 1983 1984 private void loadAllApps() { 1985 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1986 1987 final Callbacks oldCallbacks = mCallbacks.get(); 1988 if (oldCallbacks == null) { 1989 // This launcher has exited and nobody bothered to tell us. Just bail. 1990 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); 1991 return; 1992 } 1993 1994 final List<UserHandle> profiles = mUserManager.getUserProfiles(); 1995 1996 // Clear the list of apps 1997 mBgAllAppsList.clear(); 1998 for (UserHandle user : profiles) { 1999 // Query for the set of apps 2000 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 2001 final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user); 2002 if (DEBUG_LOADERS) { 2003 Log.d(TAG, "getActivityList took " 2004 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); 2005 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); 2006 } 2007 // Fail if we don't have any apps 2008 // TODO: Fix this. Only fail for the current user. 2009 if (apps == null || apps.isEmpty()) { 2010 return; 2011 } 2012 boolean quietMode = mUserManager.isQuietModeEnabled(user); 2013 // Create the ApplicationInfos 2014 for (int i = 0; i < apps.size(); i++) { 2015 LauncherActivityInfo app = apps.get(i); 2016 // This builds the icon bitmaps. 2017 mBgAllAppsList.add(new AppInfo(mContext, app, user, quietMode), app); 2018 } 2019 2020 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); 2021 if (heuristic != null) { 2022 final Runnable r = new Runnable() { 2023 2024 @Override 2025 public void run() { 2026 heuristic.processUserApps(apps); 2027 } 2028 }; 2029 runOnMainThread(new Runnable() { 2030 2031 @Override 2032 public void run() { 2033 // Check isLoadingWorkspace on the UI thread, as it is updated on 2034 // the UI thread. 2035 if (mIsLoadingAndBindingWorkspace) { 2036 synchronized (mBindCompleteRunnables) { 2037 mBindCompleteRunnables.add(r); 2038 } 2039 } else { 2040 runOnWorkerThread(r); 2041 } 2042 } 2043 }); 2044 } 2045 } 2046 // Huh? Shouldn't this be inside the Runnable below? 2047 final ArrayList<AppInfo> added = mBgAllAppsList.added; 2048 mBgAllAppsList.added = new ArrayList<AppInfo>(); 2049 2050 // Post callback on main thread 2051 mHandler.post(new Runnable() { 2052 public void run() { 2053 2054 final long bindTime = SystemClock.uptimeMillis(); 2055 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2056 if (callbacks != null) { 2057 callbacks.bindAllApplications(added); 2058 if (DEBUG_LOADERS) { 2059 Log.d(TAG, "bound " + added.size() + " apps in " 2060 + (SystemClock.uptimeMillis() - bindTime) + "ms"); 2061 } 2062 } else { 2063 Log.i(TAG, "not binding apps: no Launcher activity"); 2064 } 2065 } 2066 }); 2067 // Cleanup any data stored for a deleted user. 2068 ManagedProfileHeuristic.processAllUsers(profiles, mContext); 2069 if (DEBUG_LOADERS) { 2070 Log.d(TAG, "Icons processed in " 2071 + (SystemClock.uptimeMillis() - loadTime) + "ms"); 2072 } 2073 } 2074 2075 private void loadAndBindDeepShortcuts() { 2076 if (DEBUG_LOADERS) { 2077 Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded); 2078 } 2079 if (!mDeepShortcutsLoaded) { 2080 sBgDataModel.deepShortcutMap.clear(); 2081 DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext); 2082 mHasShortcutHostPermission = shortcutManager.hasHostPermission(); 2083 if (mHasShortcutHostPermission) { 2084 for (UserHandle user : mUserManager.getUserProfiles()) { 2085 if (mUserManager.isUserUnlocked(user)) { 2086 List<ShortcutInfoCompat> shortcuts = 2087 shortcutManager.queryForAllShortcuts(user); 2088 sBgDataModel.updateDeepShortcutMap(null, user, shortcuts); 2089 } 2090 } 2091 } 2092 synchronized (LoaderTask.this) { 2093 if (mStopped) { 2094 return; 2095 } 2096 mDeepShortcutsLoaded = true; 2097 } 2098 } 2099 bindDeepShortcuts(); 2100 } 2101 2102 public void dumpState() { 2103 synchronized (sBgDataModel) { 2104 Log.d(TAG, "mLoaderTask.mContext=" + mContext); 2105 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); 2106 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); 2107 Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size()); 2108 } 2109 } 2110 } 2111 2112 public void bindDeepShortcuts() { 2113 final MultiHashMap<ComponentKey, String> shortcutMapCopy = 2114 sBgDataModel.deepShortcutMap.clone(); 2115 Runnable r = new Runnable() { 2116 @Override 2117 public void run() { 2118 Callbacks callbacks = getCallback(); 2119 if (callbacks != null) { 2120 callbacks.bindDeepShortcutMap(shortcutMapCopy); 2121 } 2122 } 2123 }; 2124 runOnMainThread(r); 2125 } 2126 2127 /** 2128 * Refreshes the cached shortcuts if the shortcut permission has changed. 2129 * Current implementation simply reloads the workspace, but it can be optimized to 2130 * use partial updates similar to {@link UserManagerCompat} 2131 */ 2132 public void refreshShortcutsIfRequired() { 2133 if (Utilities.ATLEAST_NOUGAT_MR1) { 2134 sWorker.removeCallbacks(mShortcutPermissionCheckRunnable); 2135 sWorker.post(mShortcutPermissionCheckRunnable); 2136 } 2137 } 2138 2139 /** 2140 * Called when the icons for packages have been updated in the icon cache. 2141 */ 2142 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) { 2143 // If any package icon has changed (app was updated while launcher was dead), 2144 // update the corresponding shortcuts. 2145 enqueueModelUpdateTask(new CacheDataUpdatedTask( 2146 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); 2147 } 2148 2149 void enqueueModelUpdateTask(BaseModelUpdateTask task) { 2150 task.init(this); 2151 runOnWorkerThread(task); 2152 } 2153 2154 /** 2155 * A task to be executed on the current callbacks on the UI thread. 2156 * If there is no current callbacks, the task is ignored. 2157 */ 2158 public interface CallbackTask { 2159 2160 void execute(Callbacks callbacks); 2161 } 2162 2163 /** 2164 * A runnable which changes/updates the data model of the launcher based on certain events. 2165 */ 2166 public static abstract class BaseModelUpdateTask implements Runnable { 2167 2168 private LauncherModel mModel; 2169 private DeferredHandler mUiHandler; 2170 2171 /* package private */ 2172 void init(LauncherModel model) { 2173 mModel = model; 2174 mUiHandler = mModel.mHandler; 2175 } 2176 2177 @Override 2178 public void run() { 2179 if (!mModel.mHasLoaderCompletedOnce) { 2180 // Loader has not yet run. 2181 return; 2182 } 2183 execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList); 2184 } 2185 2186 /** 2187 * Execute the actual task. Called on the worker thread. 2188 */ 2189 public abstract void execute( 2190 LauncherAppState app, BgDataModel dataModel, AllAppsList apps); 2191 2192 /** 2193 * Schedules a {@param task} to be executed on the current callbacks. 2194 */ 2195 public final void scheduleCallbackTask(final CallbackTask task) { 2196 final Callbacks callbacks = mModel.getCallback(); 2197 mUiHandler.post(new Runnable() { 2198 public void run() { 2199 Callbacks cb = mModel.getCallback(); 2200 if (callbacks == cb && cb != null) { 2201 task.execute(callbacks); 2202 } 2203 } 2204 }); 2205 } 2206 } 2207 2208 public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) { 2209 updateAndBindShortcutInfo(new Provider<ShortcutInfo>() { 2210 @Override 2211 public ShortcutInfo get() { 2212 si.updateFromDeepShortcutInfo(info, mApp.getContext()); 2213 si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext()); 2214 return si; 2215 } 2216 }); 2217 } 2218 2219 /** 2220 * Utility method to update a shortcut on the background thread. 2221 */ 2222 public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) { 2223 enqueueModelUpdateTask(new ExtendedModelTask() { 2224 @Override 2225 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 2226 ShortcutInfo info = shortcutProvider.get(); 2227 ArrayList<ShortcutInfo> update = new ArrayList<>(); 2228 update.add(info); 2229 bindUpdatedShortcuts(update, info.user); 2230 } 2231 }); 2232 } 2233 2234 private void bindWidgetsModel(final Callbacks callbacks) { 2235 final MultiHashMap<PackageItemInfo, WidgetItem> widgets 2236 = mBgWidgetsModel.getWidgetsMap().clone(); 2237 mHandler.post(new Runnable() { 2238 @Override 2239 public void run() { 2240 Callbacks cb = getCallback(); 2241 if (callbacks == cb && cb != null) { 2242 callbacks.bindAllWidgets(widgets); 2243 } 2244 } 2245 }); 2246 } 2247 2248 public void refreshAndBindWidgetsAndShortcuts( 2249 final Callbacks callbacks, final boolean bindFirst) { 2250 runOnWorkerThread(new Runnable() { 2251 @Override 2252 public void run() { 2253 if (bindFirst && !mBgWidgetsModel.isEmpty()) { 2254 bindWidgetsModel(callbacks); 2255 } 2256 ArrayList<WidgetItem> allWidgets = mBgWidgetsModel.update(mApp.getContext()); 2257 bindWidgetsModel(callbacks); 2258 2259 // update the Widget entries inside DB on the worker thread. 2260 mApp.getWidgetCache().removeObsoletePreviews(allWidgets); 2261 } 2262 }); 2263 } 2264 2265 static boolean isValidProvider(AppWidgetProviderInfo provider) { 2266 return (provider != null) && (provider.provider != null) 2267 && (provider.provider.getPackageName() != null); 2268 } 2269 2270 public void dumpState() { 2271 Log.d(TAG, "mCallbacks=" + mCallbacks); 2272 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data); 2273 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added); 2274 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed); 2275 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified); 2276 if (mLoaderTask != null) { 2277 mLoaderTask.dumpState(); 2278 } else { 2279 Log.d(TAG, "mLoaderTask=null"); 2280 } 2281 } 2282 2283 public Callbacks getCallback() { 2284 return mCallbacks != null ? mCallbacks.get() : null; 2285 } 2286 2287 /** 2288 * @return {@link FolderInfo} if its already loaded. 2289 */ 2290 public FolderInfo findFolderById(Long folderId) { 2291 synchronized (sBgDataModel) { 2292 return sBgDataModel.folders.get(folderId); 2293 } 2294 } 2295 2296 @Thunk class DeferredMainThreadExecutor implements Executor { 2297 2298 @Override 2299 public void execute(Runnable command) { 2300 runOnMainThread(command); 2301 } 2302 } 2303 2304 /** 2305 * @return the looper for the worker thread which can be used to start background tasks. 2306 */ 2307 public static Looper getWorkerLooper() { 2308 return sWorkerThread.getLooper(); 2309 } 2310} 2311