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