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