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 com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS; 19 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED; 20 21 import android.content.Context; 22 import android.content.pm.LauncherApps; 23 import android.content.pm.ShortcutInfo; 24 import android.os.UserHandle; 25 import android.text.TextUtils; 26 import android.util.Log; 27 import android.util.MutableInt; 28 29 import com.android.launcher3.InstallShortcutReceiver; 30 import com.android.launcher3.LauncherSettings; 31 import com.android.launcher3.Workspace; 32 import com.android.launcher3.config.FeatureFlags; 33 import com.android.launcher3.model.data.AppInfo; 34 import com.android.launcher3.model.data.FolderInfo; 35 import com.android.launcher3.model.data.ItemInfo; 36 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 37 import com.android.launcher3.model.data.PromiseAppInfo; 38 import com.android.launcher3.model.data.WorkspaceItemInfo; 39 import com.android.launcher3.shortcuts.ShortcutKey; 40 import com.android.launcher3.shortcuts.ShortcutRequest; 41 import com.android.launcher3.util.ComponentKey; 42 import com.android.launcher3.util.IntArray; 43 import com.android.launcher3.util.IntSet; 44 import com.android.launcher3.util.IntSparseArrayMap; 45 import com.android.launcher3.util.ItemInfoMatcher; 46 import com.android.launcher3.util.ViewOnDrawExecutor; 47 import com.android.launcher3.widget.WidgetListRowEntry; 48 49 import java.io.FileDescriptor; 50 import java.io.PrintWriter; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.HashMap; 54 import java.util.HashSet; 55 import java.util.Iterator; 56 import java.util.List; 57 import java.util.Map; 58 import java.util.function.BiConsumer; 59 import java.util.stream.Collectors; 60 61 /** 62 * All the data stored in-memory and managed by the LauncherModel 63 */ 64 public class BgDataModel { 65 66 private static final String TAG = "BgDataModel"; 67 68 /** 69 * Map of all the ItemInfos (shortcuts, folders, and widgets) created by 70 * LauncherModel to their ids 71 */ 72 public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>(); 73 74 /** 75 * List of all the folders and shortcuts directly on the home screen (no widgets 76 * or shortcuts within folders). 77 */ 78 public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 79 80 /** 81 * All LauncherAppWidgetInfo created by LauncherModel. 82 */ 83 public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 84 85 /** 86 * Map of id to FolderInfos of all the folders created by LauncherModel 87 */ 88 public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>(); 89 90 /** 91 * Map of ShortcutKey to the number of times it is pinned. 92 */ 93 public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>(); 94 95 /** 96 * List of all cached predicted items visible on home screen 97 */ 98 public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>(); 99 100 /** 101 * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION 102 * @see Callbacks#FLAG_QUIET_MODE_ENABLED 103 * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION 104 */ 105 public int flags; 106 107 /** 108 * Maps all launcher activities to counts of their shortcuts. 109 */ 110 public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>(); 111 112 /** 113 * Entire list of widgets. 114 */ 115 public final WidgetsModel widgetsModel = new WidgetsModel(); 116 117 /** 118 * Id when the model was last bound 119 */ 120 public int lastBindId = 0; 121 122 /** 123 * Clears all the data 124 */ clear()125 public synchronized void clear() { 126 workspaceItems.clear(); 127 appWidgets.clear(); 128 folders.clear(); 129 itemsIdMap.clear(); 130 pinnedShortcutCounts.clear(); 131 deepShortcutMap.clear(); 132 } 133 134 /** 135 * Creates an array of valid workspace screens based on current items in the model. 136 */ collectWorkspaceScreens()137 public synchronized IntArray collectWorkspaceScreens() { 138 IntSet screenSet = new IntSet(); 139 for (ItemInfo item: itemsIdMap) { 140 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 141 screenSet.add(item.screenId); 142 } 143 } 144 if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) { 145 screenSet.add(Workspace.FIRST_SCREEN_ID); 146 } 147 return screenSet.getArray(); 148 } 149 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)150 public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, 151 String[] args) { 152 writer.println(prefix + "Data Model:"); 153 writer.println(prefix + " ---- workspace items "); 154 for (int i = 0; i < workspaceItems.size(); i++) { 155 writer.println(prefix + '\t' + workspaceItems.get(i).toString()); 156 } 157 writer.println(prefix + " ---- appwidget items "); 158 for (int i = 0; i < appWidgets.size(); i++) { 159 writer.println(prefix + '\t' + appWidgets.get(i).toString()); 160 } 161 writer.println(prefix + " ---- folder items "); 162 for (int i = 0; i< folders.size(); i++) { 163 writer.println(prefix + '\t' + folders.valueAt(i).toString()); 164 } 165 writer.println(prefix + " ---- items id map "); 166 for (int i = 0; i< itemsIdMap.size(); i++) { 167 writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); 168 } 169 170 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 171 writer.println(prefix + "shortcut counts "); 172 for (Integer count : deepShortcutMap.values()) { 173 writer.print(count + ", "); 174 } 175 writer.println(); 176 } 177 } 178 removeItem(Context context, ItemInfo... items)179 public synchronized void removeItem(Context context, ItemInfo... items) { 180 removeItem(context, Arrays.asList(items)); 181 } 182 removeItem(Context context, Iterable<? extends ItemInfo> items)183 public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) { 184 for (ItemInfo item : items) { 185 switch (item.itemType) { 186 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 187 folders.remove(item.id); 188 if (FeatureFlags.IS_STUDIO_BUILD) { 189 for (ItemInfo info : itemsIdMap) { 190 if (info.container == item.id) { 191 // We are deleting a folder which still contains items that 192 // think they are contained by that folder. 193 String msg = "deleting a folder (" + item + ") which still " + 194 "contains items (" + info + ")"; 195 Log.e(TAG, msg); 196 } 197 } 198 } 199 workspaceItems.remove(item); 200 break; 201 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 202 // Decrement pinned shortcut count 203 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); 204 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); 205 if ((count == null || --count.value == 0) 206 && !InstallShortcutReceiver.getPendingShortcuts(context) 207 .contains(pinnedShortcut)) { 208 unpinShortcut(context, pinnedShortcut); 209 } 210 // Fall through. 211 } 212 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 213 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 214 workspaceItems.remove(item); 215 break; 216 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 217 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 218 appWidgets.remove(item); 219 break; 220 } 221 itemsIdMap.remove(item.id); 222 } 223 } 224 addItem(Context context, ItemInfo item, boolean newItem)225 public synchronized void addItem(Context context, ItemInfo item, boolean newItem) { 226 itemsIdMap.put(item.id, item); 227 switch (item.itemType) { 228 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 229 folders.put(item.id, (FolderInfo) item); 230 workspaceItems.add(item); 231 break; 232 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 233 // Increment the count for the given shortcut 234 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); 235 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); 236 if (count == null) { 237 count = new MutableInt(1); 238 pinnedShortcutCounts.put(pinnedShortcut, count); 239 } else { 240 count.value++; 241 } 242 243 // Since this is a new item, pin the shortcut in the system server. 244 if (newItem && count.value == 1) { 245 updatePinnedShortcuts(context, pinnedShortcut, List::add); 246 } 247 // Fall through 248 } 249 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 250 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 251 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 252 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 253 workspaceItems.add(item); 254 } else { 255 if (newItem) { 256 if (!folders.containsKey(item.container)) { 257 // Adding an item to a folder that doesn't exist. 258 String msg = "adding item: " + item + " to a folder that " + 259 " doesn't exist"; 260 Log.e(TAG, msg); 261 } 262 } else { 263 findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false); 264 } 265 266 } 267 break; 268 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 269 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 270 appWidgets.add((LauncherAppWidgetInfo) item); 271 break; 272 } 273 } 274 275 /** 276 * Removes the given shortcut from the current list of pinned shortcuts. 277 * (Runs on background thread) 278 */ unpinShortcut(Context context, ShortcutKey key)279 public void unpinShortcut(Context context, ShortcutKey key) { 280 updatePinnedShortcuts(context, key, List::remove); 281 } 282 updatePinnedShortcuts(Context context, ShortcutKey key, BiConsumer<List<String>, String> idOp)283 private void updatePinnedShortcuts(Context context, ShortcutKey key, 284 BiConsumer<List<String>, String> idOp) { 285 if (GO_DISABLE_WIDGETS) { 286 return; 287 } 288 String packageName = key.componentName.getPackageName(); 289 String id = key.getId(); 290 UserHandle user = key.user; 291 List<String> pinnedIds = new ShortcutRequest(context, user) 292 .forPackage(packageName) 293 .query(PINNED) 294 .stream() 295 .map(ShortcutInfo::getId) 296 .collect(Collectors.toCollection(ArrayList::new)); 297 idOp.accept(pinnedIds, id); 298 try { 299 context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user); 300 } catch (SecurityException | IllegalStateException e) { 301 Log.w(TAG, "Failed to pin shortcut", e); 302 } 303 } 304 305 /** 306 * Return an existing FolderInfo object if we have encountered this ID previously, 307 * or make a new one. 308 */ findOrMakeFolder(int id)309 public synchronized FolderInfo findOrMakeFolder(int id) { 310 // See if a placeholder was created for us already 311 FolderInfo folderInfo = folders.get(id); 312 if (folderInfo == null) { 313 // No placeholder -- create a new instance 314 folderInfo = new FolderInfo(); 315 folders.put(id, folderInfo); 316 } 317 return folderInfo; 318 } 319 320 /** 321 * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts. 322 */ updateDeepShortcutCounts( String packageName, UserHandle user, List<ShortcutInfo> shortcuts)323 public synchronized void updateDeepShortcutCounts( 324 String packageName, UserHandle user, List<ShortcutInfo> shortcuts) { 325 if (packageName != null) { 326 Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator(); 327 while (keysIter.hasNext()) { 328 ComponentKey next = keysIter.next(); 329 if (next.componentName.getPackageName().equals(packageName) 330 && next.user.equals(user)) { 331 keysIter.remove(); 332 } 333 } 334 } 335 336 // Now add the new shortcuts to the map. 337 for (ShortcutInfo shortcut : shortcuts) { 338 boolean shouldShowInContainer = shortcut.isEnabled() 339 && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()) 340 && shortcut.getActivity() != null; 341 if (shouldShowInContainer) { 342 ComponentKey targetComponent 343 = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); 344 345 Integer previousCount = deepShortcutMap.get(targetComponent); 346 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1); 347 } 348 } 349 } 350 351 public interface Callbacks { 352 // If the launcher has permission to access deep shortcuts. 353 int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0; 354 // If quiet mode is enabled for any user 355 int FLAG_QUIET_MODE_ENABLED = 1 << 1; 356 // If launcher can change quiet mode 357 int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2; 358 359 /** 360 * Returns the page number to bind first, synchronously if possible or -1 361 */ getPageToBindSynchronously()362 int getPageToBindSynchronously(); clearPendingBinds()363 void clearPendingBinds(); startBinding()364 void startBinding(); bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)365 void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); bindScreens(IntArray orderedScreenIds)366 void bindScreens(IntArray orderedScreenIds); finishFirstPageBind(ViewOnDrawExecutor executor)367 void finishFirstPageBind(ViewOnDrawExecutor executor); finishBindingItems(int pageBoundFirst)368 void finishBindingItems(int pageBoundFirst); preAddApps()369 void preAddApps(); bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)370 void bindAppsAdded(IntArray newScreens, 371 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); bindPromiseAppProgressUpdated(PromiseAppInfo app)372 void bindPromiseAppProgressUpdated(PromiseAppInfo app); bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated)373 void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated); bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)374 void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); bindRestoreItemsChange(HashSet<ItemInfo> updates)375 void bindRestoreItemsChange(HashSet<ItemInfo> updates); bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)376 void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); bindAllWidgets(ArrayList<WidgetListRowEntry> widgets)377 void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); onPageBoundSynchronously(int page)378 void onPageBoundSynchronously(int page); executeOnNextDraw(ViewOnDrawExecutor executor)379 void executeOnNextDraw(ViewOnDrawExecutor executor); bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)380 void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap); 381 bindAllApplications(AppInfo[] apps, int flags)382 void bindAllApplications(AppInfo[] apps, int flags); 383 384 /** 385 * Binds predicted appInfos at at available prediction slots. 386 */ bindPredictedItems(List<AppInfo> appInfos, IntArray ranks)387 void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks); 388 } 389 } 390