1 /* 2 * Copyright (C) 2016 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 package com.android.launcher3.model; 17 18 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY; 19 20 import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS; 21 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED; 22 23 import static java.util.stream.Collectors.groupingBy; 24 import static java.util.stream.Collectors.mapping; 25 26 import android.content.Context; 27 import android.content.pm.LauncherApps; 28 import android.content.pm.ShortcutInfo; 29 import android.os.UserHandle; 30 import android.text.TextUtils; 31 import android.util.ArraySet; 32 import android.util.Log; 33 34 import com.android.launcher3.LauncherSettings; 35 import com.android.launcher3.LauncherSettings.Favorites; 36 import com.android.launcher3.Workspace; 37 import com.android.launcher3.config.FeatureFlags; 38 import com.android.launcher3.model.data.AppInfo; 39 import com.android.launcher3.model.data.FolderInfo; 40 import com.android.launcher3.model.data.ItemInfo; 41 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 42 import com.android.launcher3.model.data.WorkspaceItemInfo; 43 import com.android.launcher3.pm.UserCache; 44 import com.android.launcher3.shortcuts.ShortcutKey; 45 import com.android.launcher3.shortcuts.ShortcutRequest; 46 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult; 47 import com.android.launcher3.util.ComponentKey; 48 import com.android.launcher3.util.IntArray; 49 import com.android.launcher3.util.IntSet; 50 import com.android.launcher3.util.IntSparseArrayMap; 51 import com.android.launcher3.util.ItemInfoMatcher; 52 import com.android.launcher3.util.ViewOnDrawExecutor; 53 import com.android.launcher3.widget.model.WidgetsListBaseEntry; 54 55 import java.io.FileDescriptor; 56 import java.io.PrintWriter; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collections; 60 import java.util.HashMap; 61 import java.util.HashSet; 62 import java.util.Iterator; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Set; 66 import java.util.function.Consumer; 67 import java.util.stream.Collectors; 68 import java.util.stream.Stream; 69 70 /** 71 * All the data stored in-memory and managed by the LauncherModel 72 */ 73 public class BgDataModel { 74 75 private static final String TAG = "BgDataModel"; 76 77 /** 78 * Map of all the ItemInfos (shortcuts, folders, and widgets) created by 79 * LauncherModel to their ids 80 */ 81 public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>(); 82 83 /** 84 * List of all the folders and shortcuts directly on the home screen (no widgets 85 * or shortcuts within folders). 86 */ 87 public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 88 89 /** 90 * All LauncherAppWidgetInfo created by LauncherModel. 91 */ 92 public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 93 94 /** 95 * Map of id to FolderInfos of all the folders created by LauncherModel 96 */ 97 public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>(); 98 99 /** 100 * Extra container based items 101 */ 102 public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>(); 103 104 /** 105 * Maps all launcher activities to counts of their shortcuts. 106 */ 107 public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>(); 108 109 /** 110 * Entire list of widgets. 111 */ 112 public final WidgetsModel widgetsModel = new WidgetsModel(); 113 114 /** 115 * Id when the model was last bound 116 */ 117 public int lastBindId = 0; 118 119 /** 120 * Clears all the data 121 */ clear()122 public synchronized void clear() { 123 workspaceItems.clear(); 124 appWidgets.clear(); 125 folders.clear(); 126 itemsIdMap.clear(); 127 deepShortcutMap.clear(); 128 extraItems.clear(); 129 } 130 131 /** 132 * Creates an array of valid workspace screens based on current items in the model. 133 */ collectWorkspaceScreens()134 public synchronized IntArray collectWorkspaceScreens() { 135 IntSet screenSet = new IntSet(); 136 for (ItemInfo item: itemsIdMap) { 137 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 138 screenSet.add(item.screenId); 139 } 140 } 141 if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) { 142 screenSet.add(Workspace.FIRST_SCREEN_ID); 143 } 144 return screenSet.getArray(); 145 } 146 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)147 public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, 148 String[] args) { 149 writer.println(prefix + "Data Model:"); 150 writer.println(prefix + " ---- workspace items "); 151 for (int i = 0; i < workspaceItems.size(); i++) { 152 writer.println(prefix + '\t' + workspaceItems.get(i).toString()); 153 } 154 writer.println(prefix + " ---- appwidget items "); 155 for (int i = 0; i < appWidgets.size(); i++) { 156 writer.println(prefix + '\t' + appWidgets.get(i).toString()); 157 } 158 writer.println(prefix + " ---- folder items "); 159 for (int i = 0; i< folders.size(); i++) { 160 writer.println(prefix + '\t' + folders.valueAt(i).toString()); 161 } 162 writer.println(prefix + " ---- items id map "); 163 for (int i = 0; i< itemsIdMap.size(); i++) { 164 writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); 165 } 166 167 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 168 writer.println(prefix + "shortcut counts "); 169 for (Integer count : deepShortcutMap.values()) { 170 writer.print(count + ", "); 171 } 172 writer.println(); 173 } 174 } 175 removeItem(Context context, ItemInfo... items)176 public synchronized void removeItem(Context context, ItemInfo... items) { 177 removeItem(context, Arrays.asList(items)); 178 } 179 removeItem(Context context, Iterable<? extends ItemInfo> items)180 public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) { 181 ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>(); 182 for (ItemInfo item : items) { 183 switch (item.itemType) { 184 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 185 folders.remove(item.id); 186 if (FeatureFlags.IS_STUDIO_BUILD) { 187 for (ItemInfo info : itemsIdMap) { 188 if (info.container == item.id) { 189 // We are deleting a folder which still contains items that 190 // think they are contained by that folder. 191 String msg = "deleting a folder (" + item + ") which still " + 192 "contains items (" + info + ")"; 193 Log.e(TAG, msg); 194 } 195 } 196 } 197 workspaceItems.remove(item); 198 break; 199 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 200 updatedDeepShortcuts.add(item.user); 201 // Fall through. 202 } 203 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 204 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 205 workspaceItems.remove(item); 206 break; 207 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 208 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 209 appWidgets.remove(item); 210 break; 211 } 212 itemsIdMap.remove(item.id); 213 } 214 updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user)); 215 } 216 addItem(Context context, ItemInfo item, boolean newItem)217 public synchronized void addItem(Context context, ItemInfo item, boolean newItem) { 218 itemsIdMap.put(item.id, item); 219 switch (item.itemType) { 220 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 221 folders.put(item.id, (FolderInfo) item); 222 workspaceItems.add(item); 223 break; 224 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 225 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 226 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 227 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 228 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 229 workspaceItems.add(item); 230 } else { 231 if (newItem) { 232 if (!folders.containsKey(item.container)) { 233 // Adding an item to a folder that doesn't exist. 234 String msg = "adding item: " + item + " to a folder that " + 235 " doesn't exist"; 236 Log.e(TAG, msg); 237 } 238 } else { 239 findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false); 240 } 241 242 } 243 break; 244 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 245 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 246 appWidgets.add((LauncherAppWidgetInfo) item); 247 break; 248 } 249 if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 250 updateShortcutPinnedState(context, item.user); 251 } 252 } 253 254 /** 255 * Updates the deep shortucts state in system to match out internal model, pinning any missing 256 * shortcuts and unpinning any extra shortcuts. 257 */ updateShortcutPinnedState(Context context)258 public void updateShortcutPinnedState(Context context) { 259 for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) { 260 updateShortcutPinnedState(context, user); 261 } 262 } 263 264 /** 265 * Updates the deep shortucts state in system to match out internal model, pinning any missing 266 * shortcuts and unpinning any extra shortcuts. 267 */ updateShortcutPinnedState(Context context, UserHandle user)268 public synchronized void updateShortcutPinnedState(Context context, UserHandle user) { 269 if (GO_DISABLE_WIDGETS) { 270 return; 271 } 272 273 // Collect all system shortcuts 274 QueryResult result = new ShortcutRequest(context, user) 275 .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY); 276 if (!result.wasSuccess()) { 277 return; 278 } 279 // Map of packageName to shortcutIds that are currently in the system 280 Map<String, Set<String>> systemMap = result.stream() 281 .collect(groupingBy(ShortcutInfo::getPackage, 282 mapping(ShortcutInfo::getId, Collectors.toSet()))); 283 284 // Collect all model shortcuts 285 Stream.Builder<WorkspaceItemInfo> itemStream = Stream.builder(); 286 forAllWorkspaceItemInfos(user, itemStream::accept); 287 // Map of packageName to shortcutIds that are currently in our model 288 Map<String, Set<String>> modelMap = Stream.concat( 289 // Model shortcuts 290 itemStream.build() 291 .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) 292 .map(ShortcutKey::fromItemInfo), 293 // Pending shortcuts 294 ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user)) 295 .collect(groupingBy(ShortcutKey::getPackageName, 296 mapping(ShortcutKey::getId, Collectors.toSet()))); 297 298 // Check for diff 299 for (Map.Entry<String, Set<String>> entry : modelMap.entrySet()) { 300 Set<String> modelShortcuts = entry.getValue(); 301 Set<String> systemShortcuts = systemMap.remove(entry.getKey()); 302 if (systemShortcuts == null) { 303 systemShortcuts = Collections.emptySet(); 304 } 305 306 // Do not use .equals as it can vary based on the type of set 307 if (systemShortcuts.size() != modelShortcuts.size() 308 || !systemShortcuts.containsAll(modelShortcuts)) { 309 // Update system state for this package 310 try { 311 context.getSystemService(LauncherApps.class).pinShortcuts( 312 entry.getKey(), new ArrayList<>(modelShortcuts), user); 313 } catch (SecurityException | IllegalStateException e) { 314 Log.w(TAG, "Failed to pin shortcut", e); 315 } 316 } 317 } 318 319 // If there are any extra pinned shortcuts, remove them 320 systemMap.keySet().forEach(packageName -> { 321 // Update system state 322 try { 323 context.getSystemService(LauncherApps.class).pinShortcuts( 324 packageName, Collections.emptyList(), user); 325 } catch (SecurityException | IllegalStateException e) { 326 Log.w(TAG, "Failed to unpin shortcut", e); 327 } 328 }); 329 } 330 331 /** 332 * Return an existing FolderInfo object if we have encountered this ID previously, 333 * or make a new one. 334 */ findOrMakeFolder(int id)335 public synchronized FolderInfo findOrMakeFolder(int id) { 336 // See if a placeholder was created for us already 337 FolderInfo folderInfo = folders.get(id); 338 if (folderInfo == null) { 339 // No placeholder -- create a new instance 340 folderInfo = new FolderInfo(); 341 folders.put(id, folderInfo); 342 } 343 return folderInfo; 344 } 345 346 /** 347 * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts. 348 */ updateDeepShortcutCounts( String packageName, UserHandle user, List<ShortcutInfo> shortcuts)349 public synchronized void updateDeepShortcutCounts( 350 String packageName, UserHandle user, List<ShortcutInfo> shortcuts) { 351 if (packageName != null) { 352 Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator(); 353 while (keysIter.hasNext()) { 354 ComponentKey next = keysIter.next(); 355 if (next.componentName.getPackageName().equals(packageName) 356 && next.user.equals(user)) { 357 keysIter.remove(); 358 } 359 } 360 } 361 362 // Now add the new shortcuts to the map. 363 for (ShortcutInfo shortcut : shortcuts) { 364 boolean shouldShowInContainer = shortcut.isEnabled() 365 && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()) 366 && shortcut.getActivity() != null; 367 if (shouldShowInContainer) { 368 ComponentKey targetComponent 369 = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); 370 371 Integer previousCount = deepShortcutMap.get(targetComponent); 372 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1); 373 } 374 } 375 } 376 377 /** 378 * Returns a list containing all workspace items including widgets. 379 */ getAllWorkspaceItems()380 public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() { 381 ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size()); 382 items.addAll(workspaceItems); 383 items.addAll(appWidgets); 384 return items; 385 } 386 387 /** 388 * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted 389 * items and dynamic/predicted items for the provided {@code userHandle}. 390 * Note the call is not synchronized over the model, that should be handled by the called. 391 */ forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op)392 public void forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op) { 393 for (ItemInfo info : itemsIdMap) { 394 if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) { 395 op.accept((WorkspaceItemInfo) info); 396 } 397 } 398 399 for (int i = extraItems.size() - 1; i >= 0; i--) { 400 for (ItemInfo info : extraItems.valueAt(i).items) { 401 if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) { 402 op.accept((WorkspaceItemInfo) info); 403 } 404 } 405 } 406 } 407 408 /** 409 * An object containing items corresponding to a fixed container 410 */ 411 public static class FixedContainerItems { 412 413 public final int containerId; 414 public final List<ItemInfo> items; 415 FixedContainerItems(int containerId)416 public FixedContainerItems(int containerId) { 417 this(containerId, new ArrayList<>()); 418 } 419 FixedContainerItems(int containerId, List<ItemInfo> items)420 public FixedContainerItems(int containerId, List<ItemInfo> items) { 421 this.containerId = containerId; 422 this.items = items; 423 } 424 425 @Override clone()426 public FixedContainerItems clone() { 427 return new FixedContainerItems(containerId, new ArrayList<>(items)); 428 } 429 setItems(List<ItemInfo> newItems)430 public void setItems(List<ItemInfo> newItems) { 431 items.clear(); 432 newItems.forEach(item -> { 433 item.container = containerId; 434 items.add(item); 435 }); 436 } 437 } 438 439 440 public interface Callbacks { 441 // If the launcher has permission to access deep shortcuts. 442 int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0; 443 // If quiet mode is enabled for any user 444 int FLAG_QUIET_MODE_ENABLED = 1 << 1; 445 // If launcher can change quiet mode 446 int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2; 447 448 /** 449 * Returns the page number to bind first, synchronously if possible or -1 450 */ getPageToBindSynchronously()451 int getPageToBindSynchronously(); clearPendingBinds()452 void clearPendingBinds(); startBinding()453 void startBinding(); bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)454 void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); bindScreens(IntArray orderedScreenIds)455 void bindScreens(IntArray orderedScreenIds); finishFirstPageBind(ViewOnDrawExecutor executor)456 void finishFirstPageBind(ViewOnDrawExecutor executor); finishBindingItems(int pageBoundFirst)457 void finishBindingItems(int pageBoundFirst); preAddApps()458 void preAddApps(); bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)459 void bindAppsAdded(IntArray newScreens, 460 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); 461 462 /** 463 * Binds updated incremental download progress 464 */ bindIncrementalDownloadProgressUpdated(AppInfo app)465 void bindIncrementalDownloadProgressUpdated(AppInfo app); bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated)466 void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated); bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)467 void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); bindRestoreItemsChange(HashSet<ItemInfo> updates)468 void bindRestoreItemsChange(HashSet<ItemInfo> updates); bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)469 void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); bindAllWidgets(List<WidgetsListBaseEntry> widgets)470 void bindAllWidgets(List<WidgetsListBaseEntry> widgets); onPageBoundSynchronously(int page)471 void onPageBoundSynchronously(int page); executeOnNextDraw(ViewOnDrawExecutor executor)472 void executeOnNextDraw(ViewOnDrawExecutor executor); bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)473 void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap); 474 475 /** 476 * Binds extra item provided any external source 477 */ bindExtraContainerItems(FixedContainerItems item)478 default void bindExtraContainerItems(FixedContainerItems item) { } 479 bindAllApplications(AppInfo[] apps, int flags)480 void bindAllApplications(AppInfo[] apps, int flags); 481 } 482 } 483