DefaultContainerService.java revision 300c13a48132f03d48462b9cd3ec41331a71a411
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.content.Intent; 24import android.content.pm.IPackageManager; 25import android.content.pm.PackageInfo; 26import android.content.pm.PackageInfoLite; 27import android.content.pm.PackageManager; 28import android.content.pm.PackageParser; 29import android.content.res.ObbInfo; 30import android.content.res.ObbScanner; 31import android.net.Uri; 32import android.os.Environment; 33import android.os.IBinder; 34import android.os.ParcelFileDescriptor; 35import android.os.Process; 36import android.os.RemoteException; 37import android.os.ServiceManager; 38import android.os.StatFs; 39import android.app.IntentService; 40import android.util.DisplayMetrics; 41import android.util.Log; 42import android.util.Pair; 43 44import java.io.File; 45import java.io.FileInputStream; 46import java.io.FileNotFoundException; 47import java.io.FileOutputStream; 48import java.io.IOException; 49import java.io.InputStream; 50import java.util.LinkedList; 51import java.util.List; 52import java.util.zip.ZipEntry; 53import java.util.zip.ZipException; 54import java.util.zip.ZipFile; 55 56import android.os.FileUtils; 57import android.provider.Settings; 58 59/* 60 * This service copies a downloaded apk to a file passed in as 61 * a ParcelFileDescriptor or to a newly created container specified 62 * by parameters. The DownloadManager gives access to this process 63 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 64 * permission to access apks downloaded via the download manager. 65 */ 66public class DefaultContainerService extends IntentService { 67 private static final String TAG = "DefContainer"; 68 private static final boolean localLOGV = true; 69 70 private static final String LIB_DIR_NAME = "lib"; 71 72 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 73 /* 74 * Creates a new container and copies resource there. 75 * @param paackageURI the uri of resource to be copied. Can be either 76 * a content uri or a file uri 77 * @param cid the id of the secure container that should 78 * be used for creating a secure container into which the resource 79 * will be copied. 80 * @param key Refers to key used for encrypting the secure container 81 * @param resFileName Name of the target resource file(relative to newly 82 * created secure container) 83 * @return Returns the new cache path where the resource has been copied into 84 * 85 */ 86 public String copyResourceToContainer(final Uri packageURI, 87 final String cid, 88 final String key, final String resFileName) { 89 if (packageURI == null || cid == null) { 90 return null; 91 } 92 return copyResourceInner(packageURI, cid, key, resFileName); 93 } 94 95 /* 96 * Copy specified resource to output stream 97 * @param packageURI the uri of resource to be copied. Should be a 98 * file uri 99 * @param outStream Remote file descriptor to be used for copying 100 * @return Returns true if copy succeded or false otherwise. 101 */ 102 public boolean copyResource(final Uri packageURI, 103 ParcelFileDescriptor outStream) { 104 if (packageURI == null || outStream == null) { 105 return false; 106 } 107 ParcelFileDescriptor.AutoCloseOutputStream 108 autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 109 return copyFile(packageURI, autoOut); 110 } 111 112 /* 113 * Determine the recommended install location for package 114 * specified by file uri location. 115 * @param fileUri the uri of resource to be copied. Should be a 116 * file uri 117 * @return Returns PackageInfoLite object containing 118 * the package info and recommended app location. 119 */ 120 public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags) { 121 PackageInfoLite ret = new PackageInfoLite(); 122 if (fileUri == null) { 123 Log.i(TAG, "Invalid package uri " + fileUri); 124 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 125 return ret; 126 } 127 String scheme = fileUri.getScheme(); 128 if (scheme != null && !scheme.equals("file")) { 129 Log.w(TAG, "Falling back to installing on internal storage only"); 130 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL; 131 return ret; 132 } 133 String archiveFilePath = fileUri.getPath(); 134 PackageParser packageParser = new PackageParser(archiveFilePath); 135 File sourceFile = new File(archiveFilePath); 136 DisplayMetrics metrics = new DisplayMetrics(); 137 metrics.setToDefaults(); 138 PackageParser.PackageLite pkg = packageParser.parsePackageLite( 139 archiveFilePath, 0); 140 // Nuke the parser reference right away and force a gc 141 packageParser = null; 142 Runtime.getRuntime().gc(); 143 if (pkg == null) { 144 Log.w(TAG, "Failed to parse package"); 145 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 146 return ret; 147 } 148 ret.packageName = pkg.packageName; 149 ret.installLocation = pkg.installLocation; 150 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags); 151 return ret; 152 } 153 154 public boolean checkFreeStorage(boolean external, Uri fileUri) { 155 return checkFreeStorageInner(external, fileUri); 156 } 157 158 public ObbInfo getObbInfo(String filename) { 159 try { 160 return ObbScanner.getObbInfo(filename); 161 } catch (IOException e) { 162 Log.d(TAG, "Couldn't get OBB info for " + filename); 163 return null; 164 } 165 } 166 167 @Override 168 public long calculateDirectorySize(String path) throws RemoteException { 169 final File directory = new File(path); 170 if (directory.exists() && directory.isDirectory()) { 171 return MeasurementUtils.measureDirectory(path); 172 } else { 173 return 0L; 174 } 175 } 176 }; 177 178 public DefaultContainerService() { 179 super("DefaultContainerService"); 180 setIntentRedelivery(true); 181 } 182 183 @Override 184 protected void onHandleIntent(Intent intent) { 185 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 186 IPackageManager pm = IPackageManager.Stub.asInterface( 187 ServiceManager.getService("package")); 188 String pkg = null; 189 try { 190 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 191 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 192 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 193 eraseFiles(Environment.getExternalStorageAppObbDirectory(pkg)); 194 } 195 } catch (RemoteException e) { 196 } 197 } 198 } 199 200 void eraseFiles(File path) { 201 if (path.isDirectory()) { 202 String[] files = path.list(); 203 if (files != null) { 204 for (String file : files) { 205 eraseFiles(new File(path, file)); 206 } 207 } 208 } 209 path.delete(); 210 } 211 212 public IBinder onBind(Intent intent) { 213 return mBinder; 214 } 215 216 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { 217 // Make sure the sdcard is mounted. 218 String status = Environment.getExternalStorageState(); 219 if (!status.equals(Environment.MEDIA_MOUNTED)) { 220 Log.w(TAG, "Make sure sdcard is mounted."); 221 return null; 222 } 223 224 // The .apk file 225 String codePath = packageURI.getPath(); 226 File codeFile = new File(codePath); 227 228 // Calculate size of container needed to hold base APK. 229 long sizeBytes = codeFile.length(); 230 231 // Check all the native files that need to be copied and add that to the container size. 232 ZipFile zipFile; 233 List<Pair<ZipEntry, String>> nativeFiles; 234 try { 235 zipFile = new ZipFile(codeFile); 236 237 nativeFiles = new LinkedList<Pair<ZipEntry, String>>(); 238 239 NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles); 240 241 final int N = nativeFiles.size(); 242 for (int i = 0; i < N; i++) { 243 final Pair<ZipEntry, String> entry = nativeFiles.get(i); 244 245 /* 246 * Note that PackageHelper.createSdDir adds a 1MB padding on 247 * our claimed size, so we don't have to worry about block 248 * alignment here. 249 */ 250 sizeBytes += entry.first.getSize(); 251 } 252 } catch (ZipException e) { 253 Log.w(TAG, "Failed to extract data from package file", e); 254 return null; 255 } catch (IOException e) { 256 Log.w(TAG, "Failed to cache package shared libs", e); 257 return null; 258 } 259 260 // Create new container 261 String newCachePath = null; 262 if ((newCachePath = PackageHelper.createSdDir(sizeBytes, newCid, key, Process.myUid())) == null) { 263 Log.e(TAG, "Failed to create container " + newCid); 264 return null; 265 } 266 if (localLOGV) 267 Log.i(TAG, "Created container for " + newCid + " at path : " + newCachePath); 268 File resFile = new File(newCachePath, resFileName); 269 if (!FileUtils.copyFile(new File(codePath), resFile)) { 270 Log.e(TAG, "Failed to copy " + codePath + " to " + resFile); 271 // Clean up container 272 PackageHelper.destroySdDir(newCid); 273 return null; 274 } 275 276 try { 277 File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); 278 sharedLibraryDir.mkdir(); 279 280 final int N = nativeFiles.size(); 281 for (int i = 0; i < N; i++) { 282 final Pair<ZipEntry, String> entry = nativeFiles.get(i); 283 284 InputStream is = zipFile.getInputStream(entry.first); 285 try { 286 File destFile = new File(sharedLibraryDir, entry.second); 287 if (!FileUtils.copyToFile(is, destFile)) { 288 throw new IOException("Couldn't copy native binary " 289 + entry.first.getName() + " to " + entry.second); 290 } 291 } finally { 292 is.close(); 293 } 294 } 295 } catch (IOException e) { 296 Log.e(TAG, "Couldn't copy native file to container", e); 297 PackageHelper.destroySdDir(newCid); 298 return null; 299 } 300 301 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 302 if (!PackageHelper.finalizeSdDir(newCid)) { 303 Log.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath); 304 // Clean up container 305 PackageHelper.destroySdDir(newCid); 306 } 307 if (localLOGV) Log.i(TAG, "Finalized container " + newCid); 308 if (PackageHelper.isContainerMounted(newCid)) { 309 if (localLOGV) Log.i(TAG, "Unmounting " + newCid + 310 " at path " + newCachePath); 311 // Force a gc to avoid being killed. 312 Runtime.getRuntime().gc(); 313 PackageHelper.unMountSdDir(newCid); 314 } else { 315 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 316 } 317 return newCachePath; 318 } 319 320 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 321 try { 322 byte[] buffer = new byte[4096]; 323 int bytesRead; 324 while ((bytesRead = inputStream.read(buffer)) >= 0) { 325 out.write(buffer, 0, bytesRead); 326 } 327 return true; 328 } catch (IOException e) { 329 Log.i(TAG, "Exception : " + e + " when copying file"); 330 return false; 331 } 332 } 333 334 public static boolean copyToFile(File srcFile, FileOutputStream out) { 335 InputStream inputStream = null; 336 try { 337 inputStream = new FileInputStream(srcFile); 338 return copyToFile(inputStream, out); 339 } catch (IOException e) { 340 return false; 341 } finally { 342 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 343 } 344 } 345 346 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 347 String scheme = pPackageURI.getScheme(); 348 if (scheme == null || scheme.equals("file")) { 349 final File srcPackageFile = new File(pPackageURI.getPath()); 350 // We copy the source package file to a temp file and then rename it to the 351 // destination file in order to eliminate a window where the package directory 352 // scanner notices the new package file but it's not completely copied yet. 353 if (!copyToFile(srcPackageFile, outStream)) { 354 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 355 return false; 356 } 357 } else if (scheme.equals("content")) { 358 ParcelFileDescriptor fd = null; 359 try { 360 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 361 } catch (FileNotFoundException e) { 362 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 363 return false; 364 } 365 if (fd == null) { 366 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 367 return false; 368 } else { 369 if (localLOGV) { 370 Log.v(TAG, "Opened file descriptor from download service."); 371 } 372 ParcelFileDescriptor.AutoCloseInputStream 373 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 374 // We copy the source package file to a temp file and then rename it to the 375 // destination file in order to eliminate a window where the package directory 376 // scanner notices the new package file but it's not completely copied yet. 377 if (!copyToFile(dlStream, outStream)) { 378 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 379 return false; 380 } 381 } 382 } else { 383 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 384 return false; 385 } 386 return true; 387 } 388 389 // Constants related to app heuristics 390 // No-installation limit for internal flash: 10% or less space available 391 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 392 393 // SD-to-internal app size threshold: currently set to 1 MB 394 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 395 private static final int ERR_LOC = -1; 396 397 private int recommendAppInstallLocation(int installLocation, 398 String archiveFilePath, int flags) { 399 boolean checkInt = false; 400 boolean checkExt = false; 401 boolean checkBoth = false; 402 check_inner : { 403 // Check flags. 404 if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { 405 // Check for forward locked app 406 checkInt = true; 407 break check_inner; 408 } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { 409 // Explicit flag to install internally. 410 // Check internal storage and return 411 checkInt = true; 412 break check_inner; 413 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { 414 // Explicit flag to install externally. 415 // Check external storage and return 416 checkExt = true; 417 break check_inner; 418 } 419 // Check for manifest option 420 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 421 checkInt = true; 422 break check_inner; 423 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 424 checkExt = true; 425 checkBoth = true; 426 break check_inner; 427 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 428 checkInt = true; 429 checkBoth = true; 430 break check_inner; 431 } 432 // Pick user preference 433 int installPreference = Settings.System.getInt(getApplicationContext() 434 .getContentResolver(), 435 Settings.Secure.DEFAULT_INSTALL_LOCATION, 436 PackageHelper.APP_INSTALL_AUTO); 437 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { 438 checkInt = true; 439 break check_inner; 440 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { 441 checkExt = true; 442 break check_inner; 443 } 444 // Fall back to default policy if nothing else is specified. 445 checkInt = true; 446 } 447 448 // Package size = code size + cache size + data size 449 // If code size > 1 MB, install on SD card. 450 // Else install on internal NAND flash, unless space on NAND is less than 10% 451 String status = Environment.getExternalStorageState(); 452 long availSDSize = -1; 453 boolean mediaAvailable = false; 454 if (!Environment.isExternalStorageEmulated() && status.equals(Environment.MEDIA_MOUNTED)) { 455 StatFs sdStats = new StatFs( 456 Environment.getExternalStorageDirectory().getPath()); 457 availSDSize = (long)sdStats.getAvailableBlocks() * 458 (long)sdStats.getBlockSize(); 459 mediaAvailable = true; 460 } 461 StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 462 long totalInternalSize = (long)internalStats.getBlockCount() * 463 (long)internalStats.getBlockSize(); 464 long availInternalSize = (long)internalStats.getAvailableBlocks() * 465 (long)internalStats.getBlockSize(); 466 467 double pctNandFree = (double)availInternalSize / (double)totalInternalSize; 468 469 File apkFile = new File(archiveFilePath); 470 long pkgLen = apkFile.length(); 471 472 // To make final copy 473 long reqInstallSize = pkgLen; 474 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 475 long reqInternalSize = 0; 476 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 477 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); 478 boolean fitsOnSd = false; 479 if (mediaAvailable && (reqInstallSize < availSDSize)) { 480 // If we do not have an internal size requirement 481 // don't do a threshold check. 482 if (reqInternalSize == 0) { 483 fitsOnSd = true; 484 } else if ((reqInternalSize < availInternalSize) && intThresholdOk) { 485 fitsOnSd = true; 486 } 487 } 488 boolean fitsOnInt = intThresholdOk && intAvailOk; 489 if (checkInt) { 490 // Check for internal memory availability 491 if (fitsOnInt) { 492 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 493 } 494 } else if (checkExt) { 495 if (fitsOnSd) { 496 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 497 } 498 } 499 if (checkBoth) { 500 // Check for internal first 501 if (fitsOnInt) { 502 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 503 } 504 // Check for external next 505 if (fitsOnSd) { 506 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 507 } 508 } 509 if ((checkExt || checkBoth) && !mediaAvailable) { 510 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 511 } 512 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 513 } 514 515 private boolean checkFreeStorageInner(boolean external, Uri packageURI) { 516 File apkFile = new File(packageURI.getPath()); 517 long size = apkFile.length(); 518 if (external) { 519 String status = Environment.getExternalStorageState(); 520 long availSDSize = -1; 521 if (status.equals(Environment.MEDIA_MOUNTED)) { 522 StatFs sdStats = new StatFs( 523 Environment.getExternalStorageDirectory().getPath()); 524 availSDSize = (long)sdStats.getAvailableBlocks() * 525 (long)sdStats.getBlockSize(); 526 } 527 return availSDSize > size; 528 } 529 StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 530 long totalInternalSize = (long)internalStats.getBlockCount() * 531 (long)internalStats.getBlockSize(); 532 long availInternalSize = (long)internalStats.getAvailableBlocks() * 533 (long)internalStats.getBlockSize(); 534 535 double pctNandFree = (double)availInternalSize / (double)totalInternalSize; 536 // To make final copy 537 long reqInstallSize = size; 538 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 539 long reqInternalSize = 0; 540 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 541 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); 542 return intThresholdOk && intAvailOk; 543 } 544} 545