1 /* 2 * Copyright (C) 2017 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.model; 18 19 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; 20 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; 21 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; 22 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; 23 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; 24 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; 25 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; 26 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; 27 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 28 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; 29 import static com.android.launcher3.util.PackageManagerHelper.isSystemApp; 30 31 import android.annotation.SuppressLint; 32 import android.appwidget.AppWidgetProviderInfo; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.content.pm.LauncherActivityInfo; 38 import android.content.pm.LauncherApps; 39 import android.content.pm.PackageInstaller; 40 import android.content.pm.PackageInstaller.SessionInfo; 41 import android.content.pm.PackageManager; 42 import android.content.pm.ShortcutInfo; 43 import android.graphics.Point; 44 import android.os.Bundle; 45 import android.os.Trace; 46 import android.os.UserHandle; 47 import android.os.UserManager; 48 import android.text.TextUtils; 49 import android.util.ArrayMap; 50 import android.util.Log; 51 import android.util.LongSparseArray; 52 53 import androidx.annotation.NonNull; 54 import androidx.annotation.Nullable; 55 56 import com.android.launcher3.DeviceProfile; 57 import com.android.launcher3.InvariantDeviceProfile; 58 import com.android.launcher3.LauncherAppState; 59 import com.android.launcher3.LauncherModel; 60 import com.android.launcher3.LauncherSettings.Favorites; 61 import com.android.launcher3.Utilities; 62 import com.android.launcher3.config.FeatureFlags; 63 import com.android.launcher3.folder.Folder; 64 import com.android.launcher3.folder.FolderGridOrganizer; 65 import com.android.launcher3.folder.FolderNameInfos; 66 import com.android.launcher3.folder.FolderNameProvider; 67 import com.android.launcher3.icons.ComponentWithLabelAndIcon; 68 import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic; 69 import com.android.launcher3.icons.IconCache; 70 import com.android.launcher3.icons.LauncherActivityCachingLogic; 71 import com.android.launcher3.icons.ShortcutCachingLogic; 72 import com.android.launcher3.icons.cache.IconCacheUpdateHandler; 73 import com.android.launcher3.logging.FileLog; 74 import com.android.launcher3.model.data.AppInfo; 75 import com.android.launcher3.model.data.FolderInfo; 76 import com.android.launcher3.model.data.IconRequestInfo; 77 import com.android.launcher3.model.data.ItemInfo; 78 import com.android.launcher3.model.data.ItemInfoWithIcon; 79 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 80 import com.android.launcher3.model.data.WorkspaceItemInfo; 81 import com.android.launcher3.pm.InstallSessionHelper; 82 import com.android.launcher3.pm.PackageInstallInfo; 83 import com.android.launcher3.pm.UserCache; 84 import com.android.launcher3.qsb.QsbContainerView; 85 import com.android.launcher3.shortcuts.ShortcutKey; 86 import com.android.launcher3.shortcuts.ShortcutRequest; 87 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult; 88 import com.android.launcher3.util.ComponentKey; 89 import com.android.launcher3.util.IOUtils; 90 import com.android.launcher3.util.IntArray; 91 import com.android.launcher3.util.IntSet; 92 import com.android.launcher3.util.LooperIdleLock; 93 import com.android.launcher3.util.PackageManagerHelper; 94 import com.android.launcher3.util.PackageUserKey; 95 import com.android.launcher3.util.TraceHelper; 96 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; 97 import com.android.launcher3.widget.WidgetManagerHelper; 98 99 import java.util.ArrayList; 100 import java.util.Collections; 101 import java.util.HashMap; 102 import java.util.HashSet; 103 import java.util.List; 104 import java.util.Map; 105 import java.util.Set; 106 import java.util.concurrent.CancellationException; 107 108 /** 109 * Runnable for the thread that loads the contents of the launcher: 110 * - workspace icons 111 * - widgets 112 * - all apps icons 113 * - deep shortcuts within apps 114 */ 115 public class LoaderTask implements Runnable { 116 private static final String TAG = "LoaderTask"; 117 118 private static final boolean DEBUG = true; 119 120 @NonNull 121 protected final LauncherAppState mApp; 122 private final AllAppsList mBgAllAppsList; 123 protected final BgDataModel mBgDataModel; 124 private final ModelDelegate mModelDelegate; 125 126 private FirstScreenBroadcast mFirstScreenBroadcast; 127 128 @NonNull 129 private final LauncherBinder mLauncherBinder; 130 131 private final LauncherApps mLauncherApps; 132 private final UserManager mUserManager; 133 private final UserCache mUserCache; 134 135 private final InstallSessionHelper mSessionHelper; 136 private final IconCache mIconCache; 137 138 private final UserManagerState mUserManagerState = new UserManagerState(); 139 140 protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>(); 141 private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts; 142 143 private boolean mStopped; 144 145 private final Set<PackageUserKey> mPendingPackages = new HashSet<>(); 146 private boolean mItemsDeleted = false; 147 private String mDbName; 148 LoaderTask(@onNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel, ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder)149 public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel, 150 ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder) { 151 mApp = app; 152 mBgAllAppsList = bgAllAppsList; 153 mBgDataModel = bgModel; 154 mModelDelegate = modelDelegate; 155 mLauncherBinder = launcherBinder; 156 157 mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class); 158 mUserManager = mApp.getContext().getSystemService(UserManager.class); 159 mUserCache = UserCache.INSTANCE.get(mApp.getContext()); 160 mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext()); 161 mIconCache = mApp.getIconCache(); 162 } 163 waitForIdle()164 protected synchronized void waitForIdle() { 165 // Wait until the either we're stopped or the other threads are done. 166 // This way we don't start loading all apps until the workspace has settled 167 // down. 168 LooperIdleLock idleLock = mLauncherBinder.newIdleLock(this); 169 // Just in case mFlushingWorkerThread changes but we aren't woken up, 170 // wait no longer than 1sec at a time 171 while (!mStopped && idleLock.awaitLocked(1000)); 172 } 173 verifyNotStopped()174 private synchronized void verifyNotStopped() throws CancellationException { 175 if (mStopped) { 176 throw new CancellationException("Loader stopped"); 177 } 178 } 179 sendFirstScreenActiveInstallsBroadcast()180 private void sendFirstScreenActiveInstallsBroadcast() { 181 ArrayList<ItemInfo> firstScreenItems = new ArrayList<>(); 182 ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems(); 183 184 // Screen set is never empty 185 IntArray allScreens = mBgDataModel.collectWorkspaceScreens(); 186 final int firstScreen = allScreens.get(0); 187 IntSet firstScreens = IntSet.wrap(firstScreen); 188 189 filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems, 190 new ArrayList<>() /* otherScreenItems are ignored */); 191 mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems); 192 } 193 run()194 public void run() { 195 synchronized (this) { 196 // Skip fast if we are already stopped. 197 if (mStopped) { 198 return; 199 } 200 } 201 202 TraceHelper.INSTANCE.beginSection(TAG); 203 LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger(); 204 try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { 205 List<ShortcutInfo> allShortcuts = new ArrayList<>(); 206 loadWorkspace(allShortcuts, "", memoryLogger); 207 208 // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db. 209 // sanitizeData should not be invoked if the workspace is loaded from a db different 210 // from the main db as defined in the invariant device profile. 211 // (e.g. both grid preview and minimal device mode uses a different db) 212 if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) { 213 verifyNotStopped(); 214 sanitizeFolders(mItemsDeleted); 215 sanitizeWidgetsShortcutsAndPackages(); 216 logASplit("sanitizeData"); 217 } 218 219 verifyNotStopped(); 220 mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false); 221 logASplit("bindWorkspace"); 222 223 mModelDelegate.workspaceLoadComplete(); 224 // Notify the installer packages of packages with active installs on the first screen. 225 sendFirstScreenActiveInstallsBroadcast(); 226 logASplit("sendFirstScreenActiveInstallsBroadcast"); 227 228 // Take a break 229 waitForIdle(); 230 logASplit("step 1 complete"); 231 verifyNotStopped(); 232 233 // second step 234 Trace.beginSection("LoadAllApps"); 235 List<LauncherActivityInfo> allActivityList; 236 try { 237 allActivityList = loadAllApps(); 238 } finally { 239 Trace.endSection(); 240 } 241 logASplit("loadAllApps"); 242 243 if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { 244 mModelDelegate.loadAndBindAllAppsItems(mUserManagerState, 245 mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts); 246 logASplit("allAppsDelegateItems"); 247 } 248 verifyNotStopped(); 249 mLauncherBinder.bindAllApps(); 250 logASplit("bindAllApps"); 251 252 verifyNotStopped(); 253 IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); 254 setIgnorePackages(updateHandler); 255 updateHandler.updateIcons(allActivityList, 256 LauncherActivityCachingLogic.newInstance(mApp.getContext()), 257 mApp.getModel()::onPackageIconsUpdated); 258 logASplit("update icon cache"); 259 260 verifyNotStopped(); 261 logASplit("save shortcuts in icon cache"); 262 updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(), 263 mApp.getModel()::onPackageIconsUpdated); 264 265 // Take a break 266 waitForIdle(); 267 logASplit("step 2 complete"); 268 verifyNotStopped(); 269 270 // third step 271 List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts(); 272 logASplit("loadDeepShortcuts"); 273 274 verifyNotStopped(); 275 mLauncherBinder.bindDeepShortcuts(); 276 logASplit("bindDeepShortcuts"); 277 278 verifyNotStopped(); 279 logASplit("save deep shortcuts in icon cache"); 280 updateHandler.updateIcons(allDeepShortcuts, 281 new ShortcutCachingLogic(), (pkgs, user) -> { }); 282 283 // Take a break 284 waitForIdle(); 285 logASplit("step 3 complete"); 286 verifyNotStopped(); 287 288 // fourth step 289 List<ComponentWithLabelAndIcon> allWidgetsList = 290 mBgDataModel.widgetsModel.update(mApp, null); 291 logASplit("load widgets"); 292 293 verifyNotStopped(); 294 mLauncherBinder.bindWidgets(); 295 logASplit("bindWidgets"); 296 verifyNotStopped(); 297 298 if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { 299 mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList); 300 logASplit("otherDelegateItems"); 301 verifyNotStopped(); 302 } 303 304 updateHandler.updateIcons(allWidgetsList, 305 new ComponentWithIconCachingLogic(mApp.getContext(), true), 306 mApp.getModel()::onWidgetLabelsUpdated); 307 logASplit("save widgets in icon cache"); 308 309 // fifth step 310 loadFolderNames(); 311 312 verifyNotStopped(); 313 updateHandler.finish(); 314 logASplit("finish icon update"); 315 316 mModelDelegate.modelLoadComplete(); 317 transaction.commit(); 318 memoryLogger.clearLogs(); 319 } catch (CancellationException e) { 320 // Loader stopped, ignore 321 logASplit("Cancelled"); 322 } catch (Exception e) { 323 memoryLogger.printLogs(); 324 throw e; 325 } 326 TraceHelper.INSTANCE.endSection(); 327 } 328 stopLocked()329 public synchronized void stopLocked() { 330 mStopped = true; 331 this.notify(); 332 } 333 loadWorkspace( List<ShortcutInfo> allDeepShortcuts, String selection, LoaderMemoryLogger memoryLogger)334 protected void loadWorkspace( 335 List<ShortcutInfo> allDeepShortcuts, 336 String selection, 337 LoaderMemoryLogger memoryLogger) { 338 Trace.beginSection("LoadWorkspace"); 339 try { 340 loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger); 341 } finally { 342 Trace.endSection(); 343 } 344 logASplit("loadWorkspace"); 345 346 if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { 347 verifyNotStopped(); 348 mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState, 349 mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts); 350 mModelDelegate.markActive(); 351 logASplit("workspaceDelegateItems"); 352 } 353 } 354 loadWorkspaceImpl( List<ShortcutInfo> allDeepShortcuts, String selection, @Nullable LoaderMemoryLogger memoryLogger)355 private void loadWorkspaceImpl( 356 List<ShortcutInfo> allDeepShortcuts, 357 String selection, 358 @Nullable LoaderMemoryLogger memoryLogger) { 359 final Context context = mApp.getContext(); 360 final PackageManagerHelper pmHelper = new PackageManagerHelper(context); 361 final boolean isSafeMode = pmHelper.isSafeMode(); 362 final boolean isSdCardReady = Utilities.isBootCompleted(); 363 final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context); 364 365 ModelDbController dbController = mApp.getModel().getModelDbController(); 366 dbController.tryMigrateDB(); 367 Log.d(TAG, "loadWorkspace: loading default favorites"); 368 dbController.loadDefaultFavoritesIfNecessary(); 369 370 synchronized (mBgDataModel) { 371 mBgDataModel.clear(); 372 mPendingPackages.clear(); 373 374 final HashMap<PackageUserKey, SessionInfo> installingPkgs = 375 mSessionHelper.getActiveSessions(); 376 installingPkgs.forEach(mApp.getIconCache()::updateSessionCache); 377 FileLog.d(TAG, "loadWorkspace: Packages with active install sessions: " 378 + installingPkgs.values()); 379 380 final PackageUserKey tempPackageKey = new PackageUserKey(null, null); 381 mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs); 382 383 mShortcutKeyToPinnedShortcuts = new HashMap<>(); 384 final LoaderCursor c = new LoaderCursor( 385 dbController.query(TABLE_NAME, null, selection, null, null), 386 mApp, mUserManagerState); 387 final Bundle extras = c.getExtras(); 388 mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME); 389 try { 390 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); 391 392 mUserManagerState.init(mUserCache, mUserManager); 393 394 for (UserHandle user : mUserCache.getUserProfiles()) { 395 long serialNo = mUserCache.getSerialNumberForUser(user); 396 boolean userUnlocked = mUserManager.isUserUnlocked(user); 397 398 // We can only query for shortcuts when the user is unlocked. 399 if (userUnlocked) { 400 QueryResult pinnedShortcuts = new ShortcutRequest(context, user) 401 .query(ShortcutRequest.PINNED); 402 if (pinnedShortcuts.wasSuccess()) { 403 for (ShortcutInfo shortcut : pinnedShortcuts) { 404 mShortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), 405 shortcut); 406 } 407 } else { 408 // Shortcut manager can fail due to some race condition when the 409 // lock state changes too frequently. For the purpose of the loading 410 // shortcuts, consider the user is still locked. 411 userUnlocked = false; 412 } 413 } 414 unlockedUsers.put(serialNo, userUnlocked); 415 } 416 417 List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>(); 418 419 while (!mStopped && c.moveToNext()) { 420 processWorkspaceItem(c, memoryLogger, installingPkgs, isSdCardReady, 421 tempPackageKey, widgetHelper, pmHelper, 422 iconRequestInfos, unlockedUsers, isSafeMode, allDeepShortcuts); 423 } 424 tryLoadWorkspaceIconsInBulk(iconRequestInfos); 425 } finally { 426 IOUtils.closeSilently(c); 427 } 428 429 if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { 430 mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState, 431 mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts); 432 mModelDelegate.loadAndBindAllAppsItems(mUserManagerState, 433 mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts); 434 mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList); 435 mModelDelegate.markActive(); 436 } 437 438 // Break early if we've stopped loading 439 if (mStopped) { 440 mBgDataModel.clear(); 441 return; 442 } 443 444 // Remove dead items 445 mItemsDeleted = c.commitDeleted(); 446 447 // Sort the folder items, update ranks, and make sure all preview items are high res. 448 FolderGridOrganizer verifier = 449 new FolderGridOrganizer(mApp.getInvariantDeviceProfile()); 450 for (FolderInfo folder : mBgDataModel.folders) { 451 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); 452 verifier.setFolderInfo(folder); 453 int size = folder.contents.size(); 454 455 // Update ranks here to ensure there are no gaps caused by removed folder items. 456 // Ranks are the source of truth for folder items, so cellX and cellY can be ignored 457 // for now. Database will be updated once user manually modifies folder. 458 for (int rank = 0; rank < size; ++rank) { 459 WorkspaceItemInfo info = folder.contents.get(rank); 460 info.rank = rank; 461 462 if (info.usingLowResIcon() 463 && info.itemType == Favorites.ITEM_TYPE_APPLICATION 464 && verifier.isItemInPreview(info.rank)) { 465 mIconCache.getTitleAndIcon(info, false); 466 } 467 } 468 } 469 470 c.commitRestoredItems(); 471 } 472 } 473 processWorkspaceItem(LoaderCursor c, LoaderMemoryLogger memoryLogger, HashMap<PackageUserKey, SessionInfo> installingPkgs, boolean isSdCardReady, PackageUserKey tempPackageKey, WidgetManagerHelper widgetHelper, PackageManagerHelper pmHelper, List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos, LongSparseArray<Boolean> unlockedUsers, boolean isSafeMode, List<ShortcutInfo> allDeepShortcuts)474 private void processWorkspaceItem(LoaderCursor c, 475 LoaderMemoryLogger memoryLogger, 476 HashMap<PackageUserKey, SessionInfo> installingPkgs, 477 boolean isSdCardReady, 478 PackageUserKey tempPackageKey, 479 WidgetManagerHelper widgetHelper, 480 PackageManagerHelper pmHelper, 481 List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos, 482 LongSparseArray<Boolean> unlockedUsers, 483 boolean isSafeMode, 484 List<ShortcutInfo> allDeepShortcuts) { 485 486 try { 487 if (c.user == null) { 488 // User has been deleted, remove the item. 489 c.markDeleted("User has been deleted"); 490 return; 491 } 492 493 boolean allowMissingTarget = false; 494 switch (c.itemType) { 495 case Favorites.ITEM_TYPE_APPLICATION: 496 case Favorites.ITEM_TYPE_DEEP_SHORTCUT: 497 Intent intent = c.parseIntent(); 498 if (intent == null) { 499 c.markDeleted("Invalid or null intent"); 500 return; 501 } 502 503 int disabledState = mUserManagerState.isUserQuiet(c.serialNumber) 504 ? WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0; 505 ComponentName cn = intent.getComponent(); 506 String targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); 507 508 if (TextUtils.isEmpty(targetPkg)) { 509 c.markDeleted("Shortcuts can't have null package"); 510 return; 511 } 512 513 // If there is no target package, it's an implicit intent 514 // (legacy shortcut) which is always valid 515 boolean validTarget = TextUtils.isEmpty(targetPkg) 516 || mLauncherApps.isPackageEnabled(targetPkg, c.user); 517 518 // If it's a deep shortcut, we'll use pinned shortcuts to restore it 519 if (cn != null && validTarget && c.itemType 520 != Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 521 // If the apk is present and the shortcut points to a specific component. 522 523 // If the component is already present 524 if (mLauncherApps.isActivityEnabled(cn, c.user)) { 525 // no special handling necessary for this item 526 c.markRestored(); 527 } else { 528 // Gracefully try to find a fallback activity. 529 intent = pmHelper.getAppLaunchIntent(targetPkg, c.user); 530 if (intent != null) { 531 c.restoreFlag = 0; 532 c.updater().put( 533 Favorites.INTENT, 534 intent.toUri(0)).commit(); 535 cn = intent.getComponent(); 536 } else { 537 c.markDeleted("Unable to find a launch target"); 538 return; 539 } 540 } 541 } 542 // else if cn == null => can't infer much, leave it 543 // else if !validPkg => could be restored icon or missing sd-card 544 545 if (!TextUtils.isEmpty(targetPkg) && !validTarget) { 546 // Points to a valid app (superset of cn != null) but the apk 547 // is not available. 548 549 if (c.restoreFlag != 0) { 550 // Package is not yet available but might be 551 // installed later. 552 FileLog.d(TAG, "package not yet restored: " + targetPkg); 553 554 tempPackageKey.update(targetPkg, c.user); 555 if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) { 556 // Restore has started once. 557 } else if (installingPkgs.containsKey(tempPackageKey)) { 558 // App restore has started. Update the flag 559 c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED; 560 c.updater().put(Favorites.RESTORED, 561 c.restoreFlag).commit(); 562 } else { 563 c.markDeleted("Unrestored app removed: " + targetPkg); 564 return; 565 } 566 } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) { 567 // Package is present but not available. 568 disabledState |= WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE; 569 // Add the icon on the workspace anyway. 570 allowMissingTarget = true; 571 } else if (!isSdCardReady) { 572 // SdCard is not ready yet. Package might get available, 573 // once it is ready. 574 Log.d(TAG, "Missing pkg, will check later: " + targetPkg); 575 mPendingPackages.add(new PackageUserKey(targetPkg, c.user)); 576 // Add the icon on the workspace anyway. 577 allowMissingTarget = true; 578 } else { 579 // Do not wait for external media load anymore. 580 c.markDeleted("Invalid package removed: " + targetPkg); 581 return; 582 } 583 } 584 585 if ((c.restoreFlag & WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI) != 0) { 586 validTarget = false; 587 } 588 589 if (validTarget) { 590 // The shortcut points to a valid target (either no target 591 // or something which is ready to be used) 592 c.markRestored(); 593 } 594 595 boolean useLowResIcon = !c.isOnWorkspaceOrHotseat(); 596 597 WorkspaceItemInfo info; 598 if (c.restoreFlag != 0) { 599 // Already verified above that user is same as default user 600 info = c.getRestoredItemInfo(intent); 601 } else if (c.itemType == Favorites.ITEM_TYPE_APPLICATION) { 602 info = c.getAppShortcutInfo( 603 intent, allowMissingTarget, useLowResIcon, false); 604 } else if (c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 605 ShortcutKey key = ShortcutKey.fromIntent(intent, c.user); 606 if (unlockedUsers.get(c.serialNumber)) { 607 ShortcutInfo pinnedShortcut = mShortcutKeyToPinnedShortcuts.get(key); 608 if (pinnedShortcut == null) { 609 // The shortcut is no longer valid. 610 c.markDeleted("Pinned shortcut not found"); 611 return; 612 } 613 info = new WorkspaceItemInfo(pinnedShortcut, mApp.getContext()); 614 // If the pinned deep shortcut is no longer published, 615 // use the last saved icon instead of the default. 616 mIconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon); 617 618 if (pmHelper.isAppSuspended( 619 pinnedShortcut.getPackage(), info.user)) { 620 info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED; 621 } 622 intent = info.getIntent(); 623 allDeepShortcuts.add(pinnedShortcut); 624 } else { 625 // Create a shortcut info in disabled mode for now. 626 info = c.loadSimpleWorkspaceItem(); 627 info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER; 628 } 629 } else { // item type == ITEM_TYPE_SHORTCUT 630 info = c.loadSimpleWorkspaceItem(); 631 632 // Shortcuts are only available on the primary profile 633 if (!TextUtils.isEmpty(targetPkg) 634 && pmHelper.isAppSuspended(targetPkg, c.user)) { 635 disabledState |= FLAG_DISABLED_SUSPENDED; 636 } 637 info.options = c.getOptions(); 638 639 // App shortcuts that used to be automatically added to Launcher 640 // didn't always have the correct intent flags set, so do that here 641 if (intent.getAction() != null 642 && intent.getCategories() != null 643 && intent.getAction().equals(Intent.ACTION_MAIN) 644 && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 645 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK 646 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 647 } 648 } 649 650 if (info != null) { 651 if (info.itemType != Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 652 // Skip deep shortcuts; their title and icons have already been 653 // loaded above. 654 iconRequestInfos.add(c.createIconRequestInfo(info, useLowResIcon)); 655 } 656 657 c.applyCommonProperties(info); 658 659 info.intent = intent; 660 info.rank = c.getRank(); 661 info.spanX = 1; 662 info.spanY = 1; 663 info.runtimeStatusFlags |= disabledState; 664 if (isSafeMode && !isSystemApp(mApp.getContext(), intent)) { 665 info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE; 666 } 667 LauncherActivityInfo activityInfo = c.getLauncherActivityInfo(); 668 if (activityInfo != null) { 669 info.setProgressLevel( 670 PackageManagerHelper.getLoadingProgress(activityInfo), 671 PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING); 672 } 673 674 if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) { 675 tempPackageKey.update(targetPkg, c.user); 676 SessionInfo si = installingPkgs.get(tempPackageKey); 677 if (si == null) { 678 info.runtimeStatusFlags 679 &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; 680 } else if (activityInfo == null) { 681 int installProgress = (int) (si.getProgress() * 100); 682 683 info.setProgressLevel(installProgress, 684 PackageInstallInfo.STATUS_INSTALLING); 685 } 686 } 687 688 c.checkAndAddItem(info, mBgDataModel, memoryLogger); 689 } else { 690 throw new RuntimeException("Unexpected null WorkspaceItemInfo"); 691 } 692 break; 693 694 case Favorites.ITEM_TYPE_FOLDER: 695 case Favorites.ITEM_TYPE_APP_PAIR: 696 FolderInfo folderInfo = mBgDataModel.findOrMakeFolder(c.id); 697 c.applyCommonProperties(folderInfo); 698 699 folderInfo.itemType = c.itemType; 700 // Do not trim the folder label, as is was set by the user. 701 folderInfo.title = c.getString(c.mTitleIndex); 702 folderInfo.spanX = 1; 703 folderInfo.spanY = 1; 704 folderInfo.options = c.getOptions(); 705 706 // no special handling required for restored folders 707 c.markRestored(); 708 709 c.checkAndAddItem(folderInfo, mBgDataModel, memoryLogger); 710 break; 711 712 case Favorites.ITEM_TYPE_APPWIDGET: 713 if (WidgetsModel.GO_DISABLE_WIDGETS) { 714 c.markDeleted("Only legacy shortcuts can have null package"); 715 return; 716 } 717 // Follow through 718 case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 719 // Read all Launcher-specific widget details 720 boolean customWidget = c.itemType 721 == Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 722 723 int appWidgetId = c.getAppWidgetId(); 724 String savedProvider = c.getAppWidgetProvider(); 725 final ComponentName component; 726 727 if ((c.getOptions() & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0) { 728 component = QsbContainerView.getSearchComponentName(mApp.getContext()); 729 if (component == null) { 730 c.markDeleted("Discarding SearchWidget without packagename "); 731 return; 732 } 733 } else { 734 component = ComponentName.unflattenFromString(savedProvider); 735 } 736 final boolean isIdValid = 737 !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 738 final boolean wasProviderReady = 739 !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY); 740 741 ComponentKey providerKey = new ComponentKey(component, c.user); 742 if (!mWidgetProvidersMap.containsKey(providerKey)) { 743 mWidgetProvidersMap.put(providerKey, 744 widgetHelper.findProvider(component, c.user)); 745 } 746 final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(providerKey); 747 748 final boolean isProviderReady = isValidProvider(provider); 749 if (!isSafeMode && !customWidget && wasProviderReady && !isProviderReady) { 750 c.markDeleted("Deleting widget that isn't installed anymore: " + provider); 751 } else { 752 LauncherAppWidgetInfo appWidgetInfo; 753 if (isProviderReady) { 754 appWidgetInfo = 755 new LauncherAppWidgetInfo(appWidgetId, provider.provider); 756 757 // The provider is available. So the widget is either 758 // available or not available. We do not need to track 759 // any future restore updates. 760 int status = c.restoreFlag 761 & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED 762 & ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 763 if (!wasProviderReady) { 764 // If provider was not previously ready, update status and UI flag. 765 766 // Id would be valid only if the widget restore broadcast received. 767 if (isIdValid) { 768 status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 769 } 770 } 771 appWidgetInfo.restoreStatus = status; 772 } else { 773 Log.v(TAG, "Widget restore pending id=" + c.id 774 + " appWidgetId=" + appWidgetId 775 + " status =" + c.restoreFlag); 776 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, component); 777 appWidgetInfo.restoreStatus = c.restoreFlag; 778 779 tempPackageKey.update(component.getPackageName(), c.user); 780 SessionInfo si = installingPkgs.get(tempPackageKey); 781 Integer installProgress = si == null 782 ? null 783 : (int) (si.getProgress() * 100); 784 785 if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) { 786 // Restore has started once. 787 } else if (installProgress != null) { 788 // App restore has started. Update the flag 789 appWidgetInfo.restoreStatus 790 |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 791 } else if (!isSafeMode) { 792 c.markDeleted("Unrestored widget removed: " + component); 793 return; 794 } 795 796 appWidgetInfo.installProgress = 797 installProgress == null ? 0 : installProgress; 798 } 799 if (appWidgetInfo.hasRestoreFlag( 800 LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { 801 appWidgetInfo.bindOptions = c.parseIntent(); 802 } 803 804 c.applyCommonProperties(appWidgetInfo); 805 appWidgetInfo.spanX = c.getSpanX(); 806 appWidgetInfo.spanY = c.getSpanY(); 807 appWidgetInfo.options = c.getOptions(); 808 appWidgetInfo.user = c.user; 809 appWidgetInfo.sourceContainer = c.getAppWidgetSource(); 810 811 if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) { 812 c.markDeleted("Widget has invalid size: " 813 + appWidgetInfo.spanX + "x" + appWidgetInfo.spanY); 814 return; 815 } 816 LauncherAppWidgetProviderInfo widgetProviderInfo = 817 widgetHelper.getLauncherAppWidgetInfo(appWidgetId); 818 if (widgetProviderInfo != null 819 && (appWidgetInfo.spanX < widgetProviderInfo.minSpanX 820 || appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) { 821 FileLog.d(TAG, "Widget " + widgetProviderInfo.getComponent() 822 + " minSizes not meet: span=" + appWidgetInfo.spanX 823 + "x" + appWidgetInfo.spanY + " minSpan=" 824 + widgetProviderInfo.minSpanX + "x" 825 + widgetProviderInfo.minSpanY); 826 logWidgetInfo(mApp.getInvariantDeviceProfile(), 827 widgetProviderInfo); 828 } 829 if (!c.isOnWorkspaceOrHotseat()) { 830 c.markDeleted("Widget found where container != CONTAINER_DESKTOP" 831 + "nor CONTAINER_HOTSEAT - ignoring!"); 832 return; 833 } 834 835 if (!customWidget) { 836 String providerName = appWidgetInfo.providerName.flattenToString(); 837 if (!providerName.equals(savedProvider) 838 || (appWidgetInfo.restoreStatus != c.restoreFlag)) { 839 c.updater() 840 .put(Favorites.APPWIDGET_PROVIDER, 841 providerName) 842 .put(Favorites.RESTORED, 843 appWidgetInfo.restoreStatus) 844 .commit(); 845 } 846 } 847 848 if (appWidgetInfo.restoreStatus 849 != LauncherAppWidgetInfo.RESTORE_COMPLETED) { 850 appWidgetInfo.pendingItemInfo = WidgetsModel.newPendingItemInfo( 851 mApp.getContext(), 852 appWidgetInfo.providerName, 853 appWidgetInfo.user); 854 mIconCache.getTitleAndIconForApp( 855 appWidgetInfo.pendingItemInfo, false); 856 } 857 858 c.checkAndAddItem(appWidgetInfo, mBgDataModel); 859 } 860 break; 861 } 862 } catch (Exception e) { 863 Log.e(TAG, "Desktop items loading interrupted", e); 864 } 865 } 866 tryLoadWorkspaceIconsInBulk( List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos)867 private void tryLoadWorkspaceIconsInBulk( 868 List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos) { 869 Trace.beginSection("LoadWorkspaceIconsInBulk"); 870 try { 871 mIconCache.getTitlesAndIconsInBulk(iconRequestInfos); 872 for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) { 873 WorkspaceItemInfo wai = iconRequestInfo.itemInfo; 874 if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) { 875 iconRequestInfo.loadWorkspaceIcon(mApp.getContext()); 876 } 877 } 878 } finally { 879 Trace.endSection(); 880 } 881 } 882 setIgnorePackages(IconCacheUpdateHandler updateHandler)883 private void setIgnorePackages(IconCacheUpdateHandler updateHandler) { 884 // Ignore packages which have a promise icon. 885 synchronized (mBgDataModel) { 886 for (ItemInfo info : mBgDataModel.itemsIdMap) { 887 if (info instanceof WorkspaceItemInfo) { 888 WorkspaceItemInfo si = (WorkspaceItemInfo) info; 889 if (si.isPromise() && si.getTargetComponent() != null) { 890 updateHandler.addPackagesToIgnore( 891 si.user, si.getTargetComponent().getPackageName()); 892 } 893 } else if (info instanceof LauncherAppWidgetInfo) { 894 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; 895 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 896 updateHandler.addPackagesToIgnore( 897 lawi.user, lawi.providerName.getPackageName()); 898 } 899 } 900 } 901 } 902 } 903 sanitizeFolders(boolean itemsDeleted)904 private void sanitizeFolders(boolean itemsDeleted) { 905 if (itemsDeleted) { 906 // Remove any empty folder 907 IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders(); 908 synchronized (mBgDataModel) { 909 for (int folderId : deletedFolderIds) { 910 mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId)); 911 mBgDataModel.folders.remove(folderId); 912 mBgDataModel.itemsIdMap.remove(folderId); 913 } 914 } 915 } 916 } 917 sanitizeWidgetsShortcutsAndPackages()918 private void sanitizeWidgetsShortcutsAndPackages() { 919 Context context = mApp.getContext(); 920 921 // Remove any ghost widgets 922 mApp.getModel().getModelDbController().removeGhostWidgets(); 923 924 // Update pinned state of model shortcuts 925 mBgDataModel.updateShortcutPinnedState(context); 926 927 if (!Utilities.isBootCompleted() && !mPendingPackages.isEmpty()) { 928 context.registerReceiver( 929 new SdCardAvailableReceiver(mApp, mPendingPackages), 930 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), 931 null, 932 MODEL_EXECUTOR.getHandler()); 933 } 934 } 935 loadAllApps()936 private List<LauncherActivityInfo> loadAllApps() { 937 final List<UserHandle> profiles = mUserCache.getUserProfiles(); 938 List<LauncherActivityInfo> allActivityList = new ArrayList<>(); 939 // Clear the list of apps 940 mBgAllAppsList.clear(); 941 942 List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>(); 943 for (UserHandle user : profiles) { 944 // Query for the set of apps 945 final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user); 946 // Fail if we don't have any apps 947 // TODO: Fix this. Only fail for the current user. 948 if (apps == null || apps.isEmpty()) { 949 return allActivityList; 950 } 951 boolean quietMode = mUserManagerState.isUserQuiet(user); 952 // Create the ApplicationInfos 953 for (int i = 0; i < apps.size(); i++) { 954 LauncherActivityInfo app = apps.get(i); 955 AppInfo appInfo = new AppInfo(app, user, quietMode); 956 957 iconRequestInfos.add(new IconRequestInfo<>( 958 appInfo, app, /* useLowResIcon= */ false)); 959 mBgAllAppsList.add( 960 appInfo, app, false); 961 } 962 allActivityList.addAll(apps); 963 } 964 965 966 if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { 967 // get all active sessions and add them to the all apps list 968 for (PackageInstaller.SessionInfo info : 969 mSessionHelper.getAllVerifiedSessions()) { 970 AppInfo promiseAppInfo = mBgAllAppsList.addPromiseApp( 971 mApp.getContext(), 972 PackageInstallInfo.fromInstallingState(info), 973 false); 974 975 if (promiseAppInfo != null) { 976 iconRequestInfos.add(new IconRequestInfo<>( 977 promiseAppInfo, 978 /* launcherActivityInfo= */ null, 979 promiseAppInfo.usingLowResIcon())); 980 } 981 } 982 } 983 984 Trace.beginSection("LoadAllAppsIconsInBulk"); 985 try { 986 mIconCache.getTitlesAndIconsInBulk(iconRequestInfos); 987 iconRequestInfos.forEach(iconRequestInfo -> 988 mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo)); 989 } finally { 990 Trace.endSection(); 991 } 992 993 mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED, 994 mUserManagerState.isAnyProfileQuietModeEnabled()); 995 mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION, 996 hasShortcutsPermission(mApp.getContext())); 997 mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION, 998 mApp.getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE") 999 == PackageManager.PERMISSION_GRANTED); 1000 1001 mBgAllAppsList.getAndResetChangeFlag(); 1002 return allActivityList; 1003 } 1004 loadDeepShortcuts()1005 private List<ShortcutInfo> loadDeepShortcuts() { 1006 List<ShortcutInfo> allShortcuts = new ArrayList<>(); 1007 mBgDataModel.deepShortcutMap.clear(); 1008 1009 if (mBgAllAppsList.hasShortcutHostPermission()) { 1010 for (UserHandle user : mUserCache.getUserProfiles()) { 1011 if (mUserManager.isUserUnlocked(user)) { 1012 List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user) 1013 .query(ShortcutRequest.ALL); 1014 allShortcuts.addAll(shortcuts); 1015 mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts); 1016 } 1017 } 1018 } 1019 return allShortcuts; 1020 } 1021 loadFolderNames()1022 private void loadFolderNames() { 1023 FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(), 1024 mBgAllAppsList.data, mBgDataModel.folders); 1025 1026 synchronized (mBgDataModel) { 1027 for (int i = 0; i < mBgDataModel.folders.size(); i++) { 1028 FolderNameInfos suggestionInfos = new FolderNameInfos(); 1029 FolderInfo info = mBgDataModel.folders.valueAt(i); 1030 if (info.suggestedFolderNames == null) { 1031 provider.getSuggestedFolderName(mApp.getContext(), info.contents, 1032 suggestionInfos); 1033 info.suggestedFolderNames = suggestionInfos; 1034 } 1035 } 1036 } 1037 } 1038 isValidProvider(AppWidgetProviderInfo provider)1039 public static boolean isValidProvider(AppWidgetProviderInfo provider) { 1040 return (provider != null) && (provider.provider != null) 1041 && (provider.provider.getPackageName() != null); 1042 } 1043 1044 @SuppressLint("NewApi") // Already added API check. logWidgetInfo(InvariantDeviceProfile idp, LauncherAppWidgetProviderInfo widgetProviderInfo)1045 private static void logWidgetInfo(InvariantDeviceProfile idp, 1046 LauncherAppWidgetProviderInfo widgetProviderInfo) { 1047 Point cellSize = new Point(); 1048 for (DeviceProfile deviceProfile : idp.supportedProfiles) { 1049 deviceProfile.getCellSize(cellSize); 1050 FileLog.d(TAG, "DeviceProfile available width: " + deviceProfile.availableWidthPx 1051 + ", available height: " + deviceProfile.availableHeightPx 1052 + ", cellLayoutBorderSpacePx Horizontal: " 1053 + deviceProfile.cellLayoutBorderSpacePx.x 1054 + ", cellLayoutBorderSpacePx Vertical: " 1055 + deviceProfile.cellLayoutBorderSpacePx.y 1056 + ", cellSize: " + cellSize); 1057 } 1058 1059 StringBuilder widgetDimension = new StringBuilder(); 1060 widgetDimension.append("Widget dimensions:\n") 1061 .append("minResizeWidth: ") 1062 .append(widgetProviderInfo.minResizeWidth) 1063 .append("\n") 1064 .append("minResizeHeight: ") 1065 .append(widgetProviderInfo.minResizeHeight) 1066 .append("\n") 1067 .append("defaultWidth: ") 1068 .append(widgetProviderInfo.minWidth) 1069 .append("\n") 1070 .append("defaultHeight: ") 1071 .append(widgetProviderInfo.minHeight) 1072 .append("\n"); 1073 if (Utilities.ATLEAST_S) { 1074 widgetDimension.append("targetCellWidth: ") 1075 .append(widgetProviderInfo.targetCellWidth) 1076 .append("\n") 1077 .append("targetCellHeight: ") 1078 .append(widgetProviderInfo.targetCellHeight) 1079 .append("\n") 1080 .append("maxResizeWidth: ") 1081 .append(widgetProviderInfo.maxResizeWidth) 1082 .append("\n") 1083 .append("maxResizeHeight: ") 1084 .append(widgetProviderInfo.maxResizeHeight) 1085 .append("\n"); 1086 } 1087 FileLog.d(TAG, widgetDimension.toString()); 1088 } 1089 logASplit(String label)1090 private static void logASplit(String label) { 1091 if (DEBUG) { 1092 Log.d(TAG, label); 1093 } 1094 } 1095 } 1096