• 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 android.content.ComponentName;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.ShortcutInfo;
22 import android.os.Process;
23 import android.os.UserHandle;
24 import android.util.Log;
25 
26 import com.android.launcher3.AllAppsList;
27 import com.android.launcher3.AppInfo;
28 import com.android.launcher3.WorkspaceItemInfo;
29 import com.android.launcher3.icons.IconCache;
30 import com.android.launcher3.InstallShortcutReceiver;
31 import com.android.launcher3.ItemInfo;
32 import com.android.launcher3.LauncherAppState;
33 import com.android.launcher3.LauncherAppWidgetInfo;
34 import com.android.launcher3.LauncherModel.CallbackTask;
35 import com.android.launcher3.LauncherModel.Callbacks;
36 import com.android.launcher3.LauncherSettings.Favorites;
37 import com.android.launcher3.SessionCommitReceiver;
38 import com.android.launcher3.Utilities;
39 import com.android.launcher3.compat.LauncherAppsCompat;
40 import com.android.launcher3.compat.UserManagerCompat;
41 import com.android.launcher3.config.FeatureFlags;
42 import com.android.launcher3.icons.BitmapInfo;
43 import com.android.launcher3.icons.LauncherIcons;
44 import com.android.launcher3.logging.FileLog;
45 import com.android.launcher3.shortcuts.DeepShortcutManager;
46 import com.android.launcher3.util.FlagOp;
47 import com.android.launcher3.util.IntSparseArrayMap;
48 import com.android.launcher3.util.ItemInfoMatcher;
49 import com.android.launcher3.util.PackageManagerHelper;
50 import com.android.launcher3.util.PackageUserKey;
51 
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collections;
55 import java.util.HashSet;
56 import java.util.List;
57 
58 /**
59  * Handles updates due to changes in package manager (app installed/updated/removed)
60  * or when a user availability changes.
61  */
62 public class PackageUpdatedTask extends BaseModelUpdateTask {
63 
64     private static final boolean DEBUG = false;
65     private static final String TAG = "PackageUpdatedTask";
66 
67     public static final int OP_NONE = 0;
68     public static final int OP_ADD = 1;
69     public static final int OP_UPDATE = 2;
70     public static final int OP_REMOVE = 3; // uninstalled
71     public static final int OP_UNAVAILABLE = 4; // external media unmounted
72     public static final int OP_SUSPEND = 5; // package suspended
73     public static final int OP_UNSUSPEND = 6; // package unsuspended
74     public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
75 
76     private final int mOp;
77     private final UserHandle mUser;
78     private final String[] mPackages;
79 
PackageUpdatedTask(int op, UserHandle user, String... packages)80     public PackageUpdatedTask(int op, UserHandle user, String... packages) {
81         mOp = op;
82         mUser = user;
83         mPackages = packages;
84     }
85 
86     @Override
execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList)87     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
88         final Context context = app.getContext();
89         final IconCache iconCache = app.getIconCache();
90 
91         final String[] packages = mPackages;
92         final int N = packages.length;
93         FlagOp flagOp = FlagOp.NO_OP;
94         final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
95         ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
96         switch (mOp) {
97             case OP_ADD: {
98                 for (int i = 0; i < N; i++) {
99                     if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
100                     iconCache.updateIconsForPkg(packages[i], mUser);
101                     if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
102                         appsList.removePackage(packages[i], Process.myUserHandle());
103                     }
104                     appsList.addPackage(context, packages[i], mUser);
105 
106                     // Automatically add homescreen icon for work profile apps for below O device.
107                     if (!Utilities.ATLEAST_OREO && !Process.myUserHandle().equals(mUser)) {
108                         SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser);
109                     }
110                 }
111                 flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
112                 break;
113             }
114             case OP_UPDATE:
115                 for (int i = 0; i < N; i++) {
116                     if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
117                     iconCache.updateIconsForPkg(packages[i], mUser);
118                     appsList.updatePackage(context, packages[i], mUser);
119                     app.getWidgetCache().removePackage(packages[i], mUser);
120                 }
121                 // Since package was just updated, the target must be available now.
122                 flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
123                 break;
124             case OP_REMOVE: {
125                 for (int i = 0; i < N; i++) {
126                     iconCache.removeIconsForPkg(packages[i], mUser);
127                 }
128                 // Fall through
129             }
130             case OP_UNAVAILABLE:
131                 for (int i = 0; i < N; i++) {
132                     if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
133                     appsList.removePackage(packages[i], mUser);
134                     app.getWidgetCache().removePackage(packages[i], mUser);
135                 }
136                 flagOp = FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
137                 break;
138             case OP_SUSPEND:
139             case OP_UNSUSPEND:
140                 flagOp = mOp == OP_SUSPEND ?
141                         FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_SUSPENDED) :
142                         FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_SUSPENDED);
143                 if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
144                 appsList.updateDisabledFlags(matcher, flagOp);
145                 break;
146             case OP_USER_AVAILABILITY_CHANGE:
147                 flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
148                         ? FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER)
149                         : FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER);
150                 // We want to update all packages for this user.
151                 matcher = ItemInfoMatcher.ofUser(mUser);
152                 appsList.updateDisabledFlags(matcher, flagOp);
153                 break;
154         }
155 
156         final ArrayList<AppInfo> addedOrModified = new ArrayList<>();
157         addedOrModified.addAll(appsList.added);
158         appsList.added.clear();
159         addedOrModified.addAll(appsList.modified);
160         appsList.modified.clear();
161         if (!addedOrModified.isEmpty()) {
162             scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified));
163         }
164 
165         final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed);
166         appsList.removed.clear();
167         final HashSet<ComponentName> removedComponents = new HashSet<>();
168         if (mOp == OP_UPDATE) {
169             for (AppInfo ai : removedApps) {
170                 removedComponents.add(ai.componentName);
171             }
172         }
173 
174         final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
175 
176         // Update shortcut infos
177         if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
178             final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
179             final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
180 
181             // For system apps, package manager send OP_UPDATE when an app is enabled.
182             final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
183             synchronized (dataModel) {
184                 for (ItemInfo info : dataModel.itemsIdMap) {
185                     if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
186                         WorkspaceItemInfo si = (WorkspaceItemInfo) info;
187                         boolean infoUpdated = false;
188                         boolean shortcutUpdated = false;
189 
190                         // Update shortcuts which use iconResource.
191                         if ((si.iconResource != null)
192                                 && packageSet.contains(si.iconResource.packageName)) {
193                             LauncherIcons li = LauncherIcons.obtain(context);
194                             BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
195                             li.recycle();
196                             if (iconInfo != null) {
197                                 si.applyFrom(iconInfo);
198                                 infoUpdated = true;
199                             }
200                         }
201 
202                         ComponentName cn = si.getTargetComponent();
203                         if (cn != null && matcher.matches(si, cn)) {
204                             String packageName = cn.getPackageName();
205 
206                             if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
207                                 removedShortcuts.put(si.id, false);
208                                 if (mOp == OP_REMOVE) {
209                                     continue;
210                                 }
211                             }
212 
213                             if (si.isPromise() && isNewApkAvailable) {
214                                 boolean isTargetValid = true;
215                                 if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
216                                     List<ShortcutInfo> shortcut = DeepShortcutManager
217                                             .getInstance(context).queryForPinnedShortcuts(
218                                                     cn.getPackageName(),
219                                                     Arrays.asList(si.getDeepShortcutId()), mUser);
220                                     if (shortcut.isEmpty()) {
221                                         isTargetValid = false;
222                                     } else {
223                                         si.updateFromDeepShortcutInfo(shortcut.get(0), context);
224                                         infoUpdated = true;
225                                     }
226                                 } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
227                                     isTargetValid = LauncherAppsCompat.getInstance(context)
228                                             .isActivityEnabledForProfile(cn, mUser);
229                                 }
230                                 if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)
231                                         && !isTargetValid) {
232                                     if (updateWorkspaceItemIntent(context, si, packageName)) {
233                                         infoUpdated = true;
234                                     } else if (si.hasPromiseIconUi()) {
235                                         removedShortcuts.put(si.id, true);
236                                         continue;
237                                     }
238                                 } else if (!isTargetValid) {
239                                     removedShortcuts.put(si.id, true);
240                                     FileLog.e(TAG, "Restored shortcut no longer valid "
241                                             + si.intent);
242                                     continue;
243                                 } else {
244                                     si.status = WorkspaceItemInfo.DEFAULT;
245                                     infoUpdated = true;
246                                 }
247                             } else if (isNewApkAvailable && removedComponents.contains(cn)) {
248                                 if (updateWorkspaceItemIntent(context, si, packageName)) {
249                                     infoUpdated = true;
250                                 }
251                             }
252 
253                             if (isNewApkAvailable &&
254                                     si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
255                                 iconCache.getTitleAndIcon(si, si.usingLowResIcon());
256                                 infoUpdated = true;
257                             }
258 
259                             int oldRuntimeFlags = si.runtimeStatusFlags;
260                             si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
261                             if (si.runtimeStatusFlags != oldRuntimeFlags) {
262                                 shortcutUpdated = true;
263                             }
264                         }
265 
266                         if (infoUpdated || shortcutUpdated) {
267                             updatedWorkspaceItems.add(si);
268                         }
269                         if (infoUpdated) {
270                             getModelWriter().updateItemInDatabase(si);
271                         }
272                     } else if (info instanceof LauncherAppWidgetInfo && isNewApkAvailable) {
273                         LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
274                         if (mUser.equals(widgetInfo.user)
275                                 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
276                                 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
277                             widgetInfo.restoreStatus &=
278                                     ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
279                                             ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
280 
281                             // adding this flag ensures that launcher shows 'click to setup'
282                             // if the widget has a config activity. In case there is no config
283                             // activity, it will be marked as 'restored' during bind.
284                             widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
285 
286                             widgets.add(widgetInfo);
287                             getModelWriter().updateItemInDatabase(widgetInfo);
288                         }
289                     }
290                 }
291             }
292 
293             bindUpdatedWorkspaceItems(updatedWorkspaceItems);
294             if (!removedShortcuts.isEmpty()) {
295                 deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts, false));
296             }
297 
298             if (!widgets.isEmpty()) {
299                 scheduleCallbackTask(new CallbackTask() {
300                     @Override
301                     public void execute(Callbacks callbacks) {
302                         callbacks.bindWidgetsRestored(widgets);
303                     }
304                 });
305             }
306         }
307 
308         final HashSet<String> removedPackages = new HashSet<>();
309         if (mOp == OP_REMOVE) {
310             // Mark all packages in the broadcast to be removed
311             Collections.addAll(removedPackages, packages);
312 
313             // No need to update the removedComponents as
314             // removedPackages is a super-set of removedComponents
315         } else if (mOp == OP_UPDATE) {
316             // Mark disabled packages in the broadcast to be removed
317             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
318             for (int i=0; i<N; i++) {
319                 if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
320                     removedPackages.add(packages[i]);
321                 }
322             }
323         }
324 
325         if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
326             ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
327                     .or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
328                     .and(ItemInfoMatcher.ofItemIds(removedShortcuts, true));
329             deleteAndBindComponentsRemoved(removeMatch);
330 
331             // Remove any queued items from the install queue
332             InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
333         }
334 
335         if (!removedApps.isEmpty()) {
336             // Remove corresponding apps from All-Apps
337             scheduleCallbackTask(new CallbackTask() {
338                 @Override
339                 public void execute(Callbacks callbacks) {
340                     callbacks.bindAppInfosRemoved(removedApps);
341                 }
342             });
343         }
344 
345         if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
346             // Load widgets for the new package. Changes due to app updates are handled through
347             // AppWidgetHost events, this is just to initialize the long-press options.
348             for (int i = 0; i < N; i++) {
349                 dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
350             }
351             bindUpdatedWidgets(dataModel);
352         }
353     }
354 
355     /**
356      * Updates {@param si}'s intent to point to a new ComponentName.
357      * @return Whether the shortcut intent was changed.
358      */
updateWorkspaceItemIntent(Context context, WorkspaceItemInfo si, String packageName)359     private boolean updateWorkspaceItemIntent(Context context,
360             WorkspaceItemInfo si, String packageName) {
361         // Try to find the best match activity.
362         Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
363         if (intent != null) {
364             si.intent = intent;
365             si.status = WorkspaceItemInfo.DEFAULT;
366             return true;
367         }
368         return false;
369     }
370 }
371