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.Log;
     27 import android.util.Slog;
     28 
     29 import com.android.internal.annotations.VisibleForTesting;
     30 import com.android.internal.util.Preconditions;
     31 import com.android.server.pm.ShortcutService.InvalidFileFormatException;
     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 ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp";
     64     private static final String KEY_USER_ID = "userId";
     65     private static final String KEY_LAUNCHERS = "launchers";
     66     private static final String KEY_PACKAGES = "packages";
     67 
     68     static final class PackageWithUser {
     69         final int userId;
     70         final String packageName;
     71 
     72         private PackageWithUser(int userId, String packageName) {
     73             this.userId = userId;
     74             this.packageName = Preconditions.checkNotNull(packageName);
     75         }
     76 
     77         public static PackageWithUser of(int userId, String packageName) {
     78             return new PackageWithUser(userId, packageName);
     79         }
     80 
     81         public static PackageWithUser of(ShortcutPackageItem spi) {
     82             return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
     83         }
     84 
     85         @Override
     86         public int hashCode() {
     87             return packageName.hashCode() ^ userId;
     88         }
     89 
     90         @Override
     91         public boolean equals(Object obj) {
     92             if (!(obj instanceof PackageWithUser)) {
     93                 return false;
     94             }
     95             final PackageWithUser that = (PackageWithUser) obj;
     96 
     97             return userId == that.userId && packageName.equals(that.packageName);
     98         }
     99 
    100         @Override
    101         public String toString() {
    102             return String.format("[Package: %d, %s]", userId, packageName);
    103         }
    104     }
    105 
    106     final ShortcutService mService;
    107 
    108     @UserIdInt
    109     private final int mUserId;
    110 
    111     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
    112 
    113     private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
    114 
    115     /**
    116      * Last known launcher.  It's used when the default launcher isn't set in PM -- i.e.
    117      * when getHomeActivitiesAsUser() return null.  We need it so that in this situation the
    118      * previously default launcher can still access shortcuts.
    119      */
    120     private ComponentName mLastKnownLauncher;
    121 
    122     /** In-memory-cached default launcher. */
    123     private ComponentName mCachedLauncher;
    124 
    125     private String mKnownLocales;
    126 
    127     private long mLastAppScanTime;
    128 
    129     private String mLastAppScanOsFingerprint;
    130 
    131     public ShortcutUser(ShortcutService service, int userId) {
    132         mService = service;
    133         mUserId = userId;
    134     }
    135 
    136     public int getUserId() {
    137         return mUserId;
    138     }
    139 
    140     public long getLastAppScanTime() {
    141         return mLastAppScanTime;
    142     }
    143 
    144     public void setLastAppScanTime(long lastAppScanTime) {
    145         mLastAppScanTime = lastAppScanTime;
    146     }
    147 
    148     public String getLastAppScanOsFingerprint() {
    149         return mLastAppScanOsFingerprint;
    150     }
    151 
    152     public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) {
    153         mLastAppScanOsFingerprint = lastAppScanOsFingerprint;
    154     }
    155 
    156     // We don't expose this directly to non-test code because only ShortcutUser should add to/
    157     // remove from it.
    158     @VisibleForTesting
    159     ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
    160         return mPackages;
    161     }
    162 
    163     public boolean hasPackage(@NonNull String packageName) {
    164         return mPackages.containsKey(packageName);
    165     }
    166 
    167     private void addPackage(@NonNull ShortcutPackage p) {
    168         p.replaceUser(this);
    169         mPackages.put(p.getPackageName(), p);
    170     }
    171 
    172     public ShortcutPackage removePackage(@NonNull String packageName) {
    173         final ShortcutPackage removed = mPackages.remove(packageName);
    174 
    175         mService.cleanupBitmapsForPackage(mUserId, packageName);
    176 
    177         return removed;
    178     }
    179 
    180     // We don't expose this directly to non-test code because only ShortcutUser should add to/
    181     // remove from it.
    182     @VisibleForTesting
    183     ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
    184         return mLaunchers;
    185     }
    186 
    187     private void addLauncher(ShortcutLauncher launcher) {
    188         launcher.replaceUser(this);
    189         mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
    190                 launcher.getPackageName()), launcher);
    191     }
    192 
    193     @Nullable
    194     public ShortcutLauncher removeLauncher(
    195             @UserIdInt int packageUserId, @NonNull String packageName) {
    196         return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
    197     }
    198 
    199     @Nullable
    200     public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
    201         final ShortcutPackage ret = mPackages.get(packageName);
    202         if (ret != null) {
    203             ret.attemptToRestoreIfNeededAndSave();
    204         }
    205         return ret;
    206     }
    207 
    208     @NonNull
    209     public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
    210         ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
    211         if (ret == null) {
    212             ret = new ShortcutPackage(this, mUserId, packageName);
    213             mPackages.put(packageName, ret);
    214         }
    215         return ret;
    216     }
    217 
    218     @NonNull
    219     public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
    220             @UserIdInt int launcherUserId) {
    221         final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
    222         ShortcutLauncher ret = mLaunchers.get(key);
    223         if (ret == null) {
    224             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
    225             mLaunchers.put(key, ret);
    226         } else {
    227             ret.attemptToRestoreIfNeededAndSave();
    228         }
    229         return ret;
    230     }
    231 
    232     public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
    233         final int size = mPackages.size();
    234         for (int i = 0; i < size; i++) {
    235             callback.accept(mPackages.valueAt(i));
    236         }
    237     }
    238 
    239     public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
    240         final int size = mLaunchers.size();
    241         for (int i = 0; i < size; i++) {
    242             callback.accept(mLaunchers.valueAt(i));
    243         }
    244     }
    245 
    246     public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
    247         forAllLaunchers(callback);
    248         forAllPackages(callback);
    249     }
    250 
    251     public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
    252             Consumer<ShortcutPackageItem> callback) {
    253         forAllPackageItems(spi -> {
    254             if ((spi.getPackageUserId() == packageUserId)
    255                     && spi.getPackageName().equals(packageName)) {
    256                 callback.accept(spi);
    257             }
    258         });
    259     }
    260 
    261     /**
    262      * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the
    263      * information on the package is up-to-date.
    264      *
    265      * We use broadcasts to handle locale changes and package changes, but because broadcasts
    266      * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event
    267      * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast.
    268      *
    269      * So we call this method at all entry points from publishers to make sure we update all
    270      * relevant information.
    271      *
    272      * Similar inconsistencies can happen when the launcher fetches shortcut information, but
    273      * that's a less of an issue because for the launcher we report shortcut changes with
    274      * callbacks.
    275      */
    276     public void onCalledByPublisher(@NonNull String packageName) {
    277         detectLocaleChange();
    278         rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
    279     }
    280 
    281     private String getKnownLocales() {
    282         if (TextUtils.isEmpty(mKnownLocales)) {
    283             mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
    284             mService.scheduleSaveUser(mUserId);
    285         }
    286         return mKnownLocales;
    287     }
    288 
    289     /**
    290      * Check to see if the system locale has changed, and if so, reset throttling
    291      * and update resource strings.
    292      */
    293     public void detectLocaleChange() {
    294         final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
    295         if (getKnownLocales().equals(currentLocales)) {
    296             return;
    297         }
    298         if (ShortcutService.DEBUG) {
    299             Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
    300                     + " for user " + mUserId);
    301         }
    302         mKnownLocales = currentLocales;
    303 
    304         forAllPackages(pkg -> {
    305             pkg.resetRateLimiting();
    306             pkg.resolveResourceStrings();
    307         });
    308 
    309         mService.scheduleSaveUser(mUserId);
    310     }
    311 
    312     public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
    313         final boolean isNewApp = !mPackages.containsKey(packageName);
    314 
    315         final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
    316 
    317         if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
    318             if (isNewApp) {
    319                 mPackages.remove(packageName);
    320             }
    321         }
    322     }
    323 
    324     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
    325             @UserIdInt int packageUserId) {
    326         forPackageItem(packageName, packageUserId, spi -> {
    327             spi.attemptToRestoreIfNeededAndSave();
    328         });
    329     }
    330 
    331     public void saveToXml(XmlSerializer out, boolean forBackup)
    332             throws IOException, XmlPullParserException {
    333         out.startTag(null, TAG_ROOT);
    334 
    335         if (!forBackup) {
    336             // Don't have to back them up.
    337             ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
    338             ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
    339                     mLastAppScanTime);
    340             ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
    341                     mLastAppScanOsFingerprint);
    342 
    343             ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
    344         }
    345 
    346         // Can't use forEachPackageItem due to the checked exceptions.
    347         {
    348             final int size = mLaunchers.size();
    349             for (int i = 0; i < size; i++) {
    350                 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
    351             }
    352         }
    353         {
    354             final int size = mPackages.size();
    355             for (int i = 0; i < size; i++) {
    356                 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
    357             }
    358         }
    359 
    360         out.endTag(null, TAG_ROOT);
    361     }
    362 
    363     private void saveShortcutPackageItem(XmlSerializer out,
    364             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
    365         if (forBackup) {
    366             if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
    367                 return; // Don't save.
    368             }
    369             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
    370                 return; // Don't save cross-user information.
    371             }
    372         }
    373         spi.saveToXml(out, forBackup);
    374     }
    375 
    376     public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
    377             boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException {
    378         final ShortcutUser ret = new ShortcutUser(s, userId);
    379 
    380         try {
    381             ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
    382                     ATTR_KNOWN_LOCALES);
    383 
    384             // If lastAppScanTime is in the future, that means the clock went backwards.
    385             // Just scan all apps again.
    386             final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
    387                     ATTR_LAST_APP_SCAN_TIME);
    388             final long currentTime = s.injectCurrentTimeMillis();
    389             ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
    390             ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
    391                     ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
    392             final int outerDepth = parser.getDepth();
    393             int type;
    394             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    395                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    396                 if (type != XmlPullParser.START_TAG) {
    397                     continue;
    398                 }
    399                 final int depth = parser.getDepth();
    400                 final String tag = parser.getName();
    401 
    402                 if (depth == outerDepth + 1) {
    403                     switch (tag) {
    404                         case TAG_LAUNCHER: {
    405                             ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
    406                                     parser, ATTR_VALUE);
    407                             continue;
    408                         }
    409                         case ShortcutPackage.TAG_ROOT: {
    410                             final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
    411                                     s, ret, parser, fromBackup);
    412 
    413                             // Don't use addShortcut(), we don't need to save the icon.
    414                             ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
    415                             continue;
    416                         }
    417 
    418                         case ShortcutLauncher.TAG_ROOT: {
    419                             ret.addLauncher(
    420                                     ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
    421                             continue;
    422                         }
    423                     }
    424                 }
    425                 ShortcutService.warnForInvalidTag(depth, tag);
    426             }
    427         } catch (RuntimeException e) {
    428             throw new ShortcutService.InvalidFileFormatException(
    429                     "Unable to parse file", e);
    430         }
    431         return ret;
    432     }
    433 
    434     public ComponentName getLastKnownLauncher() {
    435         return mLastKnownLauncher;
    436     }
    437 
    438     public void setLauncher(ComponentName launcherComponent) {
    439         setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
    440     }
    441 
    442     /** Clears the launcher information without clearing the last known one */
    443     public void clearLauncher() {
    444         setLauncher(null);
    445     }
    446 
    447     /**
    448      * Clears the launcher information *with(* clearing the last known one; we do this witl
    449      * "cmd shortcut clear-default-launcher".
    450      */
    451     public void forceClearLauncher() {
    452         setLauncher(null, /* allowPurgeLastKnown */ true);
    453     }
    454 
    455     private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
    456         mCachedLauncher = launcherComponent; // Always update the in-memory cache.
    457 
    458         if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
    459             return;
    460         }
    461         if (!allowPurgeLastKnown && launcherComponent == null) {
    462             return;
    463         }
    464         mLastKnownLauncher = launcherComponent;
    465         mService.scheduleSaveUser(mUserId);
    466     }
    467 
    468     public ComponentName getCachedLauncher() {
    469         return mCachedLauncher;
    470     }
    471 
    472     public void resetThrottling() {
    473         for (int i = mPackages.size() - 1; i >= 0; i--) {
    474             mPackages.valueAt(i).resetThrottling();
    475         }
    476     }
    477 
    478     public void mergeRestoredFile(ShortcutUser restored) {
    479         final ShortcutService s = mService;
    480         // Note, a restore happens only at the end of setup wizard.  At this point, no apps are
    481         // installed from Play Store yet, but it's still possible that system apps have already
    482         // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED.
    483         // When such a system app has allowbackup=true, then we go ahead and replace all existing
    484         // shortcuts with the restored shortcuts.  (Then we'll re-publish manifest shortcuts later
    485         // in the call site.)
    486         // When such a system app has allowbackup=false, then we'll keep the shortcuts that have
    487         // already been published.  So we selectively add restored ShortcutPackages here.
    488         //
    489         // The same logic applies to launchers, but since launchers shouldn't pin shortcuts
    490         // without users interaction it's really not a big deal, so we just clear existing
    491         // ShortcutLauncher instances in mLaunchers and add all the restored ones here.
    492 
    493         int[] restoredLaunchers = new int[1];
    494         int[] restoredPackages = new int[1];
    495         int[] restoredShortcuts = new int[1];
    496 
    497         mLaunchers.clear();
    498         restored.forAllLaunchers(sl -> {
    499             // If the app is already installed and allowbackup = false, then ignore the restored
    500             // data.
    501             if (s.isPackageInstalled(sl.getPackageName(), getUserId())
    502                     && !s.shouldBackupApp(sl.getPackageName(), getUserId())) {
    503                 return;
    504             }
    505             addLauncher(sl);
    506             restoredLaunchers[0]++;
    507         });
    508         restored.forAllPackages(sp -> {
    509             // If the app is already installed and allowbackup = false, then ignore the restored
    510             // data.
    511             if (s.isPackageInstalled(sp.getPackageName(), getUserId())
    512                     && !s.shouldBackupApp(sp.getPackageName(), getUserId())) {
    513                 return;
    514             }
    515 
    516             final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName());
    517             if (previous != null && previous.hasNonManifestShortcuts()) {
    518                 Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored."
    519                         + " Existing non-manifeset shortcuts will be overwritten.");
    520             }
    521             addPackage(sp);
    522             restoredPackages[0]++;
    523             restoredShortcuts[0] += sp.getShortcutCount();
    524         });
    525         // Empty the launchers and packages in restored to avoid accidentally using them.
    526         restored.mLaunchers.clear();
    527         restored.mPackages.clear();
    528 
    529         Slog.i(TAG, "Restored: L=" + restoredLaunchers[0]
    530                 + " P=" + restoredPackages[0]
    531                 + " S=" + restoredShortcuts[0]);
    532     }
    533 
    534     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
    535         pw.print(prefix);
    536         pw.print("User: ");
    537         pw.print(mUserId);
    538         pw.print("  Known locales: ");
    539         pw.print(mKnownLocales);
    540         pw.print("  Last app scan: [");
    541         pw.print(mLastAppScanTime);
    542         pw.print("] ");
    543         pw.print(ShortcutService.formatTime(mLastAppScanTime));
    544         pw.print("  Last app scan FP: ");
    545         pw.print(mLastAppScanOsFingerprint);
    546         pw.println();
    547 
    548         prefix += prefix + "  ";
    549 
    550         pw.print(prefix);
    551         pw.print("Cached launcher: ");
    552         pw.print(mCachedLauncher);
    553         pw.println();
    554 
    555         pw.print(prefix);
    556         pw.print("Last known launcher: ");
    557         pw.print(mLastKnownLauncher);
    558         pw.println();
    559 
    560         for (int i = 0; i < mLaunchers.size(); i++) {
    561             mLaunchers.valueAt(i).dump(pw, prefix);
    562         }
    563 
    564         for (int i = 0; i < mPackages.size(); i++) {
    565             mPackages.valueAt(i).dump(pw, prefix);
    566         }
    567 
    568         pw.println();
    569         pw.print(prefix);
    570         pw.println("Bitmap directories: ");
    571         dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
    572     }
    573 
    574     private void dumpDirectorySize(@NonNull PrintWriter pw,
    575             @NonNull String prefix, File path) {
    576         int numFiles = 0;
    577         long size = 0;
    578         final File[] children = path.listFiles();
    579         if (children != null) {
    580             for (File child : path.listFiles()) {
    581                 if (child.isFile()) {
    582                     numFiles++;
    583                     size += child.length();
    584                 } else if (child.isDirectory()) {
    585                     dumpDirectorySize(pw, prefix + "  ", child);
    586                 }
    587             }
    588         }
    589         pw.print(prefix);
    590         pw.print("Path: ");
    591         pw.print(path.getName());
    592         pw.print("/ has ");
    593         pw.print(numFiles);
    594         pw.print(" files, size=");
    595         pw.print(size);
    596         pw.print(" (");
    597         pw.print(Formatter.formatFileSize(mService.mContext, size));
    598         pw.println(")");
    599     }
    600 
    601     public JSONObject dumpCheckin(boolean clear) throws JSONException {
    602         final JSONObject result = new JSONObject();
    603 
    604         result.put(KEY_USER_ID, mUserId);
    605 
    606         {
    607             final JSONArray launchers = new JSONArray();
    608             for (int i = 0; i < mLaunchers.size(); i++) {
    609                 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
    610             }
    611             result.put(KEY_LAUNCHERS, launchers);
    612         }
    613 
    614         {
    615             final JSONArray packages = new JSONArray();
    616             for (int i = 0; i < mPackages.size(); i++) {
    617                 packages.put(mPackages.valueAt(i).dumpCheckin(clear));
    618             }
    619             result.put(KEY_PACKAGES, packages);
    620         }
    621 
    622         return result;
    623     }
    624 }
    625