DefaultContainerService.java revision 5b993ce7bc29e43a3215a50ce6ce5d6550d4e5e2
1package com.android.defcontainer; 2 3import com.android.internal.app.IMediaContainerService; 4import com.android.internal.content.PackageHelper; 5import android.content.Intent; 6import android.content.pm.IPackageManager; 7import android.content.pm.PackageInfo; 8import android.content.pm.PackageManager; 9import android.content.pm.PackageParser; 10import android.content.pm.PackageParser.Package; 11import android.net.Uri; 12import android.os.Debug; 13import android.os.Environment; 14import android.os.IBinder; 15import android.os.storage.IMountService; 16import android.os.storage.StorageResultCode; 17import android.os.ParcelFileDescriptor; 18import android.os.Process; 19import android.os.RemoteException; 20import android.os.ServiceManager; 21import android.os.StatFs; 22import android.app.IntentService; 23import android.app.Service; 24import android.util.DisplayMetrics; 25import android.util.Log; 26 27import java.io.File; 28import java.io.FileInputStream; 29import java.io.FileNotFoundException; 30import java.io.FileOutputStream; 31import java.io.IOException; 32import java.io.InputStream; 33import java.io.OutputStream; 34 35import android.os.FileUtils; 36import android.provider.Settings; 37 38/* 39 * This service copies a downloaded apk to a file passed in as 40 * a ParcelFileDescriptor or to a newly created container specified 41 * by parameters. The DownloadManager gives access to this process 42 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 43 * permission to access apks downloaded via the download manager. 44 */ 45public class DefaultContainerService extends IntentService { 46 private static final String TAG = "DefContainer"; 47 private static final boolean localLOGV = false; 48 49 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 50 /* 51 * Creates a new container and copies resource there. 52 * @param paackageURI the uri of resource to be copied. Can be either 53 * a content uri or a file uri 54 * @param containerId the id of the secure container that should 55 * be used for creating a secure container into which the resource 56 * will be copied. 57 * @param key Refers to key used for encrypting the secure container 58 * @param resFileName Name of the target resource file(relative to newly 59 * created secure container) 60 * @return Returns the new cache path where the resource has been copied into 61 * 62 */ 63 public String copyResourceToContainer(final Uri packageURI, 64 final String containerId, 65 final String key, final String resFileName) { 66 if (packageURI == null || containerId == null) { 67 return null; 68 } 69 return copyResourceInner(packageURI, containerId, key, resFileName); 70 } 71 72 /* 73 * Copy specified resource to output stream 74 * @param packageURI the uri of resource to be copied. Should be a 75 * file uri 76 * @param outStream Remote file descriptor to be used for copying 77 * @return Returns true if copy succeded or false otherwise. 78 */ 79 public boolean copyResource(final Uri packageURI, 80 ParcelFileDescriptor outStream) { 81 if (packageURI == null || outStream == null) { 82 return false; 83 } 84 ParcelFileDescriptor.AutoCloseOutputStream 85 autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 86 return copyFile(packageURI, autoOut); 87 } 88 89 /* 90 * Determine the recommended install location for package 91 * specified by file uri location. 92 * @param fileUri the uri of resource to be copied. Should be a 93 * file uri 94 * @return Returns 95 * PackageHelper.RECOMMEND_INSTALL_INTERNAL to install on internal storage 96 * PackageHelper.RECOMMEND_INSTALL_EXTERNAL to install on external media 97 * PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE for storage errors 98 * PackageHelper.RECOMMEND_FAILED_INVALID_APK for parse errors. 99 */ 100 public int getRecommendedInstallLocation(final Uri fileUri) { 101 if (!fileUri.getScheme().equals("file")) { 102 Log.w(TAG, "Falling back to installing on internal storage only"); 103 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 104 } 105 final String archiveFilePath = fileUri.getPath(); 106 PackageParser packageParser = new PackageParser(archiveFilePath); 107 File sourceFile = new File(archiveFilePath); 108 DisplayMetrics metrics = new DisplayMetrics(); 109 metrics.setToDefaults(); 110 PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0); 111 if (pkg == null) { 112 Log.w(TAG, "Failed to parse package"); 113 return PackageHelper.RECOMMEND_FAILED_INVALID_APK; 114 } 115 int loc = recommendAppInstallLocation(pkg); 116 if (loc == PackageManager.INSTALL_EXTERNAL) { 117 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 118 } else if (loc == ERR_LOC) { 119 Log.i(TAG, "Failed to install insufficient storage"); 120 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 121 } else { 122 // Implies install on internal storage. 123 return 0; 124 } 125 } 126 }; 127 128 public DefaultContainerService() { 129 super("DefaultContainerService"); 130 setIntentRedelivery(true); 131 } 132 133 @Override 134 protected void onHandleIntent(Intent intent) { 135 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 136 IPackageManager pm = IPackageManager.Stub.asInterface( 137 ServiceManager.getService("package")); 138 String pkg = null; 139 try { 140 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 141 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 142 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 143 } 144 } catch (RemoteException e) { 145 } 146 } 147 } 148 149 void eraseFiles(File path) { 150 if (path.isDirectory()) { 151 String[] files = path.list(); 152 if (files != null) { 153 for (String file : files) { 154 eraseFiles(new File(path, file)); 155 } 156 } 157 } 158 path.delete(); 159 } 160 161 public IBinder onBind(Intent intent) { 162 return mBinder; 163 } 164 165 private IMountService getMountService() { 166 return IMountService.Stub.asInterface(ServiceManager.getService("mount")); 167 } 168 169 private String copyResourceInner(Uri packageURI, String newCacheId, String key, String resFileName) { 170 // Create new container at newCachePath 171 String codePath = packageURI.getPath(); 172 String newCachePath = null; 173 final int CREATE_FAILED = 1; 174 final int COPY_FAILED = 2; 175 final int FINALIZE_FAILED = 3; 176 final int PASS = 4; 177 int errCode = CREATE_FAILED; 178 // Create new container 179 if ((newCachePath = createSdDir(packageURI, newCacheId, key)) != null) { 180 if (localLOGV) Log.i(TAG, "Created container for " + newCacheId 181 + " at path : " + newCachePath); 182 File resFile = new File(newCachePath, resFileName); 183 errCode = COPY_FAILED; 184 // Copy file from codePath 185 if (FileUtils.copyFile(new File(codePath), resFile)) { 186 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 187 errCode = FINALIZE_FAILED; 188 if (finalizeSdDir(newCacheId)) { 189 errCode = PASS; 190 } 191 } 192 } 193 // Print error based on errCode 194 String errMsg = ""; 195 switch (errCode) { 196 case CREATE_FAILED: 197 errMsg = "CREATE_FAILED"; 198 break; 199 case COPY_FAILED: 200 errMsg = "COPY_FAILED"; 201 if (localLOGV) Log.i(TAG, "Destroying " + newCacheId + 202 " at path " + newCachePath + " after " + errMsg); 203 destroySdDir(newCacheId); 204 break; 205 case FINALIZE_FAILED: 206 errMsg = "FINALIZE_FAILED"; 207 if (localLOGV) Log.i(TAG, "Destroying " + newCacheId + 208 " at path " + newCachePath + " after " + errMsg); 209 destroySdDir(newCacheId); 210 break; 211 default: 212 errMsg = "PASS"; 213 if (localLOGV) Log.i(TAG, "Unmounting " + newCacheId + 214 " at path " + newCachePath + " after " + errMsg); 215 unMountSdDir(newCacheId); 216 break; 217 } 218 if (errCode != PASS) { 219 return null; 220 } 221 return newCachePath; 222 } 223 224 private String createSdDir(final Uri packageURI, 225 String containerId, String sdEncKey) { 226 File tmpPackageFile = new File(packageURI.getPath()); 227 // Create mount point via MountService 228 IMountService mountService = getMountService(); 229 long len = tmpPackageFile.length(); 230 int mbLen = (int) (len/(1024*1024)); 231 if ((len - (mbLen * 1024 * 1024)) > 0) { 232 mbLen++; 233 } 234 if (localLOGV) Log.i(TAG, "mbLen=" + mbLen); 235 String cachePath = null; 236 int ownerUid = Process.myUid(); 237 try { 238 int rc = mountService.createSecureContainer( 239 containerId, mbLen, "vfat", sdEncKey, ownerUid); 240 241 if (rc != StorageResultCode.OperationSucceeded) { 242 Log.e(TAG, String.format("Container creation failed (%d)", rc)); 243 244 // XXX: This destroy should not be necessary 245 rc = mountService.destroySecureContainer(containerId); 246 if (rc != StorageResultCode.OperationSucceeded) { 247 Log.e(TAG, String.format("Container creation-cleanup failed (%d)", rc)); 248 return null; 249 } 250 251 // XXX: Does this ever actually succeed? 252 rc = mountService.createSecureContainer( 253 containerId, mbLen, "vfat", sdEncKey, ownerUid); 254 if (rc != StorageResultCode.OperationSucceeded) { 255 Log.e(TAG, String.format("Container creation retry failed (%d)", rc)); 256 } 257 } 258 259 cachePath = mountService.getSecureContainerPath(containerId); 260 if (localLOGV) Log.i(TAG, "Trying to create secure container for " 261 + containerId + ", cachePath =" + cachePath); 262 return cachePath; 263 } catch(RemoteException e) { 264 Log.e(TAG, "MountService not running?"); 265 return null; 266 } 267 } 268 269 private boolean destroySdDir(String containerId) { 270 try { 271 // We need to destroy right away 272 getMountService().destroySecureContainer(containerId); 273 return true; 274 } catch (IllegalStateException e) { 275 Log.i(TAG, "Failed to destroy container : " + containerId); 276 } catch(RemoteException e) { 277 Log.e(TAG, "MountService not running?"); 278 } 279 return false; 280 } 281 282 private boolean finalizeSdDir(String containerId){ 283 try { 284 getMountService().finalizeSecureContainer(containerId); 285 return true; 286 } catch (IllegalStateException e) { 287 Log.i(TAG, "Failed to finalize container for pkg : " + containerId); 288 } catch(RemoteException e) { 289 Log.e(TAG, "MountService not running?"); 290 } 291 return false; 292 } 293 294 private boolean unMountSdDir(String containerId) { 295 try { 296 getMountService().unmountSecureContainer(containerId); 297 return true; 298 } catch (IllegalStateException e) { 299 Log.e(TAG, "Failed to unmount id: " + containerId + " with exception " + e); 300 } catch(RemoteException e) { 301 Log.e(TAG, "MountService not running?"); 302 } 303 return false; 304 } 305 306 private String mountSdDir(String containerId, String key) { 307 try { 308 int rc = getMountService().mountSecureContainer(containerId, key, Process.myUid()); 309 if (rc == StorageResultCode.OperationSucceeded) { 310 return getMountService().getSecureContainerPath(containerId); 311 } else { 312 Log.e(TAG, String.format("Failed to mount id %s with rc %d ", containerId, rc)); 313 } 314 } catch(RemoteException e) { 315 Log.e(TAG, "MountService not running?"); 316 } 317 return null; 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 if (pPackageURI.getScheme().equals("file")) { 348 final File srcPackageFile = new File(pPackageURI.getPath()); 349 // We copy the source package file to a temp file and then rename it to the 350 // destination file in order to eliminate a window where the package directory 351 // scanner notices the new package file but it's not completely copied yet. 352 if (!copyToFile(srcPackageFile, outStream)) { 353 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 354 return false; 355 } 356 } else if (pPackageURI.getScheme().equals("content")) { 357 ParcelFileDescriptor fd = null; 358 try { 359 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 360 } catch (FileNotFoundException e) { 361 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 362 return false; 363 } 364 if (fd == null) { 365 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 366 return false; 367 } else { 368 if (localLOGV) { 369 Log.v(TAG, "Opened file descriptor from download service."); 370 } 371 ParcelFileDescriptor.AutoCloseInputStream 372 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 373 // We copy the source package file to a temp file and then rename it to the 374 // destination file in order to eliminate a window where the package directory 375 // scanner notices the new package file but it's not completely copied yet. 376 if (!copyToFile(dlStream, outStream)) { 377 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 378 return false; 379 } 380 } 381 } else { 382 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 383 return false; 384 } 385 return true; 386 } 387 388 // Constants related to app heuristics 389 // No-installation limit for internal flash: 10% or less space available 390 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 391 392 // SD-to-internal app size threshold: currently set to 1 MB 393 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 394 private static final int ERR_LOC = -1; 395 396 public int recommendAppInstallLocation(Package pkg) { 397 // Initial implementation: 398 // Package size = code size + cache size + data size 399 // If code size > 1 MB, install on SD card. 400 // Else install on internal NAND flash, unless space on NAND is less than 10% 401 402 if (pkg == null) { 403 return ERR_LOC; 404 } 405 406 StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath()); 407 StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 408 409 long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() * 410 (long)internalFlashStats.getBlockSize(); 411 long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() * 412 (long)internalFlashStats.getBlockSize(); 413 long availSDSize = (long)sdcardStats.getAvailableBlocks() * 414 (long)sdcardStats.getBlockSize(); 415 416 double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize; 417 418 final String archiveFilePath = pkg.mScanPath; 419 File apkFile = new File(archiveFilePath); 420 long pkgLen = apkFile.length(); 421 422 boolean auto = true; 423 // To make final copy 424 long reqInstallSize = pkgLen; 425 // For dex files 426 long reqInternalSize = 1 * pkgLen; 427 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 428 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize); 429 boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk && 430 (reqInternalSize < availInternalFlashSize); 431 boolean fitsOnInt = intThresholdOk && intAvailOk; 432 433 // Consider application flags preferences as well... 434 boolean installOnlyOnSd = (pkg.installLocation == 435 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); 436 boolean installOnlyInternal = (pkg.installLocation == 437 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); 438 if (installOnlyInternal) { 439 // If set explicitly in manifest, 440 // let that override everything else 441 auto = false; 442 } else if (installOnlyOnSd){ 443 // Check if this can be accommodated on the sdcard 444 if (fitsOnSd) { 445 auto = false; 446 } 447 } else { 448 // Check if user option is enabled 449 boolean setInstallLoc = Settings.System.getInt(getApplicationContext() 450 .getContentResolver(), 451 Settings.System.SET_INSTALL_LOCATION, 0) != 0; 452 if (setInstallLoc) { 453 // Pick user preference 454 int installPreference = Settings.System.getInt(getApplicationContext() 455 .getContentResolver(), 456 Settings.System.DEFAULT_INSTALL_LOCATION, 457 PackageInfo.INSTALL_LOCATION_AUTO); 458 if (installPreference == 1) { 459 installOnlyInternal = true; 460 auto = false; 461 } else if (installPreference == 2) { 462 installOnlyOnSd = true; 463 auto = false; 464 } 465 } 466 } 467 if (!auto) { 468 if (installOnlyOnSd) { 469 return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC; 470 } else if (installOnlyInternal){ 471 // Check on internal flash 472 return fitsOnInt ? 0 : ERR_LOC; 473 } 474 } 475 // Try to install internally 476 if (fitsOnInt) { 477 return 0; 478 } 479 // Try the sdcard now. 480 if (fitsOnSd) { 481 return PackageManager.INSTALL_EXTERNAL; 482 } 483 // Return error code 484 return ERR_LOC; 485 } 486 487} 488