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