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