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.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