• 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.BuildConfig.QSB_ON_FIRST_SCREEN;
21 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
22 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
23 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
24 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
25 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
26 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
27 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
28 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
29 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
30 
31 import static java.util.stream.Collectors.groupingBy;
32 import static java.util.stream.Collectors.mapping;
33 
34 import android.content.Context;
35 import android.content.pm.LauncherApps;
36 import android.content.pm.ShortcutInfo;
37 import android.os.UserHandle;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.util.Pair;
41 import android.view.View;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 
46 import com.android.launcher3.BuildConfig;
47 import com.android.launcher3.Workspace;
48 import com.android.launcher3.config.FeatureFlags;
49 import com.android.launcher3.dagger.LauncherAppSingleton;
50 import com.android.launcher3.logging.FileLog;
51 import com.android.launcher3.model.data.AppInfo;
52 import com.android.launcher3.model.data.CollectionInfo;
53 import com.android.launcher3.model.data.ItemInfo;
54 import com.android.launcher3.model.data.WorkspaceItemInfo;
55 import com.android.launcher3.pm.UserCache;
56 import com.android.launcher3.shortcuts.ShortcutKey;
57 import com.android.launcher3.shortcuts.ShortcutRequest;
58 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
59 import com.android.launcher3.util.ComponentKey;
60 import com.android.launcher3.util.IntArray;
61 import com.android.launcher3.util.IntSet;
62 import com.android.launcher3.util.IntSparseArrayMap;
63 import com.android.launcher3.util.ItemInflater;
64 import com.android.launcher3.util.PackageUserKey;
65 import com.android.launcher3.util.RunnableList;
66 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
67 
68 import java.io.FileDescriptor;
69 import java.io.PrintWriter;
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.Collections;
73 import java.util.HashMap;
74 import java.util.Iterator;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.Set;
78 import java.util.function.Consumer;
79 import java.util.function.Predicate;
80 import java.util.stream.Collectors;
81 import java.util.stream.Stream;
82 
83 import javax.inject.Inject;
84 
85 /**
86  * All the data stored in-memory and managed by the LauncherModel
87  *
88  * All the static data should be accessed on the background thread, A lock should be acquired on
89  * this object when accessing any data from this model.
90  */
91 @LauncherAppSingleton
92 public class BgDataModel {
93 
94     private static final String TAG = "BgDataModel";
95 
96     /**
97      * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
98      * LauncherModel to their ids
99      */
100     public final IntSparseArrayMap<ItemInfo> itemsIdMap = 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;
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      * Load id for which the callbacks were successfully bound
129      */
130     public int lastLoadId = -1;
131     public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
132             && !enableSmartspaceRemovalToggle();
133 
134     @Inject
BgDataModel(WidgetsModel widgetsModel)135     public BgDataModel(WidgetsModel widgetsModel) {
136         this.widgetsModel = widgetsModel;
137     }
138 
139     /**
140      * Clears all the data
141      */
clear()142     public synchronized void clear() {
143         itemsIdMap.clear();
144         deepShortcutMap.clear();
145         extraItems.clear();
146     }
147 
148     /**
149      * Creates an array of valid workspace screens based on current items in the model.
150      */
collectWorkspaceScreens()151     public synchronized IntArray collectWorkspaceScreens() {
152         IntSet screenSet = new IntSet();
153         for (ItemInfo item: itemsIdMap) {
154             if (item.container == CONTAINER_DESKTOP) {
155                 screenSet.add(item.screenId);
156             }
157         }
158         if ((FeatureFlags.QSB_ON_FIRST_SCREEN
159                 && !SHOULD_SHOW_FIRST_PAGE_WIDGET)
160                 || screenSet.isEmpty()) {
161             screenSet.add(Workspace.FIRST_SCREEN_ID);
162         }
163         return screenSet.getArray();
164     }
165 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)166     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
167             String[] args) {
168         writer.println(prefix + "Data Model:");
169         writer.println(prefix + " ---- items id map ");
170         for (int i = 0; i < itemsIdMap.size(); i++) {
171             writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
172         }
173         writer.println(prefix + " ---- extra items ");
174         for (int i = 0; i < extraItems.size(); i++) {
175             writer.println(prefix + '\t' + extraItems.valueAt(i).toString());
176         }
177 
178         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
179             writer.println(prefix + "shortcut counts ");
180             for (Integer count : deepShortcutMap.values()) {
181                 writer.print(count + ", ");
182             }
183             writer.println();
184         }
185     }
186 
removeItem(Context context, ItemInfo... items)187     public synchronized void removeItem(Context context, ItemInfo... items) {
188         removeItem(context, Arrays.asList(items));
189     }
190 
removeItem(Context context, List<? extends ItemInfo> items)191     public synchronized void removeItem(Context context, List<? extends ItemInfo> items) {
192         if (BuildConfig.IS_STUDIO_BUILD) {
193             items.stream()
194                     .filter(item -> item.itemType == ITEM_TYPE_FOLDER
195                             || item.itemType == ITEM_TYPE_APP_PAIR)
196                     .forEach(item -> itemsIdMap.stream()
197                             .filter(info -> info.container == item.id)
198                             // We are deleting a collection which still contains items that
199                             // think they are contained by that collection.
200                             .forEach(info -> Log.e(TAG,
201                                     "deleting a collection (" + item + ") which still contains"
202                                             + " items (" + info + ")")));
203         }
204 
205         items.forEach(item -> itemsIdMap.remove(item.id));
206         items.stream().map(info -> info.user).distinct().forEach(
207                 user -> updateShortcutPinnedState(context, user));
208     }
209 
addItem(Context context, ItemInfo item, boolean newItem)210     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
211         itemsIdMap.put(item.id, item);
212         if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
213             updateShortcutPinnedState(context, item.user);
214         }
215         if (BuildConfig.IS_DEBUG_DEVICE
216                 && newItem
217                 && item.container != CONTAINER_DESKTOP
218                 && item.container != CONTAINER_HOTSEAT
219                 && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) {
220             // Adding an item to a nonexistent collection.
221             Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection");
222         }
223     }
224 
225     /**
226      * Updates the deep shortcuts state in system to match out internal model, pinning any missing
227      * shortcuts and unpinning any extra shortcuts.
228      */
updateShortcutPinnedState(Context context)229     public void updateShortcutPinnedState(Context context) {
230         for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
231             updateShortcutPinnedState(context, user);
232         }
233     }
234 
235     /**
236      * Updates the deep shortucts state in system to match out internal model, pinning any missing
237      * shortcuts and unpinning any extra shortcuts.
238      */
updateShortcutPinnedState(Context context, UserHandle user)239     public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
240         if (!WIDGETS_ENABLED) {
241             return;
242         }
243 
244         // Collect all system shortcuts
245         QueryResult result = new ShortcutRequest(context, user)
246                 .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY);
247         if (!result.wasSuccess()) {
248             return;
249         }
250         // Map of packageName to shortcutIds that are currently in the system
251         Map<String, Set<String>> systemMap = result.stream()
252                 .collect(groupingBy(ShortcutInfo::getPackage,
253                         mapping(ShortcutInfo::getId, Collectors.toSet())));
254 
255         // Collect all model shortcuts
256         Stream.Builder<WorkspaceItemInfo> itemStream = Stream.builder();
257         forAllWorkspaceItemInfos(user, itemStream::accept);
258         // Map of packageName to shortcutIds that are currently in our model
259         Map<String, Set<String>> modelMap = Stream.concat(
260                     // Model shortcuts
261                     itemStream.build()
262                         .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT)
263                         .map(ShortcutKey::fromItemInfo),
264                     // Pending shortcuts
265                     ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
266                 .collect(groupingBy(ShortcutKey::getPackageName,
267                         mapping(ShortcutKey::getId, Collectors.toSet())));
268 
269         // Check for diff
270         for (Map.Entry<String, Set<String>> entry : modelMap.entrySet()) {
271             Set<String> modelShortcuts = entry.getValue();
272             Set<String> systemShortcuts = systemMap.remove(entry.getKey());
273             if (systemShortcuts == null) {
274                 systemShortcuts = Collections.emptySet();
275             }
276 
277             // Do not use .equals as it can vary based on the type of set
278             if (systemShortcuts.size() != modelShortcuts.size()
279                     || !systemShortcuts.containsAll(modelShortcuts)) {
280                 // Update system state for this package
281                 try {
282                     FileLog.d(TAG, "updateShortcutPinnedState:"
283                             + " Pinning Shortcuts: " + entry.getKey() + ": " + modelShortcuts);
284                     context.getSystemService(LauncherApps.class).pinShortcuts(
285                             entry.getKey(), new ArrayList<>(modelShortcuts), user);
286                 } catch (SecurityException | IllegalStateException e) {
287                     Log.w(TAG, "Failed to pin shortcut", e);
288                 }
289             }
290         }
291 
292         // If there are any extra pinned shortcuts, remove them
293         systemMap.keySet().forEach(packageName -> {
294             // Update system state
295             try {
296                 FileLog.d(TAG, "updateShortcutPinnedState:"
297                         + " Unpinning extra Shortcuts for package: " + packageName
298                         + ": " + systemMap.get(packageName));
299                 context.getSystemService(LauncherApps.class).pinShortcuts(
300                         packageName, Collections.emptyList(), user);
301             } catch (SecurityException | IllegalStateException e) {
302                 Log.w(TAG, "Failed to unpin shortcut", e);
303             }
304         });
305     }
306 
307     /**
308      * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
309      */
updateDeepShortcutCounts( String packageName, UserHandle user, List<ShortcutInfo> shortcuts)310     public synchronized void updateDeepShortcutCounts(
311             String packageName, UserHandle user, List<ShortcutInfo> shortcuts) {
312         if (packageName != null) {
313             Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
314             while (keysIter.hasNext()) {
315                 ComponentKey next = keysIter.next();
316                 if (next.componentName.getPackageName().equals(packageName)
317                         && next.user.equals(user)) {
318                     keysIter.remove();
319                 }
320             }
321         }
322 
323         // Now add the new shortcuts to the map.
324         for (ShortcutInfo shortcut : shortcuts) {
325             boolean shouldShowInContainer = shortcut.isEnabled()
326                     && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
327                     && shortcut.getActivity() != null;
328             if (shouldShowInContainer) {
329                 ComponentKey targetComponent
330                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
331 
332                 Integer previousCount = deepShortcutMap.get(targetComponent);
333                 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
334             }
335         }
336     }
337 
338     /**
339      * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
340      * items and dynamic/predicted items for the provided {@code userHandle}.
341      * Note the call is not synchronized over the model, that should be handled by the called.
342      */
forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op)343     public void forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op) {
344         for (ItemInfo info : itemsIdMap) {
345             if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
346                 op.accept((WorkspaceItemInfo) info);
347             }
348         }
349 
350         for (int i = extraItems.size() - 1; i >= 0; i--) {
351             for (ItemInfo info : extraItems.valueAt(i).items) {
352                 if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
353                     op.accept((WorkspaceItemInfo) info);
354                 }
355             }
356         }
357     }
358 
359     /**
360      * An object containing items corresponding to a fixed container
361      */
362     public static class FixedContainerItems {
363 
364         public final int containerId;
365         public final List<ItemInfo> items;
366 
FixedContainerItems(int containerId, List<ItemInfo> items)367         public FixedContainerItems(int containerId, List<ItemInfo> items) {
368             this.containerId = containerId;
369             this.items = Collections.unmodifiableList(items);
370         }
371 
372         @Override
373         @NonNull
toString()374         public final String toString() {
375             StringBuilder s = new StringBuilder();
376             s.append("FixedContainerItems:");
377             s.append(" id=").append(containerId);
378             s.append(" itemCount=").append(items.size());
379             for (int i = 0; i < items.size(); i++) {
380                 s.append(" item #").append(i).append(": ").append(items.get(i).toString());
381             }
382             return s.toString();
383         }
384 
385     }
386 
387 
388     public interface Callbacks {
389         // If the launcher has permission to access deep shortcuts.
390         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
391         // If quiet mode is enabled for any user
392         int FLAG_QUIET_MODE_ENABLED = 1 << 1;
393         // If launcher can change quiet mode
394         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
395         // If quiet mode is enabled for work profile user
396         int FLAG_WORK_PROFILE_QUIET_MODE_ENABLED = 1 << 3;
397         // If quiet mode is enabled for private profile user
398         int FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED = 1 << 4;
399 
400         /**
401          * Returns an IntSet of page ids to bind first, synchronously if possible
402          * or an empty IntSet
403          * @param orderedScreenIds All the page ids to be bound
404          */
405         @NonNull
getPagesToBindSynchronously(IntArray orderedScreenIds)406         default IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
407             return new IntSet();
408         }
409 
clearPendingBinds()410         default void clearPendingBinds() { }
startBinding()411         default void startBinding() { }
412 
413         @Nullable
getItemInflater()414         default ItemInflater getItemInflater() {
415             return null;
416         }
417 
bindItems(@onNull List<ItemInfo> shortcuts, boolean forceAnimateIcons)418         default void bindItems(@NonNull List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
419         /** Alternate method to bind preinflated views */
bindInflatedItems(@onNull List<Pair<ItemInfo, View>> items)420         default void bindInflatedItems(@NonNull List<Pair<ItemInfo, View>> items) { }
421 
bindScreens(IntArray orderedScreenIds)422         default void bindScreens(IntArray orderedScreenIds) { }
setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled)423         default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) { }
finishBindingItems(IntSet pagesBoundFirst)424         default void finishBindingItems(IntSet pagesBoundFirst) { }
preAddApps()425         default void preAddApps() { }
bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)426         default void bindAppsAdded(IntArray newScreens,
427                 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated) { }
428 
429         /**
430          * Called when some persistent property of an item is modified
431          */
bindItemsModified(List<ItemInfo> items)432         default void bindItemsModified(List<ItemInfo> items) { }
433 
434         /**
435          * Binds updated incremental download progress
436          */
bindIncrementalDownloadProgressUpdated(AppInfo app)437         default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
438 
439         /** Called when a runtime property of the ItemInfo is updated due to some system event */
bindItemsUpdated(Set<ItemInfo> updates)440         default void bindItemsUpdated(Set<ItemInfo> updates) { }
bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher)441         default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
442 
443         /**
444          * Binds the app widgets to the providers that share widgets with the UI.
445          */
bindAllWidgets(@onNull List<WidgetsListBaseEntry> widgets)446         default void bindAllWidgets(@NonNull List<WidgetsListBaseEntry> widgets) { }
447 
bindSmartspaceWidget()448         default void bindSmartspaceWidget() { }
449 
450         /** Called when workspace has been bound. */
onInitialBindComplete(@onNull IntSet boundPages, @NonNull RunnableList pendingTasks, @NonNull RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync)451         default void onInitialBindComplete(@NonNull IntSet boundPages,
452                 @NonNull RunnableList pendingTasks,
453                 @NonNull RunnableList onCompleteSignal,
454                 int workspaceItemCount, boolean isBindSync) {
455             pendingTasks.executeAllAndDestroy();
456         }
457 
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)458         default void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
459 
460         /**
461          * Binds extra item provided any external source
462          */
bindExtraContainerItems(FixedContainerItems item)463         default void bindExtraContainerItems(FixedContainerItems item) { }
464 
bindAllApplications(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> packageUserKeytoUidMap)465         default void bindAllApplications(AppInfo[] apps, int flags,
466                 Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
467         }
468 
469         /**
470          * Binds the cache of string resources
471          */
bindStringCache(StringCache cache)472         default void bindStringCache(StringCache cache) { }
473     }
474 }
475