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