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.net.TrafficStats.MB_IN_BYTES; 20 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.PackageParser.PackageLite; 27 import android.os.Environment; 28 import android.os.FileUtils; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.storage.IMountService; 33 import android.os.storage.StorageManager; 34 import android.os.storage.StorageResultCode; 35 import android.os.storage.StorageVolume; 36 import android.os.storage.VolumeInfo; 37 import android.util.ArraySet; 38 import android.util.Log; 39 40 import libcore.io.IoUtils; 41 42 import java.io.File; 43 import java.io.FileOutputStream; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.util.Collections; 47 import java.util.zip.ZipEntry; 48 import java.util.zip.ZipFile; 49 import java.util.zip.ZipOutputStream; 50 51 /** 52 * Constants used internally between the PackageManager 53 * and media container service transports. 54 * Some utility methods to invoke MountService 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_FAILED_INSUFFICIENT_STORAGE = -1; 60 public static final int RECOMMEND_FAILED_INVALID_APK = -2; 61 public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3; 62 public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4; 63 public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5; 64 public static final int RECOMMEND_FAILED_INVALID_URI = -6; 65 public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7; 66 67 private static final boolean localLOGV = false; 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 getMountService()74 public static IMountService getMountService() throws RemoteException { 75 IBinder service = ServiceManager.getService("mount"); 76 if (service != null) { 77 return IMountService.Stub.asInterface(service); 78 } else { 79 Log.e(TAG, "Can't get mount service"); 80 throw new RemoteException("Could not contact mount service"); 81 } 82 } 83 createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, boolean isExternal)84 public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, 85 boolean isExternal) { 86 // Round up to nearest MB, plus another MB for filesystem overhead 87 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 88 try { 89 IMountService mountService = getMountService(); 90 91 if (localLOGV) 92 Log.i(TAG, "Size of container " + sizeMb + " MB"); 93 94 int rc = mountService.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid, 95 isExternal); 96 if (rc != StorageResultCode.OperationSucceeded) { 97 Log.e(TAG, "Failed to create secure container " + cid); 98 return null; 99 } 100 String cachePath = mountService.getSecureContainerPath(cid); 101 if (localLOGV) Log.i(TAG, "Created secure container " + cid + 102 " at " + cachePath); 103 return cachePath; 104 } catch (RemoteException e) { 105 Log.e(TAG, "MountService running?"); 106 } 107 return null; 108 } 109 resizeSdDir(long sizeBytes, String cid, String sdEncKey)110 public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) { 111 // Round up to nearest MB, plus another MB for filesystem overhead 112 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 113 try { 114 IMountService mountService = getMountService(); 115 int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey); 116 if (rc == StorageResultCode.OperationSucceeded) { 117 return true; 118 } 119 } catch (RemoteException e) { 120 Log.e(TAG, "MountService running?"); 121 } 122 Log.e(TAG, "Failed to create secure container " + cid); 123 return false; 124 } 125 mountSdDir(String cid, String key, int ownerUid)126 public static String mountSdDir(String cid, String key, int ownerUid) { 127 return mountSdDir(cid, key, ownerUid, true); 128 } 129 mountSdDir(String cid, String key, int ownerUid, boolean readOnly)130 public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) { 131 try { 132 int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly); 133 if (rc != StorageResultCode.OperationSucceeded) { 134 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc); 135 return null; 136 } 137 return getMountService().getSecureContainerPath(cid); 138 } catch (RemoteException e) { 139 Log.e(TAG, "MountService running?"); 140 } 141 return null; 142 } 143 unMountSdDir(String cid)144 public static boolean unMountSdDir(String cid) { 145 try { 146 int rc = getMountService().unmountSecureContainer(cid, true); 147 if (rc != StorageResultCode.OperationSucceeded) { 148 Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc); 149 return false; 150 } 151 return true; 152 } catch (RemoteException e) { 153 Log.e(TAG, "MountService running?"); 154 } 155 return false; 156 } 157 renameSdDir(String oldId, String newId)158 public static boolean renameSdDir(String oldId, String newId) { 159 try { 160 int rc = getMountService().renameSecureContainer(oldId, newId); 161 if (rc != StorageResultCode.OperationSucceeded) { 162 Log.e(TAG, "Failed to rename " + oldId + " to " + 163 newId + "with rc " + rc); 164 return false; 165 } 166 return true; 167 } catch (RemoteException e) { 168 Log.i(TAG, "Failed ot rename " + oldId + " to " + newId + 169 " with exception : " + e); 170 } 171 return false; 172 } 173 getSdDir(String cid)174 public static String getSdDir(String cid) { 175 try { 176 return getMountService().getSecureContainerPath(cid); 177 } catch (RemoteException e) { 178 Log.e(TAG, "Failed to get container path for " + cid + 179 " with exception " + e); 180 } 181 return null; 182 } 183 getSdFilesystem(String cid)184 public static String getSdFilesystem(String cid) { 185 try { 186 return getMountService().getSecureContainerFilesystemPath(cid); 187 } catch (RemoteException e) { 188 Log.e(TAG, "Failed to get container path for " + cid + 189 " with exception " + e); 190 } 191 return null; 192 } 193 finalizeSdDir(String cid)194 public static boolean finalizeSdDir(String cid) { 195 try { 196 int rc = getMountService().finalizeSecureContainer(cid); 197 if (rc != StorageResultCode.OperationSucceeded) { 198 Log.i(TAG, "Failed to finalize container " + cid); 199 return false; 200 } 201 return true; 202 } catch (RemoteException e) { 203 Log.e(TAG, "Failed to finalize container " + cid + 204 " with exception " + e); 205 } 206 return false; 207 } 208 destroySdDir(String cid)209 public static boolean destroySdDir(String cid) { 210 try { 211 if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid); 212 int rc = getMountService().destroySecureContainer(cid, true); 213 if (rc != StorageResultCode.OperationSucceeded) { 214 Log.i(TAG, "Failed to destroy container " + cid); 215 return false; 216 } 217 return true; 218 } catch (RemoteException e) { 219 Log.e(TAG, "Failed to destroy container " + cid + 220 " with exception " + e); 221 } 222 return false; 223 } 224 getSecureContainerList()225 public static String[] getSecureContainerList() { 226 try { 227 return getMountService().getSecureContainerList(); 228 } catch (RemoteException e) { 229 Log.e(TAG, "Failed to get secure container list with exception" + 230 e); 231 } 232 return null; 233 } 234 isContainerMounted(String cid)235 public static boolean isContainerMounted(String cid) { 236 try { 237 return getMountService().isSecureContainerMounted(cid); 238 } catch (RemoteException e) { 239 Log.e(TAG, "Failed to find out if container " + cid + " mounted"); 240 } 241 return false; 242 } 243 244 /** 245 * Extract public files for the single given APK. 246 */ extractPublicFiles(File apkFile, File publicZipFile)247 public static long extractPublicFiles(File apkFile, File publicZipFile) 248 throws IOException { 249 final FileOutputStream fstr; 250 final ZipOutputStream publicZipOutStream; 251 252 if (publicZipFile == null) { 253 fstr = null; 254 publicZipOutStream = null; 255 } else { 256 fstr = new FileOutputStream(publicZipFile); 257 publicZipOutStream = new ZipOutputStream(fstr); 258 Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile); 259 } 260 261 long size = 0L; 262 263 try { 264 final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath()); 265 try { 266 // Copy manifest, resources.arsc and res directory to public zip 267 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) { 268 final String zipEntryName = zipEntry.getName(); 269 if ("AndroidManifest.xml".equals(zipEntryName) 270 || "resources.arsc".equals(zipEntryName) 271 || zipEntryName.startsWith("res/")) { 272 size += zipEntry.getSize(); 273 if (publicZipFile != null) { 274 copyZipEntry(zipEntry, privateZip, publicZipOutStream); 275 } 276 } 277 } 278 } finally { 279 try { privateZip.close(); } catch (IOException e) {} 280 } 281 282 if (publicZipFile != null) { 283 publicZipOutStream.finish(); 284 publicZipOutStream.flush(); 285 FileUtils.sync(fstr); 286 publicZipOutStream.close(); 287 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR 288 | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1); 289 } 290 } finally { 291 IoUtils.closeQuietly(publicZipOutStream); 292 } 293 294 return size; 295 } 296 copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, ZipOutputStream outZipStream)297 private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, 298 ZipOutputStream outZipStream) throws IOException { 299 byte[] buffer = new byte[4096]; 300 int num; 301 302 ZipEntry newEntry; 303 if (zipEntry.getMethod() == ZipEntry.STORED) { 304 // Preserve the STORED method of the input entry. 305 newEntry = new ZipEntry(zipEntry); 306 } else { 307 // Create a new entry so that the compressed len is recomputed. 308 newEntry = new ZipEntry(zipEntry.getName()); 309 } 310 outZipStream.putNextEntry(newEntry); 311 312 final InputStream data = inZipFile.getInputStream(zipEntry); 313 try { 314 while ((num = data.read(buffer)) > 0) { 315 outZipStream.write(buffer, 0, num); 316 } 317 outZipStream.flush(); 318 } finally { 319 IoUtils.closeQuietly(data); 320 } 321 } 322 fixSdPermissions(String cid, int gid, String filename)323 public static boolean fixSdPermissions(String cid, int gid, String filename) { 324 try { 325 int rc = getMountService().fixPermissionsSecureContainer(cid, gid, filename); 326 if (rc != StorageResultCode.OperationSucceeded) { 327 Log.i(TAG, "Failed to fixperms container " + cid); 328 return false; 329 } 330 return true; 331 } catch (RemoteException e) { 332 Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e); 333 } 334 return false; 335 } 336 337 /** 338 * Given a requested {@link PackageInfo#installLocation} and calculated 339 * install size, pick the actual volume to install the app. Only considers 340 * internal and private volumes, and prefers to keep an existing package on 341 * its current volume. 342 * 343 * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null} 344 * for internal storage. 345 */ resolveInstallVolume(Context context, String packageName, int installLocation, long sizeBytes)346 public static String resolveInstallVolume(Context context, String packageName, 347 int installLocation, long sizeBytes) throws IOException { 348 // TODO: handle existing apps installed in ASEC; currently assumes 349 // they'll end up back on internal storage 350 ApplicationInfo existingInfo = null; 351 try { 352 existingInfo = context.getPackageManager().getApplicationInfo(packageName, 353 PackageManager.GET_UNINSTALLED_PACKAGES); 354 } catch (NameNotFoundException ignored) { 355 } 356 357 final StorageManager storageManager = context.getSystemService(StorageManager.class); 358 final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes); 359 360 final ArraySet<String> allCandidates = new ArraySet<>(); 361 VolumeInfo bestCandidate = null; 362 long bestCandidateAvailBytes = Long.MIN_VALUE; 363 for (VolumeInfo vol : storageManager.getVolumes()) { 364 if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) { 365 final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path)); 366 if (availBytes >= sizeBytes) { 367 allCandidates.add(vol.fsUuid); 368 } 369 if (availBytes >= bestCandidateAvailBytes) { 370 bestCandidate = vol; 371 bestCandidateAvailBytes = availBytes; 372 } 373 } 374 } 375 376 // System apps always forced to internal storage 377 if (existingInfo != null && existingInfo.isSystemApp()) { 378 installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; 379 } 380 381 // If app expresses strong desire for internal space, honor it 382 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 383 if (fitsOnInternal) { 384 return null; 385 } else { 386 throw new IOException("Requested internal only, but not enough space"); 387 } 388 } 389 390 // If app already exists somewhere, prefer to stay on that volume 391 if (existingInfo != null) { 392 if (existingInfo.volumeUuid == null && fitsOnInternal) { 393 return null; 394 } 395 if (allCandidates.contains(existingInfo.volumeUuid)) { 396 return existingInfo.volumeUuid; 397 } 398 } 399 400 // We're left with either preferring external or auto, so just pick 401 // volume with most space 402 if (bestCandidate != null) { 403 return bestCandidate.fsUuid; 404 } else if (fitsOnInternal) { 405 return null; 406 } else { 407 throw new IOException("No special requests, but no room anywhere"); 408 } 409 } 410 fitsOnInternal(Context context, long sizeBytes)411 public static boolean fitsOnInternal(Context context, long sizeBytes) { 412 final StorageManager storage = context.getSystemService(StorageManager.class); 413 final File target = Environment.getDataDirectory(); 414 return (sizeBytes <= storage.getStorageBytesUntilLow(target)); 415 } 416 fitsOnExternal(Context context, long sizeBytes)417 public static boolean fitsOnExternal(Context context, long sizeBytes) { 418 final StorageManager storage = context.getSystemService(StorageManager.class); 419 final StorageVolume primary = storage.getPrimaryVolume(); 420 return (sizeBytes > 0) && !primary.isEmulated() 421 && Environment.MEDIA_MOUNTED.equals(primary.getState()) 422 && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile()); 423 } 424 425 /** 426 * Given a requested {@link PackageInfo#installLocation} and calculated 427 * install size, pick the actual location to install the app. 428 */ resolveInstallLocation(Context context, String packageName, int installLocation, long sizeBytes, int installFlags)429 public static int resolveInstallLocation(Context context, String packageName, 430 int installLocation, long sizeBytes, int installFlags) { 431 ApplicationInfo existingInfo = null; 432 try { 433 existingInfo = context.getPackageManager().getApplicationInfo(packageName, 434 PackageManager.GET_UNINSTALLED_PACKAGES); 435 } catch (NameNotFoundException ignored) { 436 } 437 438 final int prefer; 439 final boolean checkBoth; 440 if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 441 prefer = RECOMMEND_INSTALL_INTERNAL; 442 checkBoth = false; 443 } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 444 prefer = RECOMMEND_INSTALL_EXTERNAL; 445 checkBoth = false; 446 } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 447 prefer = RECOMMEND_INSTALL_INTERNAL; 448 checkBoth = false; 449 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 450 prefer = RECOMMEND_INSTALL_EXTERNAL; 451 checkBoth = true; 452 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 453 // When app is already installed, prefer same medium 454 if (existingInfo != null) { 455 // TODO: distinguish if this is external ASEC 456 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 457 prefer = RECOMMEND_INSTALL_EXTERNAL; 458 } else { 459 prefer = RECOMMEND_INSTALL_INTERNAL; 460 } 461 } else { 462 prefer = RECOMMEND_INSTALL_INTERNAL; 463 } 464 checkBoth = true; 465 } else { 466 prefer = RECOMMEND_INSTALL_INTERNAL; 467 checkBoth = false; 468 } 469 470 boolean fitsOnInternal = false; 471 if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) { 472 fitsOnInternal = fitsOnInternal(context, sizeBytes); 473 } 474 475 boolean fitsOnExternal = false; 476 if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) { 477 fitsOnExternal = fitsOnExternal(context, sizeBytes); 478 } 479 480 if (prefer == RECOMMEND_INSTALL_INTERNAL) { 481 if (fitsOnInternal) { 482 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 483 } 484 } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) { 485 if (fitsOnExternal) { 486 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 487 } 488 } 489 490 if (checkBoth) { 491 if (fitsOnInternal) { 492 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 493 } else if (fitsOnExternal) { 494 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 495 } 496 } 497 498 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 499 } 500 calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, String abiOverride)501 public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, 502 String abiOverride) throws IOException { 503 NativeLibraryHelper.Handle handle = null; 504 try { 505 handle = NativeLibraryHelper.Handle.create(pkg); 506 return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride); 507 } finally { 508 IoUtils.closeQuietly(handle); 509 } 510 } 511 calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, boolean isForwardLocked, String abiOverride)512 public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, 513 boolean isForwardLocked, String abiOverride) throws IOException { 514 long sizeBytes = 0; 515 516 // Include raw APKs, and possibly unpacked resources 517 for (String codePath : pkg.getAllCodePaths()) { 518 final File codeFile = new File(codePath); 519 sizeBytes += codeFile.length(); 520 521 if (isForwardLocked) { 522 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); 523 } 524 } 525 526 // Include all relevant native code 527 sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); 528 529 return sizeBytes; 530 } 531 replaceEnd(String str, String before, String after)532 public static String replaceEnd(String str, String before, String after) { 533 if (!str.endsWith(before)) { 534 throw new IllegalArgumentException( 535 "Expected " + str + " to end with " + before); 536 } 537 return str.substring(0, str.length() - before.length()) + after; 538 } 539 } 540