Home | History | Annotate | Download | only in model
      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 
     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