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 android.content.Context; 19 import android.content.pm.ShortcutInfo; 20 import android.os.UserHandle; 21 import android.text.TextUtils; 22 import android.util.Log; 23 import android.util.MutableInt; 24 25 import com.android.launcher3.FolderInfo; 26 import com.android.launcher3.InstallShortcutReceiver; 27 import com.android.launcher3.ItemInfo; 28 import com.android.launcher3.LauncherAppWidgetInfo; 29 import com.android.launcher3.LauncherSettings; 30 import com.android.launcher3.WorkspaceItemInfo; 31 import com.android.launcher3.Workspace; 32 import com.android.launcher3.config.FeatureFlags; 33 import com.android.launcher3.logging.DumpTargetWrapper; 34 import com.android.launcher3.model.nano.LauncherDumpProto; 35 import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType; 36 import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget; 37 import com.android.launcher3.shortcuts.DeepShortcutManager; 38 import com.android.launcher3.shortcuts.ShortcutKey; 39 import com.android.launcher3.util.ComponentKey; 40 import com.android.launcher3.util.IntArray; 41 import com.android.launcher3.util.IntSet; 42 import com.android.launcher3.util.IntSparseArrayMap; 43 import com.google.protobuf.nano.MessageNano; 44 45 import java.io.FileDescriptor; 46 import java.io.FileOutputStream; 47 import java.io.IOException; 48 import java.io.PrintWriter; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.HashMap; 52 import java.util.Iterator; 53 import java.util.List; 54 import java.util.Map; 55 56 /** 57 * All the data stored in-memory and managed by the LauncherModel 58 */ 59 public class BgDataModel { 60 61 private static final String TAG = "BgDataModel"; 62 63 /** 64 * Map of all the ItemInfos (shortcuts, folders, and widgets) created by 65 * LauncherModel to their ids 66 */ 67 public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>(); 68 69 /** 70 * List of all the folders and shortcuts directly on the home screen (no widgets 71 * or shortcuts within folders). 72 */ 73 public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 74 75 /** 76 * All LauncherAppWidgetInfo created by LauncherModel. 77 */ 78 public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 79 80 /** 81 * Map of id to FolderInfos of all the folders created by LauncherModel 82 */ 83 public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>(); 84 85 /** 86 * Map of ShortcutKey to the number of times it is pinned. 87 */ 88 public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>(); 89 90 /** 91 * True if the launcher has permission to access deep shortcuts. 92 */ 93 public boolean hasShortcutHostPermission; 94 95 /** 96 * Maps all launcher activities to counts of their shortcuts. 97 */ 98 public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>(); 99 100 /** 101 * Entire list of widgets. 102 */ 103 public final WidgetsModel widgetsModel = new WidgetsModel(); 104 105 /** 106 * Id when the model was last bound 107 */ 108 public int lastBindId = 0; 109 110 /** 111 * Clears all the data 112 */ clear()113 public synchronized void clear() { 114 workspaceItems.clear(); 115 appWidgets.clear(); 116 folders.clear(); 117 itemsIdMap.clear(); 118 pinnedShortcutCounts.clear(); 119 deepShortcutMap.clear(); 120 } 121 122 /** 123 * Creates an array of valid workspace screens based on current items in the model. 124 */ collectWorkspaceScreens()125 public synchronized IntArray collectWorkspaceScreens() { 126 IntSet screenSet = new IntSet(); 127 for (ItemInfo item: itemsIdMap) { 128 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 129 screenSet.add(item.screenId); 130 } 131 } 132 if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) { 133 screenSet.add(Workspace.FIRST_SCREEN_ID); 134 } 135 return screenSet.getArray(); 136 } 137 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)138 public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, 139 String[] args) { 140 if (Arrays.asList(args).contains("--proto")) { 141 dumpProto(prefix, fd, writer, args); 142 return; 143 } 144 writer.println(prefix + "Data Model:"); 145 writer.println(prefix + " ---- workspace items "); 146 for (int i = 0; i < workspaceItems.size(); i++) { 147 writer.println(prefix + '\t' + workspaceItems.get(i).toString()); 148 } 149 writer.println(prefix + " ---- appwidget items "); 150 for (int i = 0; i < appWidgets.size(); i++) { 151 writer.println(prefix + '\t' + appWidgets.get(i).toString()); 152 } 153 writer.println(prefix + " ---- folder items "); 154 for (int i = 0; i< folders.size(); i++) { 155 writer.println(prefix + '\t' + folders.valueAt(i).toString()); 156 } 157 writer.println(prefix + " ---- items id map "); 158 for (int i = 0; i< itemsIdMap.size(); i++) { 159 writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); 160 } 161 162 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 163 writer.println(prefix + "shortcut counts "); 164 for (Integer count : deepShortcutMap.values()) { 165 writer.print(count + ", "); 166 } 167 writer.println(); 168 } 169 } 170 dumpProto(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)171 private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer, 172 String[] args) { 173 174 // Add top parent nodes. (L1) 175 DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0); 176 IntSparseArrayMap<DumpTargetWrapper> workspaces = new IntSparseArrayMap<>(); 177 IntArray workspaceScreens = collectWorkspaceScreens(); 178 for (int i = 0; i < workspaceScreens.size(); i++) { 179 workspaces.put(workspaceScreens.get(i), 180 new DumpTargetWrapper(ContainerType.WORKSPACE, i)); 181 } 182 DumpTargetWrapper dtw; 183 // Add non leaf / non top nodes (L2) 184 for (int i = 0; i < folders.size(); i++) { 185 FolderInfo fInfo = folders.valueAt(i); 186 dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size()); 187 dtw.writeToDumpTarget(fInfo); 188 for(WorkspaceItemInfo sInfo: fInfo.contents) { 189 DumpTargetWrapper child = new DumpTargetWrapper(sInfo); 190 child.writeToDumpTarget(sInfo); 191 dtw.add(child); 192 } 193 if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 194 hotseat.add(dtw); 195 } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 196 workspaces.get(fInfo.screenId).add(dtw); 197 } 198 } 199 // Add leaf nodes (L3): *Info 200 for (int i = 0; i < workspaceItems.size(); i++) { 201 ItemInfo info = workspaceItems.get(i); 202 if (info instanceof FolderInfo) { 203 continue; 204 } 205 dtw = new DumpTargetWrapper(info); 206 dtw.writeToDumpTarget(info); 207 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 208 hotseat.add(dtw); 209 } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 210 workspaces.get(info.screenId).add(dtw); 211 } 212 } 213 for (int i = 0; i < appWidgets.size(); i++) { 214 ItemInfo info = appWidgets.get(i); 215 dtw = new DumpTargetWrapper(info); 216 dtw.writeToDumpTarget(info); 217 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 218 hotseat.add(dtw); 219 } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 220 workspaces.get(info.screenId).add(dtw); 221 } 222 } 223 224 225 // Traverse target wrapper 226 ArrayList<DumpTarget> targetList = new ArrayList<>(); 227 targetList.addAll(hotseat.getFlattenedList()); 228 for (int i = 0; i < workspaces.size(); i++) { 229 targetList.addAll(workspaces.valueAt(i).getFlattenedList()); 230 } 231 232 if (Arrays.asList(args).contains("--debug")) { 233 for (int i = 0; i < targetList.size(); i++) { 234 writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i))); 235 } 236 return; 237 } else { 238 LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression(); 239 proto.targets = new DumpTarget[targetList.size()]; 240 for (int i = 0; i < targetList.size(); i++) { 241 proto.targets[i] = targetList.get(i); 242 } 243 FileOutputStream fos = new FileOutputStream(fd); 244 try { 245 246 fos.write(MessageNano.toByteArray(proto)); 247 Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes"); 248 } catch (IOException e) { 249 Log.e(TAG, "Exception writing dumpsys --proto", e); 250 } 251 } 252 } 253 removeItem(Context context, ItemInfo... items)254 public synchronized void removeItem(Context context, ItemInfo... items) { 255 removeItem(context, Arrays.asList(items)); 256 } 257 removeItem(Context context, Iterable<? extends ItemInfo> items)258 public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) { 259 for (ItemInfo item : items) { 260 switch (item.itemType) { 261 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 262 folders.remove(item.id); 263 if (FeatureFlags.IS_DOGFOOD_BUILD) { 264 for (ItemInfo info : itemsIdMap) { 265 if (info.container == item.id) { 266 // We are deleting a folder which still contains items that 267 // think they are contained by that folder. 268 String msg = "deleting a folder (" + item + ") which still " + 269 "contains items (" + info + ")"; 270 Log.e(TAG, msg); 271 } 272 } 273 } 274 workspaceItems.remove(item); 275 break; 276 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 277 // Decrement pinned shortcut count 278 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); 279 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); 280 if ((count == null || --count.value == 0) 281 && !InstallShortcutReceiver.getPendingShortcuts(context) 282 .contains(pinnedShortcut)) { 283 DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut); 284 } 285 // Fall through. 286 } 287 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 288 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 289 workspaceItems.remove(item); 290 break; 291 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 292 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 293 appWidgets.remove(item); 294 break; 295 } 296 itemsIdMap.remove(item.id); 297 } 298 } 299 addItem(Context context, ItemInfo item, boolean newItem)300 public synchronized void addItem(Context context, ItemInfo item, boolean newItem) { 301 itemsIdMap.put(item.id, item); 302 switch (item.itemType) { 303 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 304 folders.put(item.id, (FolderInfo) item); 305 workspaceItems.add(item); 306 break; 307 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 308 // Increment the count for the given shortcut 309 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); 310 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); 311 if (count == null) { 312 count = new MutableInt(1); 313 pinnedShortcutCounts.put(pinnedShortcut, count); 314 } else { 315 count.value++; 316 } 317 318 // Since this is a new item, pin the shortcut in the system server. 319 if (newItem && count.value == 1) { 320 DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut); 321 } 322 // Fall through 323 } 324 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 325 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 326 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 327 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 328 workspaceItems.add(item); 329 } else { 330 if (newItem) { 331 if (!folders.containsKey(item.container)) { 332 // Adding an item to a folder that doesn't exist. 333 String msg = "adding item: " + item + " to a folder that " + 334 " doesn't exist"; 335 Log.e(TAG, msg); 336 } 337 } else { 338 findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false); 339 } 340 341 } 342 break; 343 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 344 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 345 appWidgets.add((LauncherAppWidgetInfo) item); 346 break; 347 } 348 } 349 350 /** 351 * Return an existing FolderInfo object if we have encountered this ID previously, 352 * or make a new one. 353 */ findOrMakeFolder(int id)354 public synchronized FolderInfo findOrMakeFolder(int id) { 355 // See if a placeholder was created for us already 356 FolderInfo folderInfo = folders.get(id); 357 if (folderInfo == null) { 358 // No placeholder -- create a new instance 359 folderInfo = new FolderInfo(); 360 folders.put(id, folderInfo); 361 } 362 return folderInfo; 363 } 364 365 /** 366 * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts. 367 */ updateDeepShortcutCounts( String packageName, UserHandle user, List<ShortcutInfo> shortcuts)368 public synchronized void updateDeepShortcutCounts( 369 String packageName, UserHandle user, List<ShortcutInfo> shortcuts) { 370 if (packageName != null) { 371 Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator(); 372 while (keysIter.hasNext()) { 373 ComponentKey next = keysIter.next(); 374 if (next.componentName.getPackageName().equals(packageName) 375 && next.user.equals(user)) { 376 keysIter.remove(); 377 } 378 } 379 } 380 381 // Now add the new shortcuts to the map. 382 for (ShortcutInfo shortcut : shortcuts) { 383 boolean shouldShowInContainer = shortcut.isEnabled() 384 && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()); 385 if (shouldShowInContainer) { 386 ComponentKey targetComponent 387 = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); 388 389 Integer previousCount = deepShortcutMap.get(targetComponent); 390 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1); 391 } 392 } 393 } 394 } 395