DefaultContainerService.java revision 6dceb88f1c7c42c6ab43834af2c993d599895d82
1/* 2 * Copyright (C) 2010 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.defcontainer; 18 19import com.android.internal.app.IMediaContainerService; 20import com.android.internal.content.NativeLibraryHelper; 21import com.android.internal.content.PackageHelper; 22 23import android.app.IntentService; 24import android.content.Intent; 25import android.content.pm.IPackageManager; 26import android.content.pm.PackageInfo; 27import android.content.pm.PackageInfoLite; 28import android.content.pm.PackageManager; 29import android.content.pm.PackageParser; 30import android.content.res.ObbInfo; 31import android.content.res.ObbScanner; 32import android.net.Uri; 33import android.os.Environment; 34import android.os.FileUtils; 35import android.os.IBinder; 36import android.os.ParcelFileDescriptor; 37import android.os.Process; 38import android.os.RemoteException; 39import android.os.ServiceManager; 40import android.os.StatFs; 41import android.provider.Settings; 42import android.util.DisplayMetrics; 43import android.util.Slog; 44 45import java.io.BufferedInputStream; 46import java.io.File; 47import java.io.FileInputStream; 48import java.io.FileNotFoundException; 49import java.io.IOException; 50import java.io.InputStream; 51import java.io.OutputStream; 52 53import libcore.io.ErrnoException; 54import libcore.io.Libcore; 55import libcore.io.StructStatFs; 56 57/* 58 * This service copies a downloaded apk to a file passed in as 59 * a ParcelFileDescriptor or to a newly created container specified 60 * by parameters. The DownloadManager gives access to this process 61 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 62 * permission to access apks downloaded via the download manager. 63 */ 64public class DefaultContainerService extends IntentService { 65 private static final String TAG = "DefContainer"; 66 private static final boolean localLOGV = true; 67 68 private static final String LIB_DIR_NAME = "lib"; 69 70 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 71 /* 72 * Creates a new container and copies resource there. 73 * @param paackageURI the uri of resource to be copied. Can be either 74 * a content uri or a file uri 75 * @param cid the id of the secure container that should 76 * be used for creating a secure container into which the resource 77 * will be copied. 78 * @param key Refers to key used for encrypting the secure container 79 * @param resFileName Name of the target resource file(relative to newly 80 * created secure container) 81 * @return Returns the new cache path where the resource has been copied into 82 * 83 */ 84 public String copyResourceToContainer(final Uri packageURI, final String cid, 85 final String key, final String resFileName, final String publicResFileName, 86 boolean isExternal, boolean isForwardLocked) { 87 if (packageURI == null || cid == null) { 88 return null; 89 } 90 91 return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName, 92 isExternal, isForwardLocked); 93 } 94 95 /* 96 * Copy specified resource to output stream 97 * @param packageURI the uri of resource to be copied. Should be a file 98 * uri 99 * @param outStream Remote file descriptor to be used for copying 100 * @return returns status code according to those in {@link 101 * PackageManager} 102 */ 103 public int copyResource(final Uri packageURI, ParcelFileDescriptor outStream) { 104 if (packageURI == null || outStream == null) { 105 return PackageManager.INSTALL_FAILED_INVALID_URI; 106 } 107 108 ParcelFileDescriptor.AutoCloseOutputStream autoOut 109 = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 110 111 try { 112 copyFile(packageURI, autoOut); 113 return PackageManager.INSTALL_SUCCEEDED; 114 } catch (FileNotFoundException e) { 115 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " FNF: " 116 + e.getMessage()); 117 return PackageManager.INSTALL_FAILED_INVALID_URI; 118 } catch (IOException e) { 119 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: " 120 + e.getMessage()); 121 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 122 } 123 } 124 125 /* 126 * Determine the recommended install location for package 127 * specified by file uri location. 128 * @param fileUri the uri of resource to be copied. Should be a 129 * file uri 130 * @return Returns PackageInfoLite object containing 131 * the package info and recommended app location. 132 */ 133 public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) { 134 PackageInfoLite ret = new PackageInfoLite(); 135 if (fileUri == null) { 136 Slog.i(TAG, "Invalid package uri " + fileUri); 137 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 138 return ret; 139 } 140 String scheme = fileUri.getScheme(); 141 if (scheme != null && !scheme.equals("file")) { 142 Slog.w(TAG, "Falling back to installing on internal storage only"); 143 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL; 144 return ret; 145 } 146 String archiveFilePath = fileUri.getPath(); 147 DisplayMetrics metrics = new DisplayMetrics(); 148 metrics.setToDefaults(); 149 150 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0); 151 if (pkg == null) { 152 Slog.w(TAG, "Failed to parse package"); 153 154 final File apkFile = new File(archiveFilePath); 155 if (!apkFile.exists()) { 156 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 157 } else { 158 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 159 } 160 161 return ret; 162 } 163 ret.packageName = pkg.packageName; 164 ret.installLocation = pkg.installLocation; 165 ret.verifiers = pkg.verifiers; 166 167 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, 168 archiveFilePath, flags, threshold); 169 170 return ret; 171 } 172 173 @Override 174 public boolean checkInternalFreeStorage(Uri packageUri, boolean isForwardLocked, 175 long threshold) throws RemoteException { 176 final File apkFile = new File(packageUri.getPath()); 177 try { 178 return isUnderInternalThreshold(apkFile, isForwardLocked, threshold); 179 } catch (IOException e) { 180 return true; 181 } 182 } 183 184 @Override 185 public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked) 186 throws RemoteException { 187 final File apkFile = new File(packageUri.getPath()); 188 try { 189 return isUnderExternalThreshold(apkFile, isForwardLocked); 190 } catch (IOException e) { 191 return true; 192 } 193 } 194 195 public ObbInfo getObbInfo(String filename) { 196 try { 197 return ObbScanner.getObbInfo(filename); 198 } catch (IOException e) { 199 Slog.d(TAG, "Couldn't get OBB info for " + filename); 200 return null; 201 } 202 } 203 204 @Override 205 public long calculateDirectorySize(String path) throws RemoteException { 206 final File directory = new File(path); 207 if (directory.exists() && directory.isDirectory()) { 208 return MeasurementUtils.measureDirectory(path); 209 } else { 210 return 0L; 211 } 212 } 213 214 @Override 215 public long[] getFileSystemStats(String path) { 216 try { 217 final StructStatFs stat = Libcore.os.statfs(path); 218 final long totalSize = stat.f_blocks * stat.f_bsize; 219 final long availSize = stat.f_bavail * stat.f_bsize; 220 return new long[] { totalSize, availSize }; 221 } catch (ErrnoException e) { 222 throw new IllegalStateException(e); 223 } 224 } 225 }; 226 227 public DefaultContainerService() { 228 super("DefaultContainerService"); 229 setIntentRedelivery(true); 230 } 231 232 @Override 233 protected void onHandleIntent(Intent intent) { 234 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 235 IPackageManager pm = IPackageManager.Stub.asInterface( 236 ServiceManager.getService("package")); 237 String pkg = null; 238 try { 239 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 240 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 241 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 242 eraseFiles(Environment.getExternalStorageAppObbDirectory(pkg)); 243 } 244 } catch (RemoteException e) { 245 } 246 } 247 } 248 249 void eraseFiles(File path) { 250 if (path.isDirectory()) { 251 String[] files = path.list(); 252 if (files != null) { 253 for (String file : files) { 254 eraseFiles(new File(path, file)); 255 } 256 } 257 } 258 path.delete(); 259 } 260 261 public IBinder onBind(Intent intent) { 262 return mBinder; 263 } 264 265 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName, 266 String publicResFileName, boolean isExternal, boolean isForwardLocked) { 267 268 if (isExternal) { 269 // Make sure the sdcard is mounted. 270 String status = Environment.getExternalStorageState(); 271 if (!status.equals(Environment.MEDIA_MOUNTED)) { 272 Slog.w(TAG, "Make sure sdcard is mounted."); 273 return null; 274 } 275 } 276 277 // The .apk file 278 String codePath = packageURI.getPath(); 279 File codeFile = new File(codePath); 280 281 // Calculate size of container needed to hold base APK. 282 final int sizeMb; 283 try { 284 sizeMb = calculateContainerSize(codeFile, isForwardLocked); 285 } catch (IOException e) { 286 Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath()); 287 return null; 288 } 289 290 // Create new container 291 final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(), 292 isExternal); 293 if (newCachePath == null) { 294 Slog.e(TAG, "Failed to create container " + newCid); 295 return null; 296 } 297 298 if (localLOGV) { 299 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath); 300 } 301 302 final File resFile = new File(newCachePath, resFileName); 303 if (FileUtils.copyFile(new File(codePath), resFile)) { 304 if (localLOGV) { 305 Slog.i(TAG, "Copied " + codePath + " to " + resFile); 306 } 307 } else { 308 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile); 309 // Clean up container 310 PackageHelper.destroySdDir(newCid); 311 return null; 312 } 313 314 if (isForwardLocked) { 315 File publicZipFile = new File(newCachePath, publicResFileName); 316 try { 317 PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile); 318 if (localLOGV) { 319 Slog.i(TAG, "Copied resources to " + publicZipFile); 320 } 321 } catch (IOException e) { 322 Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": " 323 + e.getMessage()); 324 PackageHelper.destroySdDir(newCid); 325 return null; 326 } 327 328 try { 329 Libcore.os.chmod(resFile.getAbsolutePath(), 0640); 330 Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644); 331 } catch (ErrnoException e) { 332 Slog.e(TAG, "Could not chown APK or resource file: " + e.getMessage()); 333 PackageHelper.destroySdDir(newCid); 334 return null; 335 } 336 } 337 338 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); 339 if (sharedLibraryDir.mkdir()) { 340 int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir); 341 if (ret != PackageManager.INSTALL_SUCCEEDED) { 342 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath()); 343 PackageHelper.destroySdDir(newCid); 344 return null; 345 } 346 } else { 347 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath()); 348 PackageHelper.destroySdDir(newCid); 349 return null; 350 } 351 352 if (!PackageHelper.finalizeSdDir(newCid)) { 353 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath); 354 // Clean up container 355 PackageHelper.destroySdDir(newCid); 356 return null; 357 } 358 359 if (localLOGV) { 360 Slog.i(TAG, "Finalized container " + newCid); 361 } 362 363 if (PackageHelper.isContainerMounted(newCid)) { 364 if (localLOGV) { 365 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath); 366 } 367 368 // Force a gc to avoid being killed. 369 Runtime.getRuntime().gc(); 370 PackageHelper.unMountSdDir(newCid); 371 } else { 372 if (localLOGV) { 373 Slog.i(TAG, "Container " + newCid + " not mounted"); 374 } 375 } 376 377 return newCachePath; 378 } 379 380 private static void copyToFile(InputStream inputStream, OutputStream out) throws IOException { 381 byte[] buffer = new byte[16384]; 382 int bytesRead; 383 while ((bytesRead = inputStream.read(buffer)) >= 0) { 384 out.write(buffer, 0, bytesRead); 385 } 386 } 387 388 private static void copyToFile(File srcFile, OutputStream out) 389 throws FileNotFoundException, IOException { 390 InputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile)); 391 try { 392 copyToFile(inputStream, out); 393 } finally { 394 try { inputStream.close(); } catch (IOException e) {} 395 } 396 } 397 398 private void copyFile(Uri pPackageURI, OutputStream outStream) throws FileNotFoundException, 399 IOException { 400 String scheme = pPackageURI.getScheme(); 401 if (scheme == null || scheme.equals("file")) { 402 final File srcPackageFile = new File(pPackageURI.getPath()); 403 // We copy the source package file to a temp file and then rename it to the 404 // destination file in order to eliminate a window where the package directory 405 // scanner notices the new package file but it's not completely copied yet. 406 copyToFile(srcPackageFile, outStream); 407 } else if (scheme.equals("content")) { 408 ParcelFileDescriptor fd = null; 409 try { 410 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 411 } catch (FileNotFoundException e) { 412 Slog.e(TAG, "Couldn't open file descriptor from download service. " 413 + "Failed with exception " + e); 414 throw e; 415 } 416 417 if (fd == null) { 418 Slog.e(TAG, "Provider returned no file descriptor for " + pPackageURI.toString()); 419 throw new FileNotFoundException("provider returned no file descriptor"); 420 } else { 421 if (localLOGV) { 422 Slog.i(TAG, "Opened file descriptor from download service."); 423 } 424 ParcelFileDescriptor.AutoCloseInputStream dlStream 425 = new ParcelFileDescriptor.AutoCloseInputStream(fd); 426 427 // We copy the source package file to a temp file and then rename it to the 428 // destination file in order to eliminate a window where the package directory 429 // scanner notices the new package file but it's not completely 430 // copied 431 copyToFile(dlStream, outStream); 432 } 433 } else { 434 Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 435 throw new FileNotFoundException("Package URI is not 'file:' or 'content:'"); 436 } 437 } 438 439 private static final int PREFER_INTERNAL = 1; 440 private static final int PREFER_EXTERNAL = 2; 441 442 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags, 443 long threshold) { 444 int prefer; 445 boolean checkBoth = false; 446 447 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 448 449 check_inner : { 450 /* 451 * Explicit install flags should override the manifest settings. 452 */ 453 if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { 454 prefer = PREFER_INTERNAL; 455 break check_inner; 456 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { 457 prefer = PREFER_EXTERNAL; 458 break check_inner; 459 } 460 461 /* No install flags. Check for manifest option. */ 462 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 463 prefer = PREFER_INTERNAL; 464 break check_inner; 465 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 466 prefer = PREFER_EXTERNAL; 467 checkBoth = true; 468 break check_inner; 469 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 470 // We default to preferring internal storage. 471 prefer = PREFER_INTERNAL; 472 checkBoth = true; 473 break check_inner; 474 } 475 476 // Pick user preference 477 int installPreference = Settings.System.getInt(getApplicationContext() 478 .getContentResolver(), 479 Settings.Secure.DEFAULT_INSTALL_LOCATION, 480 PackageHelper.APP_INSTALL_AUTO); 481 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { 482 prefer = PREFER_INTERNAL; 483 break check_inner; 484 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { 485 prefer = PREFER_EXTERNAL; 486 break check_inner; 487 } 488 489 /* 490 * Fall back to default policy of internal-only if nothing else is 491 * specified. 492 */ 493 prefer = PREFER_INTERNAL; 494 } 495 496 final boolean emulated = Environment.isExternalStorageEmulated(); 497 498 final File apkFile = new File(archiveFilePath); 499 500 boolean fitsOnInternal = false; 501 if (checkBoth || prefer == PREFER_INTERNAL) { 502 try { 503 fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold); 504 } catch (IOException e) { 505 return PackageHelper.RECOMMEND_FAILED_INVALID_URI; 506 } 507 } 508 509 boolean fitsOnSd = false; 510 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { 511 try { 512 fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked); 513 } catch (IOException e) { 514 return PackageHelper.RECOMMEND_FAILED_INVALID_URI; 515 } 516 } 517 518 if (prefer == PREFER_INTERNAL) { 519 if (fitsOnInternal) { 520 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 521 } 522 } else if (!emulated && prefer == PREFER_EXTERNAL) { 523 if (fitsOnSd) { 524 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 525 } 526 } 527 528 if (checkBoth) { 529 if (fitsOnInternal) { 530 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 531 } else if (!emulated && fitsOnSd) { 532 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 533 } 534 } 535 536 /* 537 * If they requested to be on the external media by default, return that 538 * the media was unavailable. Otherwise, indicate there was insufficient 539 * storage space available. 540 */ 541 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) 542 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 543 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 544 } else { 545 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 546 } 547 } 548 549 /** 550 * Measure a file to see if it fits within the free space threshold. 551 * 552 * @param apkFile file to check 553 * @param threshold byte threshold to compare against 554 * @return true if file fits under threshold 555 * @throws FileNotFoundException when APK does not exist 556 */ 557 private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold) 558 throws IOException { 559 long size = apkFile.length(); 560 if (size == 0 && !apkFile.exists()) { 561 throw new FileNotFoundException(); 562 } 563 564 if (isForwardLocked) { 565 size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null); 566 } 567 568 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 569 final long availInternalSize = (long) internalStats.getAvailableBlocks() 570 * (long) internalStats.getBlockSize(); 571 572 return (availInternalSize - size) > threshold; 573 } 574 575 576 /** 577 * Measure a file to see if it fits in the external free space. 578 * 579 * @param apkFile file to check 580 * @return true if file fits 581 * @throws IOException when file does not exist 582 */ 583 private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked) 584 throws IOException { 585 if (Environment.isExternalStorageEmulated()) { 586 return false; 587 } 588 589 final int sizeMb = calculateContainerSize(apkFile, isForwardLocked); 590 591 final int availSdMb; 592 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 593 final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 594 final int blocksToMb = (1 << 20) / sdStats.getBlockSize(); 595 availSdMb = sdStats.getAvailableBlocks() * blocksToMb; 596 } else { 597 availSdMb = -1; 598 } 599 600 return availSdMb > sizeMb; 601 } 602 603 /** 604 * Calculate the container size for an APK. Takes into account the 605 * 606 * @param apkFile file from which to calculate size 607 * @return size in megabytes (2^20 bytes) 608 * @throws IOException when there is a problem reading the file 609 */ 610 private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException { 611 // Calculate size of container needed to hold base APK. 612 long sizeBytes = apkFile.length(); 613 if (sizeBytes == 0 && !apkFile.exists()) { 614 throw new FileNotFoundException(); 615 } 616 617 // Check all the native files that need to be copied and add that to the 618 // container size. 619 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile); 620 621 if (forwardLocked) { 622 sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null); 623 } 624 625 int sizeMb = (int) (sizeBytes >> 20); 626 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { 627 sizeMb++; 628 } 629 630 /* 631 * Add buffer size because we don't have a good way to determine the 632 * real FAT size. Your FAT size varies with how many directory entries 633 * you need, how big the whole filesystem is, and other such headaches. 634 */ 635 sizeMb++; 636 637 return sizeMb; 638 } 639} 640