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