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