DefaultContainerService.java revision cf6eaeaae9e6745dd6e07540812c79821d7043c2
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.Environment; 13import android.os.IBinder; 14import android.os.ParcelFileDescriptor; 15import android.os.Process; 16import android.os.RemoteException; 17import android.os.ServiceManager; 18import android.os.StatFs; 19import android.app.IntentService; 20import android.util.DisplayMetrics; 21import android.util.Log; 22 23import java.io.File; 24import java.io.FileInputStream; 25import java.io.FileNotFoundException; 26import java.io.FileOutputStream; 27import java.io.IOException; 28import java.io.InputStream; 29 30import android.os.FileUtils; 31import android.provider.Settings; 32 33/* 34 * This service copies a downloaded apk to a file passed in as 35 * a ParcelFileDescriptor or to a newly created container specified 36 * by parameters. The DownloadManager gives access to this process 37 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 38 * permission to access apks downloaded via the download manager. 39 */ 40public class DefaultContainerService extends IntentService { 41 private static final String TAG = "DefContainer"; 42 private static final boolean localLOGV = true; 43 44 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 45 /* 46 * Creates a new container and copies resource there. 47 * @param paackageURI the uri of resource to be copied. Can be either 48 * a content uri or a file uri 49 * @param cid the id of the secure container that should 50 * be used for creating a secure container into which the resource 51 * will be copied. 52 * @param key Refers to key used for encrypting the secure container 53 * @param resFileName Name of the target resource file(relative to newly 54 * created secure container) 55 * @return Returns the new cache path where the resource has been copied into 56 * 57 */ 58 public String copyResourceToContainer(final Uri packageURI, 59 final String cid, 60 final String key, final String resFileName) { 61 if (packageURI == null || cid == null) { 62 return null; 63 } 64 return copyResourceInner(packageURI, cid, key, resFileName); 65 } 66 67 /* 68 * Copy specified resource to output stream 69 * @param packageURI the uri of resource to be copied. Should be a 70 * file uri 71 * @param outStream Remote file descriptor to be used for copying 72 * @return Returns true if copy succeded or false otherwise. 73 */ 74 public boolean copyResource(final Uri packageURI, 75 ParcelFileDescriptor outStream) { 76 if (packageURI == null || outStream == null) { 77 return false; 78 } 79 ParcelFileDescriptor.AutoCloseOutputStream 80 autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 81 return copyFile(packageURI, autoOut); 82 } 83 84 /* 85 * Determine the recommended install location for package 86 * specified by file uri location. 87 * @param fileUri the uri of resource to be copied. Should be a 88 * file uri 89 * @return Returns 90 * PackageHelper.RECOMMEND_INSTALL_INTERNAL to install on internal storage 91 * PackageHelper.RECOMMEND_INSTALL_EXTERNAL to install on external media 92 * PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE for storage errors 93 * PackageHelper.RECOMMEND_FAILED_INVALID_APK for parse errors. 94 */ 95 public int getRecommendedInstallLocation(final Uri fileUri) { 96 if (!fileUri.getScheme().equals("file")) { 97 Log.w(TAG, "Falling back to installing on internal storage only"); 98 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 99 } 100 final String archiveFilePath = fileUri.getPath(); 101 PackageParser packageParser = new PackageParser(archiveFilePath); 102 File sourceFile = new File(archiveFilePath); 103 DisplayMetrics metrics = new DisplayMetrics(); 104 metrics.setToDefaults(); 105 PackageParser.Package pkg = packageParser.parsePackage(sourceFile, 106 archiveFilePath, metrics, 0); 107 // Nuke the parser reference right away and force a gc 108 Runtime.getRuntime().gc(); 109 packageParser = null; 110 if (pkg == null) { 111 Log.w(TAG, "Failed to parse package"); 112 return PackageHelper.RECOMMEND_FAILED_INVALID_APK; 113 } 114 int loc = recommendAppInstallLocation(pkg); 115 if (loc == PackageManager.INSTALL_EXTERNAL) { 116 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 117 } else if (loc == ERR_LOC) { 118 Log.i(TAG, "Failed to install insufficient storage"); 119 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 120 } else { 121 // Implies install on internal storage. 122 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 123 } 124 } 125 }; 126 127 public DefaultContainerService() { 128 super("DefaultContainerService"); 129 setIntentRedelivery(true); 130 } 131 132 @Override 133 protected void onHandleIntent(Intent intent) { 134 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 135 IPackageManager pm = IPackageManager.Stub.asInterface( 136 ServiceManager.getService("package")); 137 String pkg = null; 138 try { 139 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 140 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 141 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 142 } 143 } catch (RemoteException e) { 144 } 145 } 146 } 147 148 void eraseFiles(File path) { 149 if (path.isDirectory()) { 150 String[] files = path.list(); 151 if (files != null) { 152 for (String file : files) { 153 eraseFiles(new File(path, file)); 154 } 155 } 156 } 157 path.delete(); 158 } 159 160 public IBinder onBind(Intent intent) { 161 return mBinder; 162 } 163 164 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { 165 // Create new container at newCachePath 166 String codePath = packageURI.getPath(); 167 File codeFile = new File(codePath); 168 String newCachePath = null; 169 final int CREATE_FAILED = 1; 170 final int COPY_FAILED = 2; 171 final int FINALIZE_FAILED = 3; 172 final int PASS = 4; 173 int errCode = CREATE_FAILED; 174 // Create new container 175 if ((newCachePath = PackageHelper.createSdDir(codeFile, 176 newCid, key, Process.myUid())) != null) { 177 if (localLOGV) Log.i(TAG, "Created container for " + newCid 178 + " at path : " + newCachePath); 179 File resFile = new File(newCachePath, resFileName); 180 errCode = COPY_FAILED; 181 // Copy file from codePath 182 if (FileUtils.copyFile(new File(codePath), resFile)) { 183 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 184 errCode = FINALIZE_FAILED; 185 if (PackageHelper.finalizeSdDir(newCid)) { 186 if (localLOGV) Log.i(TAG, "Finalized container " + newCid); 187 errCode = PASS; 188 } 189 } 190 } 191 // Print error based on errCode 192 String errMsg = ""; 193 switch (errCode) { 194 case CREATE_FAILED: 195 errMsg = "CREATE_FAILED"; 196 break; 197 case COPY_FAILED: 198 errMsg = "COPY_FAILED"; 199 if (localLOGV) Log.i(TAG, "Destroying " + newCid + 200 " at path " + newCachePath + " after " + errMsg); 201 PackageHelper.destroySdDir(newCid); 202 break; 203 case FINALIZE_FAILED: 204 errMsg = "FINALIZE_FAILED"; 205 if (localLOGV) Log.i(TAG, "Destroying " + newCid + 206 " at path " + newCachePath + " after " + errMsg); 207 PackageHelper.destroySdDir(newCid); 208 break; 209 default: 210 errMsg = "PASS"; 211 if (PackageHelper.isContainerMounted(newCid)) { 212 if (localLOGV) Log.i(TAG, "Unmounting " + newCid + 213 " at path " + newCachePath + " after " + errMsg); 214 // Force a gc to avoid being killed. 215 Runtime.getRuntime().gc(); 216 PackageHelper.unMountSdDir(newCid); 217 } else { 218 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 219 } 220 break; 221 } 222 if (errCode != PASS) { 223 return null; 224 } 225 return newCachePath; 226 } 227 228 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 229 try { 230 byte[] buffer = new byte[4096]; 231 int bytesRead; 232 while ((bytesRead = inputStream.read(buffer)) >= 0) { 233 out.write(buffer, 0, bytesRead); 234 } 235 return true; 236 } catch (IOException e) { 237 Log.i(TAG, "Exception : " + e + " when copying file"); 238 return false; 239 } 240 } 241 242 public static boolean copyToFile(File srcFile, FileOutputStream out) { 243 InputStream inputStream = null; 244 try { 245 inputStream = new FileInputStream(srcFile); 246 return copyToFile(inputStream, out); 247 } catch (IOException e) { 248 return false; 249 } finally { 250 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 251 } 252 } 253 254 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 255 if (pPackageURI.getScheme().equals("file")) { 256 final File srcPackageFile = new File(pPackageURI.getPath()); 257 // We copy the source package file to a temp file and then rename it to the 258 // destination file in order to eliminate a window where the package directory 259 // scanner notices the new package file but it's not completely copied yet. 260 if (!copyToFile(srcPackageFile, outStream)) { 261 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 262 return false; 263 } 264 } else if (pPackageURI.getScheme().equals("content")) { 265 ParcelFileDescriptor fd = null; 266 try { 267 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 268 } catch (FileNotFoundException e) { 269 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 270 return false; 271 } 272 if (fd == null) { 273 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 274 return false; 275 } else { 276 if (localLOGV) { 277 Log.v(TAG, "Opened file descriptor from download service."); 278 } 279 ParcelFileDescriptor.AutoCloseInputStream 280 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 281 // We copy the source package file to a temp file and then rename it to the 282 // destination file in order to eliminate a window where the package directory 283 // scanner notices the new package file but it's not completely copied yet. 284 if (!copyToFile(dlStream, outStream)) { 285 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 286 return false; 287 } 288 } 289 } else { 290 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 291 return false; 292 } 293 return true; 294 } 295 296 // Constants related to app heuristics 297 // No-installation limit for internal flash: 10% or less space available 298 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 299 300 // SD-to-internal app size threshold: currently set to 1 MB 301 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 302 private static final int ERR_LOC = -1; 303 304 public int recommendAppInstallLocation(Package pkg) { 305 // Initial implementation: 306 // Package size = code size + cache size + data size 307 // If code size > 1 MB, install on SD card. 308 // Else install on internal NAND flash, unless space on NAND is less than 10% 309 310 if (pkg == null) { 311 return ERR_LOC; 312 } 313 314 StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath()); 315 StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 316 317 long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() * 318 (long)internalFlashStats.getBlockSize(); 319 long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() * 320 (long)internalFlashStats.getBlockSize(); 321 long availSDSize = (long)sdcardStats.getAvailableBlocks() * 322 (long)sdcardStats.getBlockSize(); 323 324 double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize; 325 326 final String archiveFilePath = pkg.mScanPath; 327 File apkFile = new File(archiveFilePath); 328 long pkgLen = apkFile.length(); 329 330 boolean auto = true; 331 // To make final copy 332 long reqInstallSize = pkgLen; 333 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 334 long reqInternalSize = 0; 335 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 336 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize); 337 boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk && 338 (reqInternalSize < availInternalFlashSize); 339 boolean fitsOnInt = intThresholdOk && intAvailOk; 340 341 // Consider application flags preferences as well... 342 boolean installOnlyOnSd = (pkg.installLocation == 343 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); 344 boolean installOnlyInternal = (pkg.installLocation == 345 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); 346 if (installOnlyInternal) { 347 // If set explicitly in manifest, 348 // let that override everything else 349 auto = false; 350 } else if (installOnlyOnSd){ 351 // Check if this can be accommodated on the sdcard 352 if (fitsOnSd) { 353 auto = false; 354 } 355 } else { 356 // Check if user option is enabled 357 boolean setInstallLoc = Settings.System.getInt(getApplicationContext() 358 .getContentResolver(), 359 Settings.System.SET_INSTALL_LOCATION, 0) != 0; 360 if (setInstallLoc) { 361 // Pick user preference 362 int installPreference = Settings.System.getInt(getApplicationContext() 363 .getContentResolver(), 364 Settings.System.DEFAULT_INSTALL_LOCATION, 365 PackageInfo.INSTALL_LOCATION_AUTO); 366 if (installPreference == 1) { 367 installOnlyInternal = true; 368 auto = false; 369 } else if (installPreference == 2) { 370 installOnlyOnSd = true; 371 auto = false; 372 } 373 } 374 } 375 if (!auto) { 376 if (installOnlyOnSd) { 377 return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC; 378 } else if (installOnlyInternal){ 379 // Check on internal flash 380 return fitsOnInt ? 0 : ERR_LOC; 381 } 382 } 383 // Try to install internally 384 if (fitsOnInt) { 385 return 0; 386 } 387 // Try the sdcard now. 388 if (fitsOnSd) { 389 return PackageManager.INSTALL_EXTERNAL; 390 } 391 // Return error code 392 return ERR_LOC; 393 } 394} 395