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 static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
19 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
20 
21 import android.content.Context;
22 import android.content.pm.LauncherApps;
23 import android.content.pm.ShortcutInfo;
24 import android.os.UserHandle;
25 import android.text.TextUtils;
26 import android.util.Log;
27 import android.util.MutableInt;
28 
29 import com.android.launcher3.InstallShortcutReceiver;
30 import com.android.launcher3.LauncherSettings;
31 import com.android.launcher3.Workspace;
32 import com.android.launcher3.config.FeatureFlags;
33 import com.android.launcher3.model.data.AppInfo;
34 import com.android.launcher3.model.data.FolderInfo;
35 import com.android.launcher3.model.data.ItemInfo;
36 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
37 import com.android.launcher3.model.data.PromiseAppInfo;
38 import com.android.launcher3.model.data.WorkspaceItemInfo;
39 import com.android.launcher3.shortcuts.ShortcutKey;
40 import com.android.launcher3.shortcuts.ShortcutRequest;
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 java.io.FileDescriptor;
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.function.BiConsumer;
59 import java.util.stream.Collectors;
60 
61 /**
62  * All the data stored in-memory and managed by the LauncherModel
63  */
64 public class BgDataModel {
65 
66     private static final String TAG = "BgDataModel";
67 
68     /**
69      * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
70      * LauncherModel to their ids
71      */
72     public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
73 
74     /**
75      * List of all the folders and shortcuts directly on the home screen (no widgets
76      * or shortcuts within folders).
77      */
78     public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
79 
80     /**
81      * All LauncherAppWidgetInfo created by LauncherModel.
82      */
83     public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
84 
85     /**
86      * Map of id to FolderInfos of all the folders created by LauncherModel
87      */
88     public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
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      * List of all cached predicted items visible on home screen
97      */
98     public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
99 
100     /**
101      * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
102      * @see Callbacks#FLAG_QUIET_MODE_ENABLED
103      * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
104      */
105     public int flags;
106 
107     /**
108      * Maps all launcher activities to counts of their shortcuts.
109      */
110     public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>();
111 
112     /**
113      * Entire list of widgets.
114      */
115     public final WidgetsModel widgetsModel = new WidgetsModel();
116 
117     /**
118      * Id when the model was last bound
119      */
120     public int lastBindId = 0;
121 
122     /**
123      * Clears all the data
124      */
clear()125     public synchronized void clear() {
126         workspaceItems.clear();
127         appWidgets.clear();
128         folders.clear();
129         itemsIdMap.clear();
130         pinnedShortcutCounts.clear();
131         deepShortcutMap.clear();
132     }
133 
134     /**
135      * Creates an array of valid workspace screens based on current items in the model.
136      */
collectWorkspaceScreens()137     public synchronized IntArray collectWorkspaceScreens() {
138         IntSet screenSet = new IntSet();
139         for (ItemInfo item: itemsIdMap) {
140             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
141                 screenSet.add(item.screenId);
142             }
143         }
144         if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
145             screenSet.add(Workspace.FIRST_SCREEN_ID);
146         }
147         return screenSet.getArray();
148     }
149 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)150     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
151             String[] args) {
152         writer.println(prefix + "Data Model:");
153         writer.println(prefix + " ---- workspace items ");
154         for (int i = 0; i < workspaceItems.size(); i++) {
155             writer.println(prefix + '\t' + workspaceItems.get(i).toString());
156         }
157         writer.println(prefix + " ---- appwidget items ");
158         for (int i = 0; i < appWidgets.size(); i++) {
159             writer.println(prefix + '\t' + appWidgets.get(i).toString());
160         }
161         writer.println(prefix + " ---- folder items ");
162         for (int i = 0; i< folders.size(); i++) {
163             writer.println(prefix + '\t' + folders.valueAt(i).toString());
164         }
165         writer.println(prefix + " ---- items id map ");
166         for (int i = 0; i< itemsIdMap.size(); i++) {
167             writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
168         }
169 
170         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
171             writer.println(prefix + "shortcut counts ");
172             for (Integer count : deepShortcutMap.values()) {
173                 writer.print(count + ", ");
174             }
175             writer.println();
176         }
177     }
178 
removeItem(Context context, ItemInfo... items)179     public synchronized void removeItem(Context context, ItemInfo... items) {
180         removeItem(context, Arrays.asList(items));
181     }
182 
removeItem(Context context, Iterable<? extends ItemInfo> items)183     public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
184         for (ItemInfo item : items) {
185             switch (item.itemType) {
186                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
187                     folders.remove(item.id);
188                     if (FeatureFlags.IS_STUDIO_BUILD) {
189                         for (ItemInfo info : itemsIdMap) {
190                             if (info.container == item.id) {
191                                 // We are deleting a folder which still contains items that
192                                 // think they are contained by that folder.
193                                 String msg = "deleting a folder (" + item + ") which still " +
194                                         "contains items (" + info + ")";
195                                 Log.e(TAG, msg);
196                             }
197                         }
198                     }
199                     workspaceItems.remove(item);
200                     break;
201                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
202                     // Decrement pinned shortcut count
203                     ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
204                     MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
205                     if ((count == null || --count.value == 0)
206                             && !InstallShortcutReceiver.getPendingShortcuts(context)
207                                 .contains(pinnedShortcut)) {
208                         unpinShortcut(context, pinnedShortcut);
209                     }
210                     // Fall through.
211                 }
212                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
213                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
214                     workspaceItems.remove(item);
215                     break;
216                 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
217                 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
218                     appWidgets.remove(item);
219                     break;
220             }
221             itemsIdMap.remove(item.id);
222         }
223     }
224 
addItem(Context context, ItemInfo item, boolean newItem)225     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
226         itemsIdMap.put(item.id, item);
227         switch (item.itemType) {
228             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
229                 folders.put(item.id, (FolderInfo) item);
230                 workspaceItems.add(item);
231                 break;
232             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
233                 // Increment the count for the given shortcut
234                 ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
235                 MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
236                 if (count == null) {
237                     count = new MutableInt(1);
238                     pinnedShortcutCounts.put(pinnedShortcut, count);
239                 } else {
240                     count.value++;
241                 }
242 
243                 // Since this is a new item, pin the shortcut in the system server.
244                 if (newItem && count.value == 1) {
245                     updatePinnedShortcuts(context, pinnedShortcut, List::add);
246                 }
247                 // Fall through
248             }
249             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
250             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
251                 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
252                         item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
253                     workspaceItems.add(item);
254                 } else {
255                     if (newItem) {
256                         if (!folders.containsKey(item.container)) {
257                             // Adding an item to a folder that doesn't exist.
258                             String msg = "adding item: " + item + " to a folder that " +
259                                     " doesn't exist";
260                             Log.e(TAG, msg);
261                         }
262                     } else {
263                         findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false);
264                     }
265 
266                 }
267                 break;
268             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
269             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
270                 appWidgets.add((LauncherAppWidgetInfo) item);
271                 break;
272         }
273     }
274 
275     /**
276      * Removes the given shortcut from the current list of pinned shortcuts.
277      * (Runs on background thread)
278      */
unpinShortcut(Context context, ShortcutKey key)279     public void unpinShortcut(Context context, ShortcutKey key) {
280         updatePinnedShortcuts(context, key, List::remove);
281     }
282 
updatePinnedShortcuts(Context context, ShortcutKey key, BiConsumer<List<String>, String> idOp)283     private void updatePinnedShortcuts(Context context, ShortcutKey key,
284             BiConsumer<List<String>, String> idOp) {
285         if (GO_DISABLE_WIDGETS) {
286             return;
287         }
288         String packageName = key.componentName.getPackageName();
289         String id = key.getId();
290         UserHandle user = key.user;
291         List<String> pinnedIds = new ShortcutRequest(context, user)
292                 .forPackage(packageName)
293                 .query(PINNED)
294                 .stream()
295                 .map(ShortcutInfo::getId)
296                 .collect(Collectors.toCollection(ArrayList::new));
297         idOp.accept(pinnedIds, id);
298         try {
299             context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
300         } catch (SecurityException | IllegalStateException e) {
301             Log.w(TAG, "Failed to pin shortcut", e);
302         }
303     }
304 
305     /**
306      * Return an existing FolderInfo object if we have encountered this ID previously,
307      * or make a new one.
308      */
findOrMakeFolder(int id)309     public synchronized FolderInfo findOrMakeFolder(int id) {
310         // See if a placeholder was created for us already
311         FolderInfo folderInfo = folders.get(id);
312         if (folderInfo == null) {
313             // No placeholder -- create a new instance
314             folderInfo = new FolderInfo();
315             folders.put(id, folderInfo);
316         }
317         return folderInfo;
318     }
319 
320     /**
321      * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
322      */
updateDeepShortcutCounts( String packageName, UserHandle user, List<ShortcutInfo> shortcuts)323     public synchronized void updateDeepShortcutCounts(
324             String packageName, UserHandle user, List<ShortcutInfo> shortcuts) {
325         if (packageName != null) {
326             Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
327             while (keysIter.hasNext()) {
328                 ComponentKey next = keysIter.next();
329                 if (next.componentName.getPackageName().equals(packageName)
330                         && next.user.equals(user)) {
331                     keysIter.remove();
332                 }
333             }
334         }
335 
336         // Now add the new shortcuts to the map.
337         for (ShortcutInfo shortcut : shortcuts) {
338             boolean shouldShowInContainer = shortcut.isEnabled()
339                     && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
340                     && shortcut.getActivity() != null;
341             if (shouldShowInContainer) {
342                 ComponentKey targetComponent
343                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
344 
345                 Integer previousCount = deepShortcutMap.get(targetComponent);
346                 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
347             }
348         }
349     }
350 
351     public interface Callbacks {
352         // If the launcher has permission to access deep shortcuts.
353         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
354         // If quiet mode is enabled for any user
355         int FLAG_QUIET_MODE_ENABLED = 1 << 1;
356         // If launcher can change quiet mode
357         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
358 
359         /**
360          * Returns the page number to bind first, synchronously if possible or -1
361          */
getPageToBindSynchronously()362         int getPageToBindSynchronously();
clearPendingBinds()363         void clearPendingBinds();
startBinding()364         void startBinding();
bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)365         void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
bindScreens(IntArray orderedScreenIds)366         void bindScreens(IntArray orderedScreenIds);
finishFirstPageBind(ViewOnDrawExecutor executor)367         void finishFirstPageBind(ViewOnDrawExecutor executor);
finishBindingItems(int pageBoundFirst)368         void finishBindingItems(int pageBoundFirst);
preAddApps()369         void preAddApps();
bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)370         void bindAppsAdded(IntArray newScreens,
371                 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
bindPromiseAppProgressUpdated(PromiseAppInfo app)372         void bindPromiseAppProgressUpdated(PromiseAppInfo app);
bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated)373         void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)374         void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
bindRestoreItemsChange(HashSet<ItemInfo> updates)375         void bindRestoreItemsChange(HashSet<ItemInfo> updates);
bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)376         void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
bindAllWidgets(ArrayList<WidgetListRowEntry> widgets)377         void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
onPageBoundSynchronously(int page)378         void onPageBoundSynchronously(int page);
executeOnNextDraw(ViewOnDrawExecutor executor)379         void executeOnNextDraw(ViewOnDrawExecutor executor);
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)380         void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
381 
bindAllApplications(AppInfo[] apps, int flags)382         void bindAllApplications(AppInfo[] apps, int flags);
383 
384         /**
385          * Binds predicted appInfos at at available prediction slots.
386          */
bindPredictedItems(List<AppInfo> appInfos, IntArray ranks)387         void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
388     }
389 }
390