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