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