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