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