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