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 * Id when the model was last bound 110 */ 111 public int lastBindId = 0; 112 113 /** 114 * Clears all the data 115 */ 116 public synchronized void clear() { 117 workspaceItems.clear(); 118 appWidgets.clear(); 119 folders.clear(); 120 itemsIdMap.clear(); 121 workspaceScreens.clear(); 122 pinnedShortcutCounts.clear(); 123 deepShortcutMap.clear(); 124 } 125 126 public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, 127 String[] args) { 128 if (Arrays.asList(args).contains("--proto")) { 129 dumpProto(prefix, fd, writer, args); 130 return; 131 } 132 writer.println(prefix + "Data Model:"); 133 writer.print(prefix + " ---- workspace screens: "); 134 for (int i = 0; i < workspaceScreens.size(); i++) { 135 writer.print(" " + workspaceScreens.get(i).toString()); 136 } 137 writer.println(); 138 writer.println(prefix + " ---- workspace items "); 139 for (int i = 0; i < workspaceItems.size(); i++) { 140 writer.println(prefix + '\t' + workspaceItems.get(i).toString()); 141 } 142 writer.println(prefix + " ---- appwidget items "); 143 for (int i = 0; i < appWidgets.size(); i++) { 144 writer.println(prefix + '\t' + appWidgets.get(i).toString()); 145 } 146 writer.println(prefix + " ---- folder items "); 147 for (int i = 0; i< folders.size(); i++) { 148 writer.println(prefix + '\t' + folders.valueAt(i).toString()); 149 } 150 writer.println(prefix + " ---- items id map "); 151 for (int i = 0; i< itemsIdMap.size(); i++) { 152 writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); 153 } 154 155 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 156 writer.println(prefix + "shortcuts"); 157 for (ArrayList<String> map : deepShortcutMap.values()) { 158 writer.print(prefix + " "); 159 for (String str : map) { 160 writer.print(str + ", "); 161 } 162 writer.println(); 163 } 164 } 165 } 166 167 private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer, 168 String[] args) { 169 170 // Add top parent nodes. (L1) 171 DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0); 172 LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>(); 173 for (int i = 0; i < workspaceScreens.size(); i++) { 174 workspaces.put(workspaceScreens.get(i), 175 new DumpTargetWrapper(ContainerType.WORKSPACE, i)); 176 } 177 DumpTargetWrapper dtw; 178 // Add non leaf / non top nodes (L2) 179 for (int i = 0; i < folders.size(); i++) { 180 FolderInfo fInfo = folders.valueAt(i); 181 dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size()); 182 dtw.writeToDumpTarget(fInfo); 183 for(ShortcutInfo sInfo: fInfo.contents) { 184 DumpTargetWrapper child = new DumpTargetWrapper(sInfo); 185 child.writeToDumpTarget(sInfo); 186 dtw.add(child); 187 } 188 if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 189 hotseat.add(dtw); 190 } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 191 workspaces.get(fInfo.screenId).add(dtw); 192 } 193 } 194 // Add leaf nodes (L3): *Info 195 for (int i = 0; i < workspaceItems.size(); i++) { 196 ItemInfo info = workspaceItems.get(i); 197 if (info instanceof FolderInfo) { 198 continue; 199 } 200 dtw = new DumpTargetWrapper(info); 201 dtw.writeToDumpTarget(info); 202 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 203 hotseat.add(dtw); 204 } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 205 workspaces.get(info.screenId).add(dtw); 206 } 207 } 208 for (int i = 0; i < appWidgets.size(); i++) { 209 ItemInfo info = appWidgets.get(i); 210 dtw = new DumpTargetWrapper(info); 211 dtw.writeToDumpTarget(info); 212 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 213 hotseat.add(dtw); 214 } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 215 workspaces.get(info.screenId).add(dtw); 216 } 217 } 218 219 220 // Traverse target wrapper 221 ArrayList<DumpTarget> targetList = new ArrayList<>(); 222 targetList.addAll(hotseat.getFlattenedList()); 223 for (int i = 0; i < workspaces.size(); i++) { 224 targetList.addAll(workspaces.valueAt(i).getFlattenedList()); 225 } 226 227 if (Arrays.asList(args).contains("--debug")) { 228 for (int i = 0; i < targetList.size(); i++) { 229 writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i))); 230 } 231 return; 232 } else { 233 LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression(); 234 proto.targets = new DumpTarget[targetList.size()]; 235 for (int i = 0; i < targetList.size(); i++) { 236 proto.targets[i] = targetList.get(i); 237 } 238 FileOutputStream fos = new FileOutputStream(fd); 239 try { 240 241 fos.write(MessageNano.toByteArray(proto)); 242 Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes"); 243 } catch (IOException e) { 244 Log.e(TAG, "Exception writing dumpsys --proto", e); 245 } 246 } 247 } 248 249 public synchronized void removeItem(Context context, ItemInfo... items) { 250 removeItem(context, Arrays.asList(items)); 251 } 252 253 public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) { 254 for (ItemInfo item : items) { 255 switch (item.itemType) { 256 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 257 folders.remove(item.id); 258 if (FeatureFlags.IS_DOGFOOD_BUILD) { 259 for (ItemInfo info : itemsIdMap) { 260 if (info.container == item.id) { 261 // We are deleting a folder which still contains items that 262 // think they are contained by that folder. 263 String msg = "deleting a folder (" + item + ") which still " + 264 "contains items (" + info + ")"; 265 Log.e(TAG, msg); 266 } 267 } 268 } 269 workspaceItems.remove(item); 270 break; 271 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 272 // Decrement pinned shortcut count 273 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); 274 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); 275 if ((count == null || --count.value == 0) 276 && !InstallShortcutReceiver.getPendingShortcuts(context) 277 .contains(pinnedShortcut)) { 278 DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut); 279 } 280 // Fall through. 281 } 282 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 283 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 284 workspaceItems.remove(item); 285 break; 286 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 287 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 288 appWidgets.remove(item); 289 break; 290 } 291 itemsIdMap.remove(item.id); 292 } 293 } 294 295 public synchronized void addItem(Context context, ItemInfo item, boolean newItem) { 296 itemsIdMap.put(item.id, item); 297 switch (item.itemType) { 298 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 299 folders.put(item.id, (FolderInfo) item); 300 workspaceItems.add(item); 301 break; 302 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 303 // Increment the count for the given shortcut 304 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item); 305 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut); 306 if (count == null) { 307 count = new MutableInt(1); 308 pinnedShortcutCounts.put(pinnedShortcut, count); 309 } else { 310 count.value++; 311 } 312 313 // Since this is a new item, pin the shortcut in the system server. 314 if (newItem && count.value == 1) { 315 DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut); 316 } 317 // Fall through 318 } 319 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 320 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 321 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 322 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 323 workspaceItems.add(item); 324 } else { 325 if (newItem) { 326 if (!folders.containsKey(item.container)) { 327 // Adding an item to a folder that doesn't exist. 328 String msg = "adding item: " + item + " to a folder that " + 329 " doesn't exist"; 330 Log.e(TAG, msg); 331 } 332 } else { 333 findOrMakeFolder(item.container).add((ShortcutInfo) item, false); 334 } 335 336 } 337 break; 338 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 339 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 340 appWidgets.add((LauncherAppWidgetInfo) item); 341 break; 342 } 343 } 344 345 /** 346 * Return an existing FolderInfo object if we have encountered this ID previously, 347 * or make a new one. 348 */ 349 public synchronized FolderInfo findOrMakeFolder(long id) { 350 // See if a placeholder was created for us already 351 FolderInfo folderInfo = folders.get(id); 352 if (folderInfo == null) { 353 // No placeholder -- create a new instance 354 folderInfo = new FolderInfo(); 355 folders.put(id, folderInfo); 356 } 357 return folderInfo; 358 } 359 360 /** 361 * Clear all the deep shortcuts for the given package, and re-add the new shortcuts. 362 */ 363 public synchronized void updateDeepShortcutMap( 364 String packageName, UserHandle user, List<ShortcutInfoCompat> shortcuts) { 365 if (packageName != null) { 366 Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator(); 367 while (keysIter.hasNext()) { 368 ComponentKey next = keysIter.next(); 369 if (next.componentName.getPackageName().equals(packageName) 370 && next.user.equals(user)) { 371 keysIter.remove(); 372 } 373 } 374 } 375 376 // Now add the new shortcuts to the map. 377 for (ShortcutInfoCompat shortcut : shortcuts) { 378 boolean shouldShowInContainer = shortcut.isEnabled() 379 && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()); 380 if (shouldShowInContainer) { 381 ComponentKey targetComponent 382 = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); 383 deepShortcutMap.addToList(targetComponent, shortcut.getId()); 384 } 385 } 386 } 387 } 388