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