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