• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 package com.android.launcher3.model;
18 
19 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
20 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
21 
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.LauncherActivityInfo;
26 import android.content.pm.LauncherApps;
27 import android.os.LocaleList;
28 import android.os.UserHandle;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 
34 import com.android.launcher3.AppFilter;
35 import com.android.launcher3.compat.AlphabeticIndexCompat;
36 import com.android.launcher3.icons.IconCache;
37 import com.android.launcher3.model.BgDataModel.Callbacks;
38 import com.android.launcher3.model.data.AppInfo;
39 import com.android.launcher3.model.data.ItemInfo;
40 import com.android.launcher3.pm.PackageInstallInfo;
41 import com.android.launcher3.util.FlagOp;
42 import com.android.launcher3.util.PackageManagerHelper;
43 import com.android.launcher3.util.SafeCloseable;
44 
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.function.Consumer;
50 import java.util.function.Predicate;
51 
52 
53 /**
54  * Stores the list of all applications for the all apps view.
55  */
56 public class AllAppsList {
57 
58     private static final String TAG = "AllAppsList";
59     private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { };
60 
61 
62     public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
63 
64     /** The list off all apps. */
65     public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
66 
67     @NonNull
68     private IconCache mIconCache;
69 
70     @NonNull
71     private AppFilter mAppFilter;
72 
73     private boolean mDataChanged = false;
74     private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER;
75 
76     private AlphabeticIndexCompat mIndex;
77 
78     /**
79      * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
80      * @see Callbacks#FLAG_QUIET_MODE_ENABLED
81      * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
82      */
83     private int mFlags;
84 
85     /**
86      * Boring constructor.
87      */
AllAppsList(IconCache iconCache, AppFilter appFilter)88     public AllAppsList(IconCache iconCache, AppFilter appFilter) {
89         mIconCache = iconCache;
90         mAppFilter = appFilter;
91         mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
92     }
93 
94     /**
95      * Returns true if there have been any changes since last call.
96      */
getAndResetChangeFlag()97     public boolean getAndResetChangeFlag() {
98         boolean result = mDataChanged;
99         mDataChanged = false;
100         return result;
101     }
102 
103     /**
104      * Helper to checking {@link Callbacks#FLAG_HAS_SHORTCUT_PERMISSION}
105      */
hasShortcutHostPermission()106     public boolean hasShortcutHostPermission() {
107         return (mFlags & Callbacks.FLAG_HAS_SHORTCUT_PERMISSION) != 0;
108     }
109 
110     /**
111      * Sets or clears the provided flag
112      */
setFlags(int flagMask, boolean enabled)113     public void setFlags(int flagMask, boolean enabled) {
114         if (enabled) {
115             mFlags |= flagMask;
116         } else {
117             mFlags &= ~flagMask;
118         }
119         mDataChanged = true;
120     }
121 
122     /**
123      * Returns the model flags
124      */
getFlags()125     public int getFlags() {
126         return mFlags;
127     }
128 
129 
130     /**
131      * Add the supplied ApplicationInfo objects to the list, and enqueue it into the
132      * list to broadcast when notify() is called.
133      *
134      * If the app is already in the list, doesn't add it.
135      */
add(AppInfo info, LauncherActivityInfo activityInfo)136     public void add(AppInfo info, LauncherActivityInfo activityInfo) {
137         add(info, activityInfo, true);
138     }
139 
add(AppInfo info, LauncherActivityInfo activityInfo, boolean loadIcon)140     public void add(AppInfo info, LauncherActivityInfo activityInfo, boolean loadIcon) {
141         if (!mAppFilter.shouldShowApp(info.componentName)) {
142             return;
143         }
144         if (findAppInfo(info.componentName, info.user) != null) {
145             return;
146         }
147         if (loadIcon) {
148             mIconCache.getTitleAndIcon(info, activityInfo, false /* useLowResIcon */);
149             info.sectionName = mIndex.computeSectionName(info.title);
150         } else {
151             info.title = "";
152         }
153 
154         data.add(info);
155         mDataChanged = true;
156     }
157 
158     @Nullable
addPromiseApp(Context context, PackageInstallInfo installInfo)159     public AppInfo addPromiseApp(Context context, PackageInstallInfo installInfo) {
160         return addPromiseApp(context, installInfo, true);
161     }
162 
163     @Nullable
addPromiseApp( Context context, PackageInstallInfo installInfo, boolean loadIcon)164     public AppInfo addPromiseApp(
165             Context context, PackageInstallInfo installInfo, boolean loadIcon) {
166         // only if not yet installed
167         if (new PackageManagerHelper(context)
168                 .isAppInstalled(installInfo.packageName, installInfo.user)) {
169             return null;
170         }
171         AppInfo promiseAppInfo = new AppInfo(installInfo);
172 
173         if (loadIcon) {
174             mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.usingLowResIcon());
175             promiseAppInfo.sectionName = mIndex.computeSectionName(promiseAppInfo.title);
176         } else {
177             promiseAppInfo.title = "";
178         }
179 
180         data.add(promiseAppInfo);
181         mDataChanged = true;
182 
183         return promiseAppInfo;
184     }
185 
updateSectionName(AppInfo appInfo)186     public void updateSectionName(AppInfo appInfo) {
187         appInfo.sectionName = mIndex.computeSectionName(appInfo.title);
188 
189     }
190 
191     /** Updates the given PackageInstallInfo's associated AppInfo's installation info. */
updatePromiseInstallInfo(PackageInstallInfo installInfo)192     public List<AppInfo> updatePromiseInstallInfo(PackageInstallInfo installInfo) {
193         List<AppInfo> updatedAppInfos = new ArrayList<>();
194         UserHandle user = installInfo.user;
195         for (int i = data.size() - 1; i >= 0; i--) {
196             final AppInfo appInfo = data.get(i);
197             final ComponentName tgtComp = appInfo.getTargetComponent();
198             if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName)
199                     && appInfo.user.equals(user)) {
200                 if (installInfo.state == PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING
201                         || installInfo.state == PackageInstallInfo.STATUS_INSTALLING) {
202                     if (appInfo.isAppStartable()
203                             && installInfo.state == PackageInstallInfo.STATUS_INSTALLING) {
204                         continue;
205                     }
206                     appInfo.setProgressLevel(installInfo);
207 
208                     updatedAppInfos.add(appInfo);
209                 } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED
210                         && !appInfo.isAppStartable()) {
211                     removeApp(i);
212                 }
213             }
214         }
215         return updatedAppInfos;
216     }
217 
removeApp(int index)218     private void removeApp(int index) {
219         AppInfo removed = data.remove(index);
220         if (removed != null) {
221             mDataChanged = true;
222             mRemoveListener.accept(removed);
223         }
224     }
225 
clear()226     public void clear() {
227         data.clear();
228         mDataChanged = false;
229         // Reset the index as locales might have changed
230         mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
231     }
232 
233     /**
234      * Add the icons for the supplied apk called packageName.
235      */
addPackage( Context context, String packageName, UserHandle user)236     public List<LauncherActivityInfo> addPackage(
237             Context context, String packageName, UserHandle user) {
238         List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class)
239                 .getActivityList(packageName, user);
240 
241         for (LauncherActivityInfo info : activities) {
242             add(new AppInfo(context, info, user), info);
243         }
244 
245         return activities;
246     }
247 
248     /**
249      * Remove the apps for the given apk identified by packageName.
250      */
removePackage(String packageName, UserHandle user)251     public void removePackage(String packageName, UserHandle user) {
252         final List<AppInfo> data = this.data;
253         for (int i = data.size() - 1; i >= 0; i--) {
254             AppInfo info = data.get(i);
255             if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
256                 removeApp(i);
257             }
258         }
259     }
260 
261     /**
262      * Updates the disabled flags of apps matching {@param matcher} based on {@param op}.
263      */
updateDisabledFlags(Predicate<ItemInfo> matcher, FlagOp op)264     public void updateDisabledFlags(Predicate<ItemInfo> matcher, FlagOp op) {
265         final List<AppInfo> data = this.data;
266         for (int i = data.size() - 1; i >= 0; i--) {
267             AppInfo info = data.get(i);
268             if (matcher.test(info)) {
269                 info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
270                 mDataChanged = true;
271             }
272         }
273     }
274 
updateIconsAndLabels(HashSet<String> packages, UserHandle user)275     public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) {
276         for (AppInfo info : data) {
277             if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
278                 mIconCache.updateTitleAndIcon(info);
279                 info.sectionName = mIndex.computeSectionName(info.title);
280                 mDataChanged = true;
281             }
282         }
283     }
284 
285     /**
286      * Add and remove icons for this package which has been updated.
287      */
updatePackage( Context context, String packageName, UserHandle user)288     public List<LauncherActivityInfo> updatePackage(
289             Context context, String packageName, UserHandle user) {
290         final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
291                 .getActivityList(packageName, user);
292         if (matches.size() > 0) {
293             // Find disabled/removed activities and remove them from data and add them
294             // to the removed list.
295             for (int i = data.size() - 1; i >= 0; i--) {
296                 final AppInfo applicationInfo = data.get(i);
297                 if (user.equals(applicationInfo.user)
298                         && packageName.equals(applicationInfo.componentName.getPackageName())) {
299                     if (!findActivity(matches, applicationInfo.componentName)) {
300                         Log.w(TAG, "Changing shortcut target due to app component name change.");
301                         removeApp(i);
302                     }
303                 }
304             }
305 
306             // Find enabled activities and add them to the adapter
307             // Also updates existing activities with new labels/icons
308             for (final LauncherActivityInfo info : matches) {
309                 AppInfo applicationInfo = findAppInfo(info.getComponentName(), user);
310                 if (applicationInfo == null) {
311                     add(new AppInfo(context, info, user), info);
312                 } else {
313                     Intent launchIntent = AppInfo.makeLaunchIntent(info);
314 
315                     mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
316                     applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
317                     applicationInfo.setProgressLevel(
318                             PackageManagerHelper.getLoadingProgress(info),
319                             PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
320                     applicationInfo.intent = launchIntent;
321 
322                     mDataChanged = true;
323                 }
324             }
325         } else {
326             // Remove all data for this package.
327             for (int i = data.size() - 1; i >= 0; i--) {
328                 final AppInfo applicationInfo = data.get(i);
329                 if (user.equals(applicationInfo.user)
330                         && packageName.equals(applicationInfo.componentName.getPackageName())) {
331                     mIconCache.remove(applicationInfo.componentName, user);
332                     removeApp(i);
333                 }
334             }
335         }
336 
337         return matches;
338     }
339 
340     /**
341      * Returns whether <em>apps</em> contains <em>component</em>.
342      */
findActivity(List<LauncherActivityInfo> apps, ComponentName component)343     private static boolean findActivity(List<LauncherActivityInfo> apps,
344             ComponentName component) {
345         for (LauncherActivityInfo info : apps) {
346             if (info.getComponentName().equals(component)) {
347                 return true;
348             }
349         }
350         return false;
351     }
352 
353     /**
354      * Find an AppInfo object for the given componentName
355      *
356      * @return the corresponding AppInfo or null
357      */
findAppInfo(@onNull ComponentName componentName, @NonNull UserHandle user)358     public @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
359                                           @NonNull UserHandle user) {
360         for (AppInfo info: data) {
361             if (componentName.equals(info.componentName) && user.equals(info.user)) {
362                 return info;
363             }
364         }
365         return null;
366     }
367 
copyData()368     public AppInfo[] copyData() {
369         AppInfo[] result = data.toArray(EMPTY_ARRAY);
370         Arrays.sort(result, COMPONENT_KEY_COMPARATOR);
371         return result;
372     }
373 
trackRemoves(Consumer<AppInfo> removeListener)374     public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) {
375         mRemoveListener = removeListener;
376 
377         return () -> mRemoveListener = NO_OP_CONSUMER;
378     }
379 }
380