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