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