Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2009 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 
     17 package com.android.internal.content;
     18 
     19 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
     20 
     21 import android.content.Context;
     22 import android.content.pm.ApplicationInfo;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageInstaller.SessionParams;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.PackageParser.PackageLite;
     28 import android.content.pm.dex.DexMetadataHelper;
     29 import android.os.Environment;
     30 import android.os.IBinder;
     31 import android.os.RemoteException;
     32 import android.os.ServiceManager;
     33 import android.os.storage.IStorageManager;
     34 import android.os.storage.StorageManager;
     35 import android.os.storage.StorageVolume;
     36 import android.os.storage.VolumeInfo;
     37 import android.provider.Settings;
     38 import android.util.ArraySet;
     39 import android.util.Log;
     40 
     41 import com.android.internal.annotations.VisibleForTesting;
     42 
     43 import libcore.io.IoUtils;
     44 
     45 import java.io.File;
     46 import java.io.FileDescriptor;
     47 import java.io.IOException;
     48 import java.util.Objects;
     49 import java.util.UUID;
     50 
     51 /**
     52  * Constants used internally between the PackageManager
     53  * and media container service transports.
     54  * Some utility methods to invoke StorageManagerService api.
     55  */
     56 public class PackageHelper {
     57     public static final int RECOMMEND_INSTALL_INTERNAL = 1;
     58     public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
     59     public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
     60     public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
     61     public static final int RECOMMEND_FAILED_INVALID_APK = -2;
     62     public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
     63     public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
     64     public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
     65     public static final int RECOMMEND_FAILED_INVALID_URI = -6;
     66     public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
     67 
     68     private static final String TAG = "PackageHelper";
     69     // App installation location settings values
     70     public static final int APP_INSTALL_AUTO = 0;
     71     public static final int APP_INSTALL_INTERNAL = 1;
     72     public static final int APP_INSTALL_EXTERNAL = 2;
     73 
     74     private static TestableInterface sDefaultTestableInterface = null;
     75 
     76     public static IStorageManager getStorageManager() throws RemoteException {
     77         IBinder service = ServiceManager.getService("mount");
     78         if (service != null) {
     79             return IStorageManager.Stub.asInterface(service);
     80         } else {
     81             Log.e(TAG, "Can't get storagemanager service");
     82             throw new RemoteException("Could not contact storagemanager service");
     83         }
     84     }
     85 
     86     /**
     87      * A group of external dependencies used in
     88      * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
     89      * from the system or mocked ones for testing purposes.
     90      */
     91     public static abstract class TestableInterface {
     92         abstract public StorageManager getStorageManager(Context context);
     93         abstract public boolean getForceAllowOnExternalSetting(Context context);
     94         abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
     95         abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
     96         abstract public File getDataDirectory();
     97     }
     98 
     99     private synchronized static TestableInterface getDefaultTestableInterface() {
    100         if (sDefaultTestableInterface == null) {
    101             sDefaultTestableInterface = new TestableInterface() {
    102                 @Override
    103                 public StorageManager getStorageManager(Context context) {
    104                     return context.getSystemService(StorageManager.class);
    105                 }
    106 
    107                 @Override
    108                 public boolean getForceAllowOnExternalSetting(Context context) {
    109                     return Settings.Global.getInt(context.getContentResolver(),
    110                             Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
    111                 }
    112 
    113                 @Override
    114                 public boolean getAllow3rdPartyOnInternalConfig(Context context) {
    115                     return context.getResources().getBoolean(
    116                             com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
    117                 }
    118 
    119                 @Override
    120                 public ApplicationInfo getExistingAppInfo(Context context, String packageName) {
    121                     ApplicationInfo existingInfo = null;
    122                     try {
    123                         existingInfo = context.getPackageManager().getApplicationInfo(packageName,
    124                                 PackageManager.MATCH_ANY_USER);
    125                     } catch (NameNotFoundException ignored) {
    126                     }
    127                     return existingInfo;
    128                 }
    129 
    130                 @Override
    131                 public File getDataDirectory() {
    132                     return Environment.getDataDirectory();
    133                 }
    134             };
    135         }
    136         return sDefaultTestableInterface;
    137     }
    138 
    139     @VisibleForTesting
    140     @Deprecated
    141     public static String resolveInstallVolume(Context context, String packageName,
    142             int installLocation, long sizeBytes, TestableInterface testInterface)
    143             throws IOException {
    144         final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
    145         params.appPackageName = packageName;
    146         params.installLocation = installLocation;
    147         params.sizeBytes = sizeBytes;
    148         return resolveInstallVolume(context, params, testInterface);
    149     }
    150 
    151     /**
    152      * Given a requested {@link PackageInfo#installLocation} and calculated
    153      * install size, pick the actual volume to install the app. Only considers
    154      * internal and private volumes, and prefers to keep an existing package on
    155      * its current volume.
    156      *
    157      * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
    158      *         for internal storage.
    159      */
    160     public static String resolveInstallVolume(Context context, SessionParams params)
    161             throws IOException {
    162         TestableInterface testableInterface = getDefaultTestableInterface();
    163         return resolveInstallVolume(context, params.appPackageName, params.installLocation,
    164                 params.sizeBytes, testableInterface);
    165     }
    166 
    167     @VisibleForTesting
    168     public static String resolveInstallVolume(Context context, SessionParams params,
    169             TestableInterface testInterface) throws IOException {
    170         final StorageManager storageManager = testInterface.getStorageManager(context);
    171         final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
    172         final boolean allow3rdPartyOnInternal =
    173                 testInterface.getAllow3rdPartyOnInternalConfig(context);
    174         // TODO: handle existing apps installed in ASEC; currently assumes
    175         // they'll end up back on internal storage
    176         ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
    177                 params.appPackageName);
    178 
    179         // Figure out best candidate volume, and also if we fit on internal
    180         final ArraySet<String> allCandidates = new ArraySet<>();
    181         boolean fitsOnInternal = false;
    182         VolumeInfo bestCandidate = null;
    183         long bestCandidateAvailBytes = Long.MIN_VALUE;
    184         for (VolumeInfo vol : storageManager.getVolumes()) {
    185             if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
    186                 final boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
    187                 final UUID target = storageManager.getUuidForPath(new File(vol.path));
    188                 final long availBytes = storageManager.getAllocatableBytes(target,
    189                         translateAllocateFlags(params.installFlags));
    190                 if (isInternalStorage) {
    191                     fitsOnInternal = (params.sizeBytes <= availBytes);
    192                 }
    193                 if (!isInternalStorage || allow3rdPartyOnInternal) {
    194                     if (availBytes >= params.sizeBytes) {
    195                         allCandidates.add(vol.fsUuid);
    196                     }
    197                     if (availBytes >= bestCandidateAvailBytes) {
    198                         bestCandidate = vol;
    199                         bestCandidateAvailBytes = availBytes;
    200                     }
    201                 }
    202             }
    203         }
    204 
    205         // System apps always forced to internal storage
    206         if (existingInfo != null && existingInfo.isSystemApp()) {
    207             if (fitsOnInternal) {
    208                 return StorageManager.UUID_PRIVATE_INTERNAL;
    209             } else {
    210                 throw new IOException("Not enough space on existing volume "
    211                         + existingInfo.volumeUuid + " for system app " + params.appPackageName
    212                         + " upgrade");
    213             }
    214         }
    215 
    216         // If app expresses strong desire for internal storage, honor it
    217         if (!forceAllowOnExternal
    218                 && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
    219             if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
    220                     StorageManager.UUID_PRIVATE_INTERNAL)) {
    221                 throw new IOException("Cannot automatically move " + params.appPackageName
    222                         + " from " + existingInfo.volumeUuid + " to internal storage");
    223             }
    224 
    225             if (!allow3rdPartyOnInternal) {
    226                 throw new IOException("Not allowed to install non-system apps on internal storage");
    227             }
    228 
    229             if (fitsOnInternal) {
    230                 return StorageManager.UUID_PRIVATE_INTERNAL;
    231             } else {
    232                 throw new IOException("Requested internal only, but not enough space");
    233             }
    234         }
    235 
    236         // If app already exists somewhere, we must stay on that volume
    237         if (existingInfo != null) {
    238             if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)
    239                     && fitsOnInternal) {
    240                 return StorageManager.UUID_PRIVATE_INTERNAL;
    241             } else if (allCandidates.contains(existingInfo.volumeUuid)) {
    242                 return existingInfo.volumeUuid;
    243             } else {
    244                 throw new IOException("Not enough space on existing volume "
    245                         + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
    246             }
    247         }
    248 
    249         // We're left with new installations with either preferring external or auto, so just pick
    250         // volume with most space
    251         if (bestCandidate != null) {
    252             return bestCandidate.fsUuid;
    253         } else {
    254             throw new IOException("No special requests, but no room on allowed volumes. "
    255                 + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
    256         }
    257     }
    258 
    259     public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
    260         final StorageManager storage = context.getSystemService(StorageManager.class);
    261         final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
    262         return (params.sizeBytes <= storage.getAllocatableBytes(target,
    263                 translateAllocateFlags(params.installFlags)));
    264     }
    265 
    266     public static boolean fitsOnExternal(Context context, SessionParams params) {
    267         final StorageManager storage = context.getSystemService(StorageManager.class);
    268         final StorageVolume primary = storage.getPrimaryVolume();
    269         return (params.sizeBytes > 0) && !primary.isEmulated()
    270                 && Environment.MEDIA_MOUNTED.equals(primary.getState())
    271                 && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
    272     }
    273 
    274     @Deprecated
    275     public static int resolveInstallLocation(Context context, String packageName,
    276             int installLocation, long sizeBytes, int installFlags) {
    277         final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
    278         params.appPackageName = packageName;
    279         params.installLocation = installLocation;
    280         params.sizeBytes = sizeBytes;
    281         params.installFlags = installFlags;
    282         try {
    283             return resolveInstallLocation(context, params);
    284         } catch (IOException e) {
    285             throw new IllegalStateException(e);
    286         }
    287     }
    288 
    289     /**
    290      * Given a requested {@link PackageInfo#installLocation} and calculated
    291      * install size, pick the actual location to install the app.
    292      */
    293     public static int resolveInstallLocation(Context context, SessionParams params)
    294             throws IOException {
    295         ApplicationInfo existingInfo = null;
    296         try {
    297             existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
    298                     PackageManager.MATCH_ANY_USER);
    299         } catch (NameNotFoundException ignored) {
    300         }
    301 
    302         final int prefer;
    303         final boolean checkBoth;
    304         boolean ephemeral = false;
    305         if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
    306             prefer = RECOMMEND_INSTALL_INTERNAL;
    307             ephemeral = true;
    308             checkBoth = false;
    309         } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
    310             prefer = RECOMMEND_INSTALL_INTERNAL;
    311             checkBoth = false;
    312         } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
    313             prefer = RECOMMEND_INSTALL_EXTERNAL;
    314             checkBoth = false;
    315         } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
    316             prefer = RECOMMEND_INSTALL_INTERNAL;
    317             checkBoth = false;
    318         } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
    319             prefer = RECOMMEND_INSTALL_EXTERNAL;
    320             checkBoth = true;
    321         } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
    322             // When app is already installed, prefer same medium
    323             if (existingInfo != null) {
    324                 // TODO: distinguish if this is external ASEC
    325                 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
    326                     prefer = RECOMMEND_INSTALL_EXTERNAL;
    327                 } else {
    328                     prefer = RECOMMEND_INSTALL_INTERNAL;
    329                 }
    330             } else {
    331                 prefer = RECOMMEND_INSTALL_INTERNAL;
    332             }
    333             checkBoth = true;
    334         } else {
    335             prefer = RECOMMEND_INSTALL_INTERNAL;
    336             checkBoth = false;
    337         }
    338 
    339         boolean fitsOnInternal = false;
    340         if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
    341             fitsOnInternal = fitsOnInternal(context, params);
    342         }
    343 
    344         boolean fitsOnExternal = false;
    345         if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
    346             fitsOnExternal = fitsOnExternal(context, params);
    347         }
    348 
    349         if (prefer == RECOMMEND_INSTALL_INTERNAL) {
    350             // The ephemeral case will either fit and return EPHEMERAL, or will not fit
    351             // and will fall through to return INSUFFICIENT_STORAGE
    352             if (fitsOnInternal) {
    353                 return (ephemeral)
    354                         ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
    355                         : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
    356             }
    357         } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
    358             if (fitsOnExternal) {
    359                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
    360             }
    361         }
    362 
    363         if (checkBoth) {
    364             if (fitsOnInternal) {
    365                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
    366             } else if (fitsOnExternal) {
    367                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
    368             }
    369         }
    370 
    371         return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
    372     }
    373 
    374     @Deprecated
    375     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
    376             String abiOverride) throws IOException {
    377         return calculateInstalledSize(pkg, abiOverride);
    378     }
    379 
    380     public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
    381             throws IOException {
    382         return calculateInstalledSize(pkg, abiOverride, null);
    383     }
    384 
    385     public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
    386             FileDescriptor fd) throws IOException {
    387         NativeLibraryHelper.Handle handle = null;
    388         try {
    389             handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
    390                     : NativeLibraryHelper.Handle.create(pkg);
    391             return calculateInstalledSize(pkg, handle, abiOverride);
    392         } finally {
    393             IoUtils.closeQuietly(handle);
    394         }
    395     }
    396 
    397     @Deprecated
    398     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
    399             NativeLibraryHelper.Handle handle, String abiOverride) throws IOException {
    400         return calculateInstalledSize(pkg, handle, abiOverride);
    401     }
    402 
    403     public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
    404             String abiOverride) throws IOException {
    405         long sizeBytes = 0;
    406 
    407         // Include raw APKs, and possibly unpacked resources
    408         for (String codePath : pkg.getAllCodePaths()) {
    409             final File codeFile = new File(codePath);
    410             sizeBytes += codeFile.length();
    411         }
    412 
    413         // Include raw dex metadata files
    414         sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg);
    415 
    416         // Include all relevant native code
    417         sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
    418 
    419         return sizeBytes;
    420     }
    421 
    422     public static String replaceEnd(String str, String before, String after) {
    423         if (!str.endsWith(before)) {
    424             throw new IllegalArgumentException(
    425                     "Expected " + str + " to end with " + before);
    426         }
    427         return str.substring(0, str.length() - before.length()) + after;
    428     }
    429 
    430     public static int translateAllocateFlags(int installFlags) {
    431         if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {
    432             return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;
    433         } else {
    434             return 0;
    435         }
    436     }
    437 }
    438