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