1 /*
2  * Copyright (C) 2018 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.allapps;
17 
18 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
19 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
20 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
21 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
22 
23 import android.content.Context;
24 import android.os.UserHandle;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 
31 import com.android.launcher3.BubbleTextView;
32 import com.android.launcher3.model.data.AppInfo;
33 import com.android.launcher3.model.data.ItemInfo;
34 import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
35 import com.android.launcher3.util.ComponentKey;
36 import com.android.launcher3.util.PackageUserKey;
37 import com.android.launcher3.views.ActivityContext;
38 
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.concurrent.CopyOnWriteArrayList;
47 import java.util.function.Consumer;
48 import java.util.function.Predicate;
49 
50 /**
51  * A utility class to maintain the collection of all apps.
52  *
53  * @param <T> The type of the context.
54  */
55 public class AllAppsStore<T extends Context & ActivityContext> {
56 
57     // Defer updates flag used to defer all apps updates to the next draw.
58     public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
59     // Defer updates flag used to defer all apps updates by a test's request.
60     public static final int DEFER_UPDATES_TEST = 1 << 1;
61 
62     private PackageUserKey mTempKey = new PackageUserKey(null, null);
63     private AppInfo mTempInfo = new AppInfo();
64 
65     private @NonNull AppInfo[] mApps = EMPTY_ARRAY;
66 
67     private final List<OnUpdateListener> mUpdateListeners = new CopyOnWriteArrayList<>();
68     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
69     private Map<PackageUserKey, Integer> mPackageUserKeytoUidMap = Collections.emptyMap();
70     private int mModelFlags;
71     private int mDeferUpdatesFlags = 0;
72     private boolean mUpdatePending = false;
73     private final AllAppsRecyclerViewPool mAllAppsRecyclerViewPool = new AllAppsRecyclerViewPool();
74 
75     private final T mContext;
76 
getApps()77     public AppInfo[] getApps() {
78         return mApps;
79     }
80 
AllAppsStore(@onNull T context)81     public AllAppsStore(@NonNull T context) {
82         mContext = context;
83     }
84 
85     /**
86      * Calling {@link #setApps(AppInfo[], int, Map, boolean)} with shouldPreinflate set to
87      * {@code true}. This method should be called in launcher (not for taskbar).
88      */
setApps(@ullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map)89     public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
90         setApps(apps, flags, map, /* shouldPreinflate= */ true);
91     }
92 
93     /**
94      * Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
95      * the current set of apps.
96      *
97      * <p> Note that shouldPreinflate param should be set to {@code false} for taskbar, because
98      * this method is too late to preinflate all apps, as user will open all apps in the frame
99      *
100      * <p>Param: apps are required to be sorted using the comparator COMPONENT_KEY_COMPARATOR
101      * in order to enable binary search on the mApps store
102      */
setApps(@ullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map, boolean shouldPreinflate)103     public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map,
104             boolean shouldPreinflate) {
105         mApps = apps == null ? EMPTY_ARRAY : apps;
106         mModelFlags = flags;
107         notifyUpdate();
108         mPackageUserKeytoUidMap = map;
109         // Preinflate all apps RV when apps has changed, which can happen after unlocking screen,
110         // rotating screen, or downloading/upgrading apps.
111         if (shouldPreinflate && ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
112             mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext);
113         }
114     }
115 
getRecyclerViewPool()116     AllAppsRecyclerViewPool getRecyclerViewPool() {
117         return mAllAppsRecyclerViewPool;
118     }
119 
120     /**
121      * Look up for Uid using package name and user handle for the current set of apps.
122      */
lookUpForUid(String packageName, UserHandle user)123     public int lookUpForUid(String packageName, UserHandle user) {
124         return mPackageUserKeytoUidMap.getOrDefault(new PackageUserKey(packageName, user), -1);
125     }
126 
127     /**
128      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_ENABLED
129      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
130      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
131      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_WORK_PROFILE_QUIET_MODE_ENABLED
132      * @see
133      * com.android.launcher3.model.BgDataModel.Callbacks#FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED
134      */
hasModelFlag(int mask)135     public boolean hasModelFlag(int mask) {
136         return (mModelFlags & mask) != 0;
137     }
138 
139     /**
140      * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise
141      * null.
142      *
143      * Uses {@link AppInfo#COMPONENT_KEY_COMPARATOR} as a default comparator.
144      */
145     @Nullable
getApp(ComponentKey key)146     public AppInfo getApp(ComponentKey key) {
147         return getApp(key, COMPONENT_KEY_COMPARATOR);
148     }
149 
150     /**
151      * Generic version of {@link #getApp(ComponentKey)} that allows comparator to be specified.
152      */
153     @Nullable
getApp(ComponentKey key, Comparator<AppInfo> comparator)154     public AppInfo getApp(ComponentKey key, Comparator<AppInfo> comparator) {
155         mTempInfo.componentName = key.componentName;
156         mTempInfo.user = key.user;
157         int index = Arrays.binarySearch(mApps, mTempInfo, comparator);
158         return index < 0 ? null : mApps[index];
159     }
160 
enableDeferUpdates(int flag)161     public void enableDeferUpdates(int flag) {
162         mDeferUpdatesFlags |= flag;
163     }
164 
disableDeferUpdates(int flag)165     public void disableDeferUpdates(int flag) {
166         mDeferUpdatesFlags &= ~flag;
167         if (mDeferUpdatesFlags == 0 && mUpdatePending) {
168             notifyUpdate();
169             mUpdatePending = false;
170         }
171     }
172 
disableDeferUpdatesSilently(int flag)173     public void disableDeferUpdatesSilently(int flag) {
174         mDeferUpdatesFlags &= ~flag;
175     }
176 
getDeferUpdatesFlags()177     public int getDeferUpdatesFlags() {
178         return mDeferUpdatesFlags;
179     }
180 
notifyUpdate()181     private void notifyUpdate() {
182         if (mDeferUpdatesFlags != 0) {
183             mUpdatePending = true;
184             return;
185         }
186         for (OnUpdateListener listener : mUpdateListeners) {
187             listener.onAppsUpdated();
188         }
189     }
190 
addUpdateListener(OnUpdateListener listener)191     public void addUpdateListener(OnUpdateListener listener) {
192         mUpdateListeners.add(listener);
193     }
194 
removeUpdateListener(OnUpdateListener listener)195     public void removeUpdateListener(OnUpdateListener listener) {
196         mUpdateListeners.remove(listener);
197     }
198 
registerIconContainer(ViewGroup container)199     public void registerIconContainer(ViewGroup container) {
200         if (container != null && !mIconContainers.contains(container)) {
201             mIconContainers.add(container);
202         }
203     }
204 
unregisterIconContainer(ViewGroup container)205     public void unregisterIconContainer(ViewGroup container) {
206         mIconContainers.remove(container);
207     }
208 
updateNotificationDots(Predicate<PackageUserKey> updatedDots)209     public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
210         updateAllIcons((child) -> {
211             if (child.getTag() instanceof ItemInfo) {
212                 ItemInfo info = (ItemInfo) child.getTag();
213                 if (mTempKey.updateFromItemInfo(info) && updatedDots.test(mTempKey)) {
214                     child.applyDotState(info, true /* animate */);
215                 }
216             }
217         });
218     }
219 
220     /**
221      * Sets the AppInfo's associated icon's progress bar.
222      *
223      * If this app is installed and supports incremental downloads, the progress bar will be updated
224      * the app's total download progress. Otherwise, the progress bar will be updated to the app's
225      * installation progress.
226      *
227      * If this app is fully downloaded, the app icon will be reapplied.
228      */
updateProgressBar(AppInfo app)229     public void updateProgressBar(AppInfo app) {
230         updateAllIcons((child) -> {
231             if (child.getTag() == app) {
232                 if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) {
233                     child.applyFromApplicationInfo(app);
234                 } else {
235                     child.applyProgressLevel();
236                 }
237             }
238         });
239     }
240 
updateAllIcons(Consumer<BubbleTextView> action)241     private void updateAllIcons(Consumer<BubbleTextView> action) {
242         for (int i = mIconContainers.size() - 1; i >= 0; i--) {
243             ViewGroup parent = mIconContainers.get(i);
244             int childCount = parent.getChildCount();
245 
246             for (int j = 0; j < childCount; j++) {
247                 View child = parent.getChildAt(j);
248                 if (child instanceof BubbleTextView) {
249                     action.accept((BubbleTextView) child);
250                 }
251             }
252         }
253     }
254 
255     public interface OnUpdateListener {
onAppsUpdated()256         void onAppsUpdated();
257     }
258 
259     /** Generate a dumpsys for each app package name and position in the apps list */
dump(String prefix, PrintWriter writer)260     public void dump(String prefix, PrintWriter writer) {
261         writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length);
262         for (int i = 0; i < mApps.length; i++) {
263             writer.println(String.format("%s\tPackage index and name: %d/%s", prefix, i,
264                     mApps[i].componentName.getPackageName()));
265         }
266     }
267 }
268