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