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