• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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