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