• 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 static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
19 
20 import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
21 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
22 
23 import static java.util.stream.Collectors.groupingBy;
24 import static java.util.stream.Collectors.mapping;
25 
26 import android.content.Context;
27 import android.content.pm.LauncherApps;
28 import android.content.pm.ShortcutInfo;
29 import android.os.UserHandle;
30 import android.text.TextUtils;
31 import android.util.ArraySet;
32 import android.util.Log;
33 
34 import com.android.launcher3.LauncherSettings;
35 import com.android.launcher3.LauncherSettings.Favorites;
36 import com.android.launcher3.Workspace;
37 import com.android.launcher3.config.FeatureFlags;
38 import com.android.launcher3.model.data.AppInfo;
39 import com.android.launcher3.model.data.FolderInfo;
40 import com.android.launcher3.model.data.ItemInfo;
41 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
42 import com.android.launcher3.model.data.WorkspaceItemInfo;
43 import com.android.launcher3.pm.UserCache;
44 import com.android.launcher3.shortcuts.ShortcutKey;
45 import com.android.launcher3.shortcuts.ShortcutRequest;
46 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
47 import com.android.launcher3.util.ComponentKey;
48 import com.android.launcher3.util.IntArray;
49 import com.android.launcher3.util.IntSet;
50 import com.android.launcher3.util.IntSparseArrayMap;
51 import com.android.launcher3.util.ItemInfoMatcher;
52 import com.android.launcher3.util.ViewOnDrawExecutor;
53 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
54 
55 import java.io.FileDescriptor;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Set;
66 import java.util.function.Consumer;
67 import java.util.stream.Collectors;
68 import java.util.stream.Stream;
69 
70 /**
71  * All the data stored in-memory and managed by the LauncherModel
72  */
73 public class BgDataModel {
74 
75     private static final String TAG = "BgDataModel";
76 
77     /**
78      * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
79      * LauncherModel to their ids
80      */
81     public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
82 
83     /**
84      * List of all the folders and shortcuts directly on the home screen (no widgets
85      * or shortcuts within folders).
86      */
87     public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
88 
89     /**
90      * All LauncherAppWidgetInfo created by LauncherModel.
91      */
92     public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
93 
94     /**
95      * Map of id to FolderInfos of all the folders created by LauncherModel
96      */
97     public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
98 
99     /**
100      * Extra container based items
101      */
102     public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
103 
104     /**
105      * Maps all launcher activities to counts of their shortcuts.
106      */
107     public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>();
108 
109     /**
110      * Entire list of widgets.
111      */
112     public final WidgetsModel widgetsModel = new WidgetsModel();
113 
114     /**
115      * Id when the model was last bound
116      */
117     public int lastBindId = 0;
118 
119     /**
120      * Clears all the data
121      */
clear()122     public synchronized void clear() {
123         workspaceItems.clear();
124         appWidgets.clear();
125         folders.clear();
126         itemsIdMap.clear();
127         deepShortcutMap.clear();
128         extraItems.clear();
129     }
130 
131     /**
132      * Creates an array of valid workspace screens based on current items in the model.
133      */
collectWorkspaceScreens()134     public synchronized IntArray collectWorkspaceScreens() {
135         IntSet screenSet = new IntSet();
136         for (ItemInfo item: itemsIdMap) {
137             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
138                 screenSet.add(item.screenId);
139             }
140         }
141         if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
142             screenSet.add(Workspace.FIRST_SCREEN_ID);
143         }
144         return screenSet.getArray();
145     }
146 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)147     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
148             String[] args) {
149         writer.println(prefix + "Data Model:");
150         writer.println(prefix + " ---- workspace items ");
151         for (int i = 0; i < workspaceItems.size(); i++) {
152             writer.println(prefix + '\t' + workspaceItems.get(i).toString());
153         }
154         writer.println(prefix + " ---- appwidget items ");
155         for (int i = 0; i < appWidgets.size(); i++) {
156             writer.println(prefix + '\t' + appWidgets.get(i).toString());
157         }
158         writer.println(prefix + " ---- folder items ");
159         for (int i = 0; i< folders.size(); i++) {
160             writer.println(prefix + '\t' + folders.valueAt(i).toString());
161         }
162         writer.println(prefix + " ---- items id map ");
163         for (int i = 0; i< itemsIdMap.size(); i++) {
164             writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
165         }
166 
167         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
168             writer.println(prefix + "shortcut counts ");
169             for (Integer count : deepShortcutMap.values()) {
170                 writer.print(count + ", ");
171             }
172             writer.println();
173         }
174     }
175 
removeItem(Context context, ItemInfo... items)176     public synchronized void removeItem(Context context, ItemInfo... items) {
177         removeItem(context, Arrays.asList(items));
178     }
179 
removeItem(Context context, Iterable<? extends ItemInfo> items)180     public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
181         ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
182         for (ItemInfo item : items) {
183             switch (item.itemType) {
184                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
185                     folders.remove(item.id);
186                     if (FeatureFlags.IS_STUDIO_BUILD) {
187                         for (ItemInfo info : itemsIdMap) {
188                             if (info.container == item.id) {
189                                 // We are deleting a folder which still contains items that
190                                 // think they are contained by that folder.
191                                 String msg = "deleting a folder (" + item + ") which still " +
192                                         "contains items (" + info + ")";
193                                 Log.e(TAG, msg);
194                             }
195                         }
196                     }
197                     workspaceItems.remove(item);
198                     break;
199                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
200                     updatedDeepShortcuts.add(item.user);
201                     // Fall through.
202                 }
203                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
204                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
205                     workspaceItems.remove(item);
206                     break;
207                 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
208                 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
209                     appWidgets.remove(item);
210                     break;
211             }
212             itemsIdMap.remove(item.id);
213         }
214         updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
215     }
216 
addItem(Context context, ItemInfo item, boolean newItem)217     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
218         itemsIdMap.put(item.id, item);
219         switch (item.itemType) {
220             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
221                 folders.put(item.id, (FolderInfo) item);
222                 workspaceItems.add(item);
223                 break;
224             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
225             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
226             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
227                 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
228                         item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
229                     workspaceItems.add(item);
230                 } else {
231                     if (newItem) {
232                         if (!folders.containsKey(item.container)) {
233                             // Adding an item to a folder that doesn't exist.
234                             String msg = "adding item: " + item + " to a folder that " +
235                                     " doesn't exist";
236                             Log.e(TAG, msg);
237                         }
238                     } else {
239                         findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false);
240                     }
241 
242                 }
243                 break;
244             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
245             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
246                 appWidgets.add((LauncherAppWidgetInfo) item);
247                 break;
248         }
249         if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
250             updateShortcutPinnedState(context, item.user);
251         }
252     }
253 
254     /**
255      * Updates the deep shortucts state in system to match out internal model, pinning any missing
256      * shortcuts and unpinning any extra shortcuts.
257      */
updateShortcutPinnedState(Context context)258     public void updateShortcutPinnedState(Context context) {
259         for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
260             updateShortcutPinnedState(context, user);
261         }
262     }
263 
264     /**
265      * Updates the deep shortucts state in system to match out internal model, pinning any missing
266      * shortcuts and unpinning any extra shortcuts.
267      */
updateShortcutPinnedState(Context context, UserHandle user)268     public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
269         if (GO_DISABLE_WIDGETS) {
270             return;
271         }
272 
273         // Collect all system shortcuts
274         QueryResult result = new ShortcutRequest(context, user)
275                 .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY);
276         if (!result.wasSuccess()) {
277             return;
278         }
279         // Map of packageName to shortcutIds that are currently in the system
280         Map<String, Set<String>> systemMap = result.stream()
281                 .collect(groupingBy(ShortcutInfo::getPackage,
282                         mapping(ShortcutInfo::getId, Collectors.toSet())));
283 
284         // Collect all model shortcuts
285         Stream.Builder<WorkspaceItemInfo> itemStream = Stream.builder();
286         forAllWorkspaceItemInfos(user, itemStream::accept);
287         // Map of packageName to shortcutIds that are currently in our model
288         Map<String, Set<String>> modelMap = Stream.concat(
289                     // Model shortcuts
290                     itemStream.build()
291                         .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
292                         .map(ShortcutKey::fromItemInfo),
293                     // Pending shortcuts
294                     ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
295                 .collect(groupingBy(ShortcutKey::getPackageName,
296                         mapping(ShortcutKey::getId, Collectors.toSet())));
297 
298         // Check for diff
299         for (Map.Entry<String, Set<String>> entry : modelMap.entrySet()) {
300             Set<String> modelShortcuts = entry.getValue();
301             Set<String> systemShortcuts = systemMap.remove(entry.getKey());
302             if (systemShortcuts == null) {
303                 systemShortcuts = Collections.emptySet();
304             }
305 
306             // Do not use .equals as it can vary based on the type of set
307             if (systemShortcuts.size() != modelShortcuts.size()
308                     || !systemShortcuts.containsAll(modelShortcuts)) {
309                 // Update system state for this package
310                 try {
311                     context.getSystemService(LauncherApps.class).pinShortcuts(
312                             entry.getKey(), new ArrayList<>(modelShortcuts), user);
313                 } catch (SecurityException | IllegalStateException e) {
314                     Log.w(TAG, "Failed to pin shortcut", e);
315                 }
316             }
317         }
318 
319         // If there are any extra pinned shortcuts, remove them
320         systemMap.keySet().forEach(packageName -> {
321             // Update system state
322             try {
323                 context.getSystemService(LauncherApps.class).pinShortcuts(
324                         packageName, Collections.emptyList(), user);
325             } catch (SecurityException | IllegalStateException e) {
326                 Log.w(TAG, "Failed to unpin shortcut", e);
327             }
328         });
329     }
330 
331     /**
332      * Return an existing FolderInfo object if we have encountered this ID previously,
333      * or make a new one.
334      */
findOrMakeFolder(int id)335     public synchronized FolderInfo findOrMakeFolder(int id) {
336         // See if a placeholder was created for us already
337         FolderInfo folderInfo = folders.get(id);
338         if (folderInfo == null) {
339             // No placeholder -- create a new instance
340             folderInfo = new FolderInfo();
341             folders.put(id, folderInfo);
342         }
343         return folderInfo;
344     }
345 
346     /**
347      * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
348      */
updateDeepShortcutCounts( String packageName, UserHandle user, List<ShortcutInfo> shortcuts)349     public synchronized void updateDeepShortcutCounts(
350             String packageName, UserHandle user, List<ShortcutInfo> shortcuts) {
351         if (packageName != null) {
352             Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
353             while (keysIter.hasNext()) {
354                 ComponentKey next = keysIter.next();
355                 if (next.componentName.getPackageName().equals(packageName)
356                         && next.user.equals(user)) {
357                     keysIter.remove();
358                 }
359             }
360         }
361 
362         // Now add the new shortcuts to the map.
363         for (ShortcutInfo shortcut : shortcuts) {
364             boolean shouldShowInContainer = shortcut.isEnabled()
365                     && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
366                     && shortcut.getActivity() != null;
367             if (shouldShowInContainer) {
368                 ComponentKey targetComponent
369                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
370 
371                 Integer previousCount = deepShortcutMap.get(targetComponent);
372                 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
373             }
374         }
375     }
376 
377     /**
378      * Returns a list containing all workspace items including widgets.
379      */
getAllWorkspaceItems()380     public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
381         ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
382         items.addAll(workspaceItems);
383         items.addAll(appWidgets);
384         return items;
385     }
386 
387     /**
388      * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
389      * items and dynamic/predicted items for the provided {@code userHandle}.
390      * Note the call is not synchronized over the model, that should be handled by the called.
391      */
forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op)392     public void forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op) {
393         for (ItemInfo info : itemsIdMap) {
394             if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
395                 op.accept((WorkspaceItemInfo) info);
396             }
397         }
398 
399         for (int i = extraItems.size() - 1; i >= 0; i--) {
400             for (ItemInfo info : extraItems.valueAt(i).items) {
401                 if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
402                     op.accept((WorkspaceItemInfo) info);
403                 }
404             }
405         }
406     }
407 
408     /**
409      * An object containing items corresponding to a fixed container
410      */
411     public static class FixedContainerItems {
412 
413         public final int containerId;
414         public final List<ItemInfo> items;
415 
FixedContainerItems(int containerId)416         public FixedContainerItems(int containerId) {
417             this(containerId, new ArrayList<>());
418         }
419 
FixedContainerItems(int containerId, List<ItemInfo> items)420         public FixedContainerItems(int containerId, List<ItemInfo> items) {
421             this.containerId = containerId;
422             this.items = items;
423         }
424 
425         @Override
clone()426         public FixedContainerItems clone() {
427             return new FixedContainerItems(containerId, new ArrayList<>(items));
428         }
429 
setItems(List<ItemInfo> newItems)430         public void setItems(List<ItemInfo> newItems) {
431             items.clear();
432             newItems.forEach(item -> {
433                 item.container = containerId;
434                 items.add(item);
435             });
436         }
437     }
438 
439 
440     public interface Callbacks {
441         // If the launcher has permission to access deep shortcuts.
442         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
443         // If quiet mode is enabled for any user
444         int FLAG_QUIET_MODE_ENABLED = 1 << 1;
445         // If launcher can change quiet mode
446         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
447 
448         /**
449          * Returns the page number to bind first, synchronously if possible or -1
450          */
getPageToBindSynchronously()451         int getPageToBindSynchronously();
clearPendingBinds()452         void clearPendingBinds();
startBinding()453         void startBinding();
bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)454         void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
bindScreens(IntArray orderedScreenIds)455         void bindScreens(IntArray orderedScreenIds);
finishFirstPageBind(ViewOnDrawExecutor executor)456         void finishFirstPageBind(ViewOnDrawExecutor executor);
finishBindingItems(int pageBoundFirst)457         void finishBindingItems(int pageBoundFirst);
preAddApps()458         void preAddApps();
bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)459         void bindAppsAdded(IntArray newScreens,
460                 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
461 
462         /**
463          * Binds updated incremental download progress
464          */
bindIncrementalDownloadProgressUpdated(AppInfo app)465         void bindIncrementalDownloadProgressUpdated(AppInfo app);
bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated)466         void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)467         void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
bindRestoreItemsChange(HashSet<ItemInfo> updates)468         void bindRestoreItemsChange(HashSet<ItemInfo> updates);
bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)469         void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
bindAllWidgets(List<WidgetsListBaseEntry> widgets)470         void bindAllWidgets(List<WidgetsListBaseEntry> widgets);
onPageBoundSynchronously(int page)471         void onPageBoundSynchronously(int page);
executeOnNextDraw(ViewOnDrawExecutor executor)472         void executeOnNextDraw(ViewOnDrawExecutor executor);
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)473         void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
474 
475         /**
476          * Binds extra item provided any external source
477          */
bindExtraContainerItems(FixedContainerItems item)478         default void bindExtraContainerItems(FixedContainerItems item) { }
479 
bindAllApplications(AppInfo[] apps, int flags)480         void bindAllApplications(AppInfo[] apps, int flags);
481     }
482 }
483