DefaultContainerService.java revision 66269ea6f68f2f25888ce1080c94ac782742fafc
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.ArrayList; 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, long threshold) { 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 DisplayMetrics metrics = new DisplayMetrics(); 135 metrics.setToDefaults(); 136 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0); 137 if (pkg == null) { 138 Log.w(TAG, "Failed to parse package"); 139 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 140 return ret; 141 } 142 ret.packageName = pkg.packageName; 143 ret.installLocation = pkg.installLocation; 144 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, 145 archiveFilePath, flags, threshold); 146 return ret; 147 } 148 149 @Override 150 public boolean checkInternalFreeStorage(Uri packageUri, long threshold) 151 throws RemoteException { 152 final File apkFile = new File(packageUri.getPath()); 153 return isUnderInternalThreshold(apkFile, threshold); 154 } 155 156 @Override 157 public boolean checkExternalFreeStorage(Uri packageUri) throws RemoteException { 158 final File apkFile = new File(packageUri.getPath()); 159 return isUnderExternalThreshold(apkFile); 160 } 161 162 public ObbInfo getObbInfo(String filename) { 163 try { 164 return ObbScanner.getObbInfo(filename); 165 } catch (IOException e) { 166 Log.d(TAG, "Couldn't get OBB info for " + filename); 167 return null; 168 } 169 } 170 171 @Override 172 public long calculateDirectorySize(String path) throws RemoteException { 173 final File directory = new File(path); 174 if (directory.exists() && directory.isDirectory()) { 175 return MeasurementUtils.measureDirectory(path); 176 } else { 177 return 0L; 178 } 179 } 180 }; 181 182 public DefaultContainerService() { 183 super("DefaultContainerService"); 184 setIntentRedelivery(true); 185 } 186 187 @Override 188 protected void onHandleIntent(Intent intent) { 189 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 190 IPackageManager pm = IPackageManager.Stub.asInterface( 191 ServiceManager.getService("package")); 192 String pkg = null; 193 try { 194 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 195 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 196 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 197 eraseFiles(Environment.getExternalStorageAppObbDirectory(pkg)); 198 } 199 } catch (RemoteException e) { 200 } 201 } 202 } 203 204 void eraseFiles(File path) { 205 if (path.isDirectory()) { 206 String[] files = path.list(); 207 if (files != null) { 208 for (String file : files) { 209 eraseFiles(new File(path, file)); 210 } 211 } 212 } 213 path.delete(); 214 } 215 216 public IBinder onBind(Intent intent) { 217 return mBinder; 218 } 219 220 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { 221 // Make sure the sdcard is mounted. 222 String status = Environment.getExternalStorageState(); 223 if (!status.equals(Environment.MEDIA_MOUNTED)) { 224 Log.w(TAG, "Make sure sdcard is mounted."); 225 return null; 226 } 227 228 // The .apk file 229 String codePath = packageURI.getPath(); 230 File codeFile = new File(codePath); 231 232 // Native files we need to copy to the container. 233 List<Pair<ZipEntry, String>> nativeFiles = new ArrayList<Pair<ZipEntry, String>>(); 234 235 // Calculate size of container needed to hold base APK. 236 final int sizeMb = calculateContainerSize(codeFile, nativeFiles); 237 238 // Create new container 239 String newCachePath = null; 240 if ((newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid())) == null) { 241 Log.e(TAG, "Failed to create container " + newCid); 242 return null; 243 } 244 if (localLOGV) 245 Log.i(TAG, "Created container for " + newCid + " at path : " + newCachePath); 246 File resFile = new File(newCachePath, resFileName); 247 if (!FileUtils.copyFile(new File(codePath), resFile)) { 248 Log.e(TAG, "Failed to copy " + codePath + " to " + resFile); 249 // Clean up container 250 PackageHelper.destroySdDir(newCid); 251 return null; 252 } 253 254 try { 255 ZipFile zipFile = new ZipFile(codeFile); 256 257 File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); 258 sharedLibraryDir.mkdir(); 259 260 final int N = nativeFiles.size(); 261 for (int i = 0; i < N; i++) { 262 final Pair<ZipEntry, String> entry = nativeFiles.get(i); 263 264 InputStream is = zipFile.getInputStream(entry.first); 265 try { 266 File destFile = new File(sharedLibraryDir, entry.second); 267 if (!FileUtils.copyToFile(is, destFile)) { 268 throw new IOException("Couldn't copy native binary " 269 + entry.first.getName() + " to " + entry.second); 270 } 271 } finally { 272 is.close(); 273 } 274 } 275 } catch (IOException e) { 276 Log.e(TAG, "Couldn't copy native file to container", e); 277 PackageHelper.destroySdDir(newCid); 278 return null; 279 } 280 281 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 282 if (!PackageHelper.finalizeSdDir(newCid)) { 283 Log.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath); 284 // Clean up container 285 PackageHelper.destroySdDir(newCid); 286 } 287 if (localLOGV) Log.i(TAG, "Finalized container " + newCid); 288 if (PackageHelper.isContainerMounted(newCid)) { 289 if (localLOGV) Log.i(TAG, "Unmounting " + newCid + 290 " at path " + newCachePath); 291 // Force a gc to avoid being killed. 292 Runtime.getRuntime().gc(); 293 PackageHelper.unMountSdDir(newCid); 294 } else { 295 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 296 } 297 return newCachePath; 298 } 299 300 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 301 try { 302 byte[] buffer = new byte[4096]; 303 int bytesRead; 304 while ((bytesRead = inputStream.read(buffer)) >= 0) { 305 out.write(buffer, 0, bytesRead); 306 } 307 return true; 308 } catch (IOException e) { 309 Log.i(TAG, "Exception : " + e + " when copying file"); 310 return false; 311 } 312 } 313 314 public static boolean copyToFile(File srcFile, FileOutputStream out) { 315 InputStream inputStream = null; 316 try { 317 inputStream = new FileInputStream(srcFile); 318 return copyToFile(inputStream, out); 319 } catch (IOException e) { 320 return false; 321 } finally { 322 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 323 } 324 } 325 326 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 327 String scheme = pPackageURI.getScheme(); 328 if (scheme == null || scheme.equals("file")) { 329 final File srcPackageFile = new File(pPackageURI.getPath()); 330 // We copy the source package file to a temp file and then rename it to the 331 // destination file in order to eliminate a window where the package directory 332 // scanner notices the new package file but it's not completely copied yet. 333 if (!copyToFile(srcPackageFile, outStream)) { 334 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 335 return false; 336 } 337 } else if (scheme.equals("content")) { 338 ParcelFileDescriptor fd = null; 339 try { 340 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 341 } catch (FileNotFoundException e) { 342 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 343 return false; 344 } 345 if (fd == null) { 346 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 347 return false; 348 } else { 349 if (localLOGV) { 350 Log.v(TAG, "Opened file descriptor from download service."); 351 } 352 ParcelFileDescriptor.AutoCloseInputStream 353 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 354 // We copy the source package file to a temp file and then rename it to the 355 // destination file in order to eliminate a window where the package directory 356 // scanner notices the new package file but it's not completely copied yet. 357 if (!copyToFile(dlStream, outStream)) { 358 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 359 return false; 360 } 361 } 362 } else { 363 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 364 return false; 365 } 366 return true; 367 } 368 369 private static final int PREFER_INTERNAL = 1; 370 private static final int PREFER_EXTERNAL = 2; 371 372 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags, 373 long threshold) { 374 int prefer; 375 boolean checkBoth = false; 376 377 check_inner : { 378 /* 379 * Explicit install flags should override the manifest settings. 380 */ 381 if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { 382 /* 383 * Forward-locked applications cannot be installed on SD card, 384 * so only allow checking internal storage. 385 */ 386 prefer = PREFER_INTERNAL; 387 break check_inner; 388 } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { 389 prefer = PREFER_INTERNAL; 390 break check_inner; 391 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { 392 prefer = PREFER_EXTERNAL; 393 break check_inner; 394 } 395 396 /* No install flags. Check for manifest option. */ 397 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 398 prefer = PREFER_INTERNAL; 399 break check_inner; 400 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 401 prefer = PREFER_EXTERNAL; 402 checkBoth = true; 403 break check_inner; 404 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 405 // We default to preferring internal storage. 406 prefer = PREFER_INTERNAL; 407 checkBoth = true; 408 break check_inner; 409 } 410 411 // Pick user preference 412 int installPreference = Settings.System.getInt(getApplicationContext() 413 .getContentResolver(), 414 Settings.Secure.DEFAULT_INSTALL_LOCATION, 415 PackageHelper.APP_INSTALL_AUTO); 416 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { 417 prefer = PREFER_INTERNAL; 418 break check_inner; 419 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { 420 prefer = PREFER_EXTERNAL; 421 break check_inner; 422 } 423 424 /* 425 * Fall back to default policy of internal-only if nothing else is 426 * specified. 427 */ 428 prefer = PREFER_INTERNAL; 429 } 430 431 final boolean emulated = Environment.isExternalStorageEmulated(); 432 433 final File apkFile = new File(archiveFilePath); 434 435 boolean fitsOnInternal = false; 436 if (checkBoth || prefer == PREFER_INTERNAL) { 437 fitsOnInternal = isUnderInternalThreshold(apkFile, threshold); 438 } 439 440 boolean fitsOnSd = false; 441 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { 442 fitsOnSd = isUnderExternalThreshold(apkFile); 443 } 444 445 if (prefer == PREFER_INTERNAL) { 446 if (fitsOnInternal) { 447 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 448 } 449 } else if (!emulated && prefer == PREFER_EXTERNAL) { 450 if (fitsOnSd) { 451 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 452 } 453 } 454 455 if (checkBoth) { 456 if (fitsOnInternal) { 457 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 458 } else if (!emulated && fitsOnSd) { 459 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 460 } 461 } 462 463 /* 464 * If they requested to be on the external media by default, return that 465 * the media was unavailable. Otherwise, indicate there was insufficient 466 * storage space available. 467 */ 468 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) 469 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 470 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 471 } else { 472 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 473 } 474 } 475 476 private boolean isUnderInternalThreshold(File apkFile, long threshold) { 477 final long size = apkFile.length(); 478 479 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 480 final long availInternalSize = (long) internalStats.getAvailableBlocks() 481 * (long) internalStats.getBlockSize(); 482 483 return (availInternalSize - size) > threshold; 484 } 485 486 487 private boolean isUnderExternalThreshold(File apkFile) { 488 if (Environment.isExternalStorageEmulated()) { 489 return false; 490 } 491 492 final int sizeMb = calculateContainerSize(apkFile, null); 493 494 final int availSdMb; 495 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 496 StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 497 long availSdSize = (long) (sdStats.getAvailableBlocks() * sdStats.getBlockSize()); 498 availSdMb = (int) (availSdSize >> 20); 499 } else { 500 availSdMb = -1; 501 } 502 503 return availSdMb > sizeMb; 504 } 505 506 /** 507 * Calculate the container size for an APK. Takes into account the 508 * 509 * @param apkFile file from which to calculate size 510 * @return size in megabytes (2^20 bytes) 511 */ 512 private int calculateContainerSize(File apkFile, List<Pair<ZipEntry, String>> outFiles) { 513 // Calculate size of container needed to hold base APK. 514 long sizeBytes = apkFile.length(); 515 516 // Check all the native files that need to be copied and add that to the 517 // container size. 518 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile); 519 520 int sizeMb = (int) (sizeBytes >> 20); 521 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { 522 sizeMb++; 523 } 524 525 /* 526 * Add buffer size because we don't have a good way to determine the 527 * real FAT size. Your FAT size varies with how many directory entries 528 * you need, how big the whole filesystem is, and other such headaches. 529 */ 530 sizeMb++; 531 532 return sizeMb; 533 } 534} 535