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