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 */ 16package com.android.launcher3.model; 17 18import android.content.ComponentName; 19import android.content.Context; 20import android.content.Intent; 21import android.graphics.Bitmap; 22import android.os.Process; 23import android.os.UserHandle; 24import android.util.ArrayMap; 25import android.util.Log; 26 27import com.android.launcher3.AllAppsList; 28import com.android.launcher3.AppInfo; 29import com.android.launcher3.IconCache; 30import com.android.launcher3.InstallShortcutReceiver; 31import com.android.launcher3.ItemInfo; 32import com.android.launcher3.LauncherAppState; 33import com.android.launcher3.LauncherAppWidgetInfo; 34import com.android.launcher3.LauncherModel.CallbackTask; 35import com.android.launcher3.LauncherModel.Callbacks; 36import com.android.launcher3.LauncherSettings.Favorites; 37import com.android.launcher3.SessionCommitReceiver; 38import com.android.launcher3.ShortcutInfo; 39import com.android.launcher3.Utilities; 40import com.android.launcher3.compat.LauncherAppsCompat; 41import com.android.launcher3.compat.UserManagerCompat; 42import com.android.launcher3.config.FeatureFlags; 43import com.android.launcher3.graphics.BitmapInfo; 44import com.android.launcher3.graphics.LauncherIcons; 45import com.android.launcher3.util.FlagOp; 46import com.android.launcher3.util.ItemInfoMatcher; 47import com.android.launcher3.util.LongArrayMap; 48import com.android.launcher3.util.PackageManagerHelper; 49import com.android.launcher3.util.PackageUserKey; 50 51import java.util.ArrayList; 52import java.util.Arrays; 53import java.util.Collections; 54import 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 */ 60public 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 78 public PackageUpdatedTask(int op, UserHandle user, String... packages) { 79 mOp = op; 80 mUser = user; 81 mPackages = packages; 82 } 83 84 @Override 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