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