Home | History | Annotate | Download | only in pm
      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.server.pm;
     17 
     18 import android.annotation.NonNull;
     19 import android.annotation.Nullable;
     20 import android.annotation.UserIdInt;
     21 import android.content.ComponentName;
     22 import android.content.pm.ShortcutManager;
     23 import android.text.TextUtils;
     24 import android.text.format.Formatter;
     25 import android.util.ArrayMap;
     26 import android.util.Slog;
     27 import android.util.SparseArray;
     28 
     29 import com.android.internal.annotations.GuardedBy;
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.internal.util.Preconditions;
     32 
     33 import libcore.util.Objects;
     34 
     35 import org.json.JSONArray;
     36 import org.json.JSONException;
     37 import org.json.JSONObject;
     38 import org.xmlpull.v1.XmlPullParser;
     39 import org.xmlpull.v1.XmlPullParserException;
     40 import org.xmlpull.v1.XmlSerializer;
     41 
     42 import java.io.File;
     43 import java.io.IOException;
     44 import java.io.PrintWriter;
     45 import java.util.function.Consumer;
     46 
     47 /**
     48  * User information used by {@link ShortcutService}.
     49  *
     50  * All methods should be guarded by {@code #mService.mLock}.
     51  */
     52 class ShortcutUser {
     53     private static final String TAG = ShortcutService.TAG;
     54 
     55     static final String TAG_ROOT = "user";
     56     private static final String TAG_LAUNCHER = "launcher";
     57 
     58     private static final String ATTR_VALUE = "value";
     59     private static final String ATTR_KNOWN_LOCALES = "locales";
     60 
     61     // Suffix "2" was added to force rescan all packages after the next OTA.
     62     private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2";
     63     private static final String KEY_USER_ID = "userId";
     64     private static final String KEY_LAUNCHERS = "launchers";
     65     private static final String KEY_PACKAGES = "packages";
     66 
     67     static final class PackageWithUser {
     68         final int userId;
     69         final String packageName;
     70 
     71         private PackageWithUser(int userId, String packageName) {
     72             this.userId = userId;
     73             this.packageName = Preconditions.checkNotNull(packageName);
     74         }
     75 
     76         public static PackageWithUser of(int userId, String packageName) {
     77             return new PackageWithUser(userId, packageName);
     78         }
     79 
     80         public static PackageWithUser of(ShortcutPackageItem spi) {
     81             return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
     82         }
     83 
     84         @Override
     85         public int hashCode() {
     86             return packageName.hashCode() ^ userId;
     87         }
     88 
     89         @Override
     90         public boolean equals(Object obj) {
     91             if (!(obj instanceof PackageWithUser)) {
     92                 return false;
     93             }
     94             final PackageWithUser that = (PackageWithUser) obj;
     95 
     96             return userId == that.userId && packageName.equals(that.packageName);
     97         }
     98 
     99         @Override
    100         public String toString() {
    101             return String.format("[Package: %d, %s]", userId, packageName);
    102         }
    103     }
    104 
    105     final ShortcutService mService;
    106 
    107     @UserIdInt
    108     private final int mUserId;
    109 
    110     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
    111 
    112     private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
    113 
    114     /**
    115      * Last known launcher.  It's used when the default launcher isn't set in PM -- i.e.
    116      * when getHomeActivitiesAsUser() return null.  We need it so that in this situation the
    117      * previously default launcher can still access shortcuts.
    118      */
    119     private ComponentName mLastKnownLauncher;
    120 
    121     /** In-memory-cached default launcher. */
    122     private ComponentName mCachedLauncher;
    123 
    124     private String mKnownLocales;
    125 
    126     private long mLastAppScanTime;
    127 
    128     public ShortcutUser(ShortcutService service, int userId) {
    129         mService = service;
    130         mUserId = userId;
    131     }
    132 
    133     public int getUserId() {
    134         return mUserId;
    135     }
    136 
    137     public long getLastAppScanTime() {
    138         return mLastAppScanTime;
    139     }
    140 
    141     public void setLastAppScanTime(long lastAppScanTime) {
    142         mLastAppScanTime = lastAppScanTime;
    143     }
    144 
    145     // We don't expose this directly to non-test code because only ShortcutUser should add to/
    146     // remove from it.
    147     @VisibleForTesting
    148     ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
    149         return mPackages;
    150     }
    151 
    152     public boolean hasPackage(@NonNull String packageName) {
    153         return mPackages.containsKey(packageName);
    154     }
    155 
    156     public ShortcutPackage removePackage(@NonNull String packageName) {
    157         final ShortcutPackage removed = mPackages.remove(packageName);
    158 
    159         mService.cleanupBitmapsForPackage(mUserId, packageName);
    160 
    161         return removed;
    162     }
    163 
    164     // We don't expose this directly to non-test code because only ShortcutUser should add to/
    165     // remove from it.
    166     @VisibleForTesting
    167     ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
    168         return mLaunchers;
    169     }
    170 
    171     public void addLauncher(ShortcutLauncher launcher) {
    172         mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
    173                 launcher.getPackageName()), launcher);
    174     }
    175 
    176     @Nullable
    177     public ShortcutLauncher removeLauncher(
    178             @UserIdInt int packageUserId, @NonNull String packageName) {
    179         return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
    180     }
    181 
    182     @Nullable
    183     public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
    184         final ShortcutPackage ret = mPackages.get(packageName);
    185         if (ret != null) {
    186             ret.attemptToRestoreIfNeededAndSave();
    187         }
    188         return ret;
    189     }
    190 
    191     @NonNull
    192     public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
    193         ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
    194         if (ret == null) {
    195             ret = new ShortcutPackage(this, mUserId, packageName);
    196             mPackages.put(packageName, ret);
    197         }
    198         return ret;
    199     }
    200 
    201     @NonNull
    202     public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
    203             @UserIdInt int launcherUserId) {
    204         final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
    205         ShortcutLauncher ret = mLaunchers.get(key);
    206         if (ret == null) {
    207             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
    208             mLaunchers.put(key, ret);
    209         } else {
    210             ret.attemptToRestoreIfNeededAndSave();
    211         }
    212         return ret;
    213     }
    214 
    215     public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
    216         final int size = mPackages.size();
    217         for (int i = 0; i < size; i++) {
    218             callback.accept(mPackages.valueAt(i));
    219         }
    220     }
    221 
    222     public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
    223         final int size = mLaunchers.size();
    224         for (int i = 0; i < size; i++) {
    225             callback.accept(mLaunchers.valueAt(i));
    226         }
    227     }
    228 
    229     public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
    230         forAllLaunchers(callback);
    231         forAllPackages(callback);
    232     }
    233 
    234     public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
    235             Consumer<ShortcutPackageItem> callback) {
    236         forAllPackageItems(spi -> {
    237             if ((spi.getPackageUserId() == packageUserId)
    238                     && spi.getPackageName().equals(packageName)) {
    239                 callback.accept(spi);
    240             }
    241         });
    242     }
    243 
    244     /**
    245      * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the
    246      * information on the package is up-to-date.
    247      *
    248      * We use broadcasts to handle locale changes and package changes, but because broadcasts
    249      * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event
    250      * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast.
    251      *
    252      * So we call this method at all entry points from publishers to make sure we update all
    253      * relevant information.
    254      *
    255      * Similar inconsistencies can happen when the launcher fetches shortcut information, but
    256      * that's a less of an issue because for the launcher we report shortcut changes with
    257      * callbacks.
    258      */
    259     public void onCalledByPublisher(@NonNull String packageName) {
    260         detectLocaleChange();
    261         rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
    262     }
    263 
    264     private String getKnownLocales() {
    265         if (TextUtils.isEmpty(mKnownLocales)) {
    266             mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
    267             mService.scheduleSaveUser(mUserId);
    268         }
    269         return mKnownLocales;
    270     }
    271 
    272     /**
    273      * Check to see if the system locale has changed, and if so, reset throttling
    274      * and update resource strings.
    275      */
    276     public void detectLocaleChange() {
    277         final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
    278         if (getKnownLocales().equals(currentLocales)) {
    279             return;
    280         }
    281         if (ShortcutService.DEBUG) {
    282             Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
    283                     + " for user " + mUserId);
    284         }
    285         mKnownLocales = currentLocales;
    286 
    287         forAllPackages(pkg -> {
    288             pkg.resetRateLimiting();
    289             pkg.resolveResourceStrings();
    290         });
    291 
    292         mService.scheduleSaveUser(mUserId);
    293     }
    294 
    295     public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
    296         final boolean isNewApp = !mPackages.containsKey(packageName);
    297 
    298         final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
    299 
    300         if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
    301             if (isNewApp) {
    302                 mPackages.remove(packageName);
    303             }
    304         }
    305     }
    306 
    307     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
    308             @UserIdInt int packageUserId) {
    309         forPackageItem(packageName, packageUserId, spi -> {
    310             spi.attemptToRestoreIfNeededAndSave();
    311         });
    312     }
    313 
    314     public void saveToXml(XmlSerializer out, boolean forBackup)
    315             throws IOException, XmlPullParserException {
    316         out.startTag(null, TAG_ROOT);
    317 
    318         ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
    319         ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
    320                 mLastAppScanTime);
    321 
    322         ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
    323 
    324         // Can't use forEachPackageItem due to the checked exceptions.
    325         {
    326             final int size = mLaunchers.size();
    327             for (int i = 0; i < size; i++) {
    328                 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
    329             }
    330         }
    331         {
    332             final int size = mPackages.size();
    333             for (int i = 0; i < size; i++) {
    334                 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
    335             }
    336         }
    337 
    338         out.endTag(null, TAG_ROOT);
    339     }
    340 
    341     private void saveShortcutPackageItem(XmlSerializer out,
    342             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
    343         if (forBackup) {
    344             if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
    345                 return; // Don't save.
    346             }
    347             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
    348                 return; // Don't save cross-user information.
    349             }
    350         }
    351         spi.saveToXml(out, forBackup);
    352     }
    353 
    354     public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
    355             boolean fromBackup) throws IOException, XmlPullParserException {
    356         final ShortcutUser ret = new ShortcutUser(s, userId);
    357 
    358         ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
    359                 ATTR_KNOWN_LOCALES);
    360 
    361         // If lastAppScanTime is in the future, that means the clock went backwards.
    362         // Just scan all apps again.
    363         final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
    364                 ATTR_LAST_APP_SCAN_TIME);
    365         final long currentTime = s.injectCurrentTimeMillis();
    366         ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
    367 
    368         final int outerDepth = parser.getDepth();
    369         int type;
    370         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    371                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    372             if (type != XmlPullParser.START_TAG) {
    373                 continue;
    374             }
    375             final int depth = parser.getDepth();
    376             final String tag = parser.getName();
    377 
    378             if (depth == outerDepth + 1) {
    379                 switch (tag) {
    380                     case TAG_LAUNCHER: {
    381                         ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
    382                                 parser, ATTR_VALUE);
    383                         continue;
    384                     }
    385                     case ShortcutPackage.TAG_ROOT: {
    386                         final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
    387                                 s, ret, parser, fromBackup);
    388 
    389                         // Don't use addShortcut(), we don't need to save the icon.
    390                         ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
    391                         continue;
    392                     }
    393 
    394                     case ShortcutLauncher.TAG_ROOT: {
    395                         ret.addLauncher(
    396                                 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
    397                         continue;
    398                     }
    399                 }
    400             }
    401             ShortcutService.warnForInvalidTag(depth, tag);
    402         }
    403         return ret;
    404     }
    405 
    406     public ComponentName getLastKnownLauncher() {
    407         return mLastKnownLauncher;
    408     }
    409 
    410     public void setLauncher(ComponentName launcherComponent) {
    411         setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
    412     }
    413 
    414     /** Clears the launcher information without clearing the last known one */
    415     public void clearLauncher() {
    416         setLauncher(null);
    417     }
    418 
    419     /**
    420      * Clears the launcher information *with(* clearing the last known one; we do this witl
    421      * "cmd shortcut clear-default-launcher".
    422      */
    423     public void forceClearLauncher() {
    424         setLauncher(null, /* allowPurgeLastKnown */ true);
    425     }
    426 
    427     private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
    428         mCachedLauncher = launcherComponent; // Always update the in-memory cache.
    429 
    430         if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
    431             return;
    432         }
    433         if (!allowPurgeLastKnown && launcherComponent == null) {
    434             return;
    435         }
    436         mLastKnownLauncher = launcherComponent;
    437         mService.scheduleSaveUser(mUserId);
    438     }
    439 
    440     public ComponentName getCachedLauncher() {
    441         return mCachedLauncher;
    442     }
    443 
    444     public void resetThrottling() {
    445         for (int i = mPackages.size() - 1; i >= 0; i--) {
    446             mPackages.valueAt(i).resetThrottling();
    447         }
    448     }
    449 
    450     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
    451         pw.print(prefix);
    452         pw.print("User: ");
    453         pw.print(mUserId);
    454         pw.print("  Known locales: ");
    455         pw.print(mKnownLocales);
    456         pw.print("  Last app scan: [");
    457         pw.print(mLastAppScanTime);
    458         pw.print("] ");
    459         pw.print(ShortcutService.formatTime(mLastAppScanTime));
    460         pw.println();
    461 
    462         prefix += prefix + "  ";
    463 
    464         pw.print(prefix);
    465         pw.print("Cached launcher: ");
    466         pw.print(mCachedLauncher);
    467         pw.println();
    468 
    469         pw.print(prefix);
    470         pw.print("Last known launcher: ");
    471         pw.print(mLastKnownLauncher);
    472         pw.println();
    473 
    474         for (int i = 0; i < mLaunchers.size(); i++) {
    475             mLaunchers.valueAt(i).dump(pw, prefix);
    476         }
    477 
    478         for (int i = 0; i < mPackages.size(); i++) {
    479             mPackages.valueAt(i).dump(pw, prefix);
    480         }
    481 
    482         pw.println();
    483         pw.print(prefix);
    484         pw.println("Bitmap directories: ");
    485         dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
    486     }
    487 
    488     private void dumpDirectorySize(@NonNull PrintWriter pw,
    489             @NonNull String prefix, File path) {
    490         int numFiles = 0;
    491         long size = 0;
    492         final File[] children = path.listFiles();
    493         if (children != null) {
    494             for (File child : path.listFiles()) {
    495                 if (child.isFile()) {
    496                     numFiles++;
    497                     size += child.length();
    498                 } else if (child.isDirectory()) {
    499                     dumpDirectorySize(pw, prefix + "  ", child);
    500                 }
    501             }
    502         }
    503         pw.print(prefix);
    504         pw.print("Path: ");
    505         pw.print(path.getName());
    506         pw.print("/ has ");
    507         pw.print(numFiles);
    508         pw.print(" files, size=");
    509         pw.print(size);
    510         pw.print(" (");
    511         pw.print(Formatter.formatFileSize(mService.mContext, size));
    512         pw.println(")");
    513     }
    514 
    515     public JSONObject dumpCheckin(boolean clear) throws JSONException {
    516         final JSONObject result = new JSONObject();
    517 
    518         result.put(KEY_USER_ID, mUserId);
    519 
    520         {
    521             final JSONArray launchers = new JSONArray();
    522             for (int i = 0; i < mLaunchers.size(); i++) {
    523                 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
    524             }
    525             result.put(KEY_LAUNCHERS, launchers);
    526         }
    527 
    528         {
    529             final JSONArray packages = new JSONArray();
    530             for (int i = 0; i < mPackages.size(); i++) {
    531                 packages.put(mPackages.valueAt(i).dumpCheckin(clear));
    532             }
    533             result.put(KEY_PACKAGES, packages);
    534         }
    535 
    536         return result;
    537     }
    538 }
    539