DefaultContainerService.java revision 3602f76d417b0940a26f28b7ad892abcfe03ef7c
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 == null) { 97 Log.i(TAG, "Invalid package uri " + fileUri); 98 return PackageHelper.RECOMMEND_FAILED_INVALID_APK; 99 } 100 String scheme = fileUri.getScheme(); 101 if (scheme != null && !scheme.equals("file")) { 102 Log.w(TAG, "Falling back to installing on internal storage only"); 103 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 104 } 105 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, 111 archiveFilePath, metrics, 0); 112 // Nuke the parser reference right away and force a gc 113 Runtime.getRuntime().gc(); 114 packageParser = null; 115 if (pkg == null) { 116 Log.w(TAG, "Failed to parse package"); 117 return PackageHelper.RECOMMEND_FAILED_INVALID_APK; 118 } 119 int loc = recommendAppInstallLocation(pkg); 120 if (loc == PackageManager.INSTALL_EXTERNAL) { 121 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 122 } else if (loc == ERR_LOC) { 123 Log.i(TAG, "Failed to install insufficient storage"); 124 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 125 } else { 126 // Implies install on internal storage. 127 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 128 } 129 } 130 }; 131 132 public DefaultContainerService() { 133 super("DefaultContainerService"); 134 setIntentRedelivery(true); 135 } 136 137 @Override 138 protected void onHandleIntent(Intent intent) { 139 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 140 IPackageManager pm = IPackageManager.Stub.asInterface( 141 ServiceManager.getService("package")); 142 String pkg = null; 143 try { 144 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 145 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 146 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 147 } 148 } catch (RemoteException e) { 149 } 150 } 151 } 152 153 void eraseFiles(File path) { 154 if (path.isDirectory()) { 155 String[] files = path.list(); 156 if (files != null) { 157 for (String file : files) { 158 eraseFiles(new File(path, file)); 159 } 160 } 161 } 162 path.delete(); 163 } 164 165 public IBinder onBind(Intent intent) { 166 return mBinder; 167 } 168 169 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { 170 // Create new container at newCachePath 171 String codePath = packageURI.getPath(); 172 File codeFile = new File(codePath); 173 String newCachePath = null; 174 final int CREATE_FAILED = 1; 175 final int COPY_FAILED = 2; 176 final int FINALIZE_FAILED = 3; 177 final int PASS = 4; 178 int errCode = CREATE_FAILED; 179 // Create new container 180 if ((newCachePath = PackageHelper.createSdDir(codeFile, 181 newCid, key, Process.myUid())) != null) { 182 if (localLOGV) Log.i(TAG, "Created container for " + newCid 183 + " at path : " + newCachePath); 184 File resFile = new File(newCachePath, resFileName); 185 errCode = COPY_FAILED; 186 // Copy file from codePath 187 if (FileUtils.copyFile(new File(codePath), resFile)) { 188 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 189 errCode = FINALIZE_FAILED; 190 if (PackageHelper.finalizeSdDir(newCid)) { 191 if (localLOGV) Log.i(TAG, "Finalized container " + newCid); 192 errCode = PASS; 193 } 194 } 195 } 196 // Print error based on errCode 197 String errMsg = ""; 198 switch (errCode) { 199 case CREATE_FAILED: 200 errMsg = "CREATE_FAILED"; 201 break; 202 case COPY_FAILED: 203 errMsg = "COPY_FAILED"; 204 if (localLOGV) Log.i(TAG, "Destroying " + newCid + 205 " at path " + newCachePath + " after " + errMsg); 206 PackageHelper.destroySdDir(newCid); 207 break; 208 case FINALIZE_FAILED: 209 errMsg = "FINALIZE_FAILED"; 210 if (localLOGV) Log.i(TAG, "Destroying " + newCid + 211 " at path " + newCachePath + " after " + errMsg); 212 PackageHelper.destroySdDir(newCid); 213 break; 214 default: 215 errMsg = "PASS"; 216 if (PackageHelper.isContainerMounted(newCid)) { 217 if (localLOGV) Log.i(TAG, "Unmounting " + newCid + 218 " at path " + newCachePath + " after " + errMsg); 219 // Force a gc to avoid being killed. 220 Runtime.getRuntime().gc(); 221 PackageHelper.unMountSdDir(newCid); 222 } else { 223 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 224 } 225 break; 226 } 227 if (errCode != PASS) { 228 return null; 229 } 230 return newCachePath; 231 } 232 233 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 234 try { 235 byte[] buffer = new byte[4096]; 236 int bytesRead; 237 while ((bytesRead = inputStream.read(buffer)) >= 0) { 238 out.write(buffer, 0, bytesRead); 239 } 240 return true; 241 } catch (IOException e) { 242 Log.i(TAG, "Exception : " + e + " when copying file"); 243 return false; 244 } 245 } 246 247 public static boolean copyToFile(File srcFile, FileOutputStream out) { 248 InputStream inputStream = null; 249 try { 250 inputStream = new FileInputStream(srcFile); 251 return copyToFile(inputStream, out); 252 } catch (IOException e) { 253 return false; 254 } finally { 255 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 256 } 257 } 258 259 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 260 String scheme = pPackageURI.getScheme(); 261 if (scheme == null || scheme.equals("file")) { 262 final File srcPackageFile = new File(pPackageURI.getPath()); 263 // We copy the source package file to a temp file and then rename it to the 264 // destination file in order to eliminate a window where the package directory 265 // scanner notices the new package file but it's not completely copied yet. 266 if (!copyToFile(srcPackageFile, outStream)) { 267 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 268 return false; 269 } 270 } else if (scheme.equals("content")) { 271 ParcelFileDescriptor fd = null; 272 try { 273 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 274 } catch (FileNotFoundException e) { 275 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 276 return false; 277 } 278 if (fd == null) { 279 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 280 return false; 281 } else { 282 if (localLOGV) { 283 Log.v(TAG, "Opened file descriptor from download service."); 284 } 285 ParcelFileDescriptor.AutoCloseInputStream 286 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 287 // We copy the source package file to a temp file and then rename it to the 288 // destination file in order to eliminate a window where the package directory 289 // scanner notices the new package file but it's not completely copied yet. 290 if (!copyToFile(dlStream, outStream)) { 291 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 292 return false; 293 } 294 } 295 } else { 296 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 297 return false; 298 } 299 return true; 300 } 301 302 // Constants related to app heuristics 303 // No-installation limit for internal flash: 10% or less space available 304 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 305 306 // SD-to-internal app size threshold: currently set to 1 MB 307 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 308 private static final int ERR_LOC = -1; 309 310 public int recommendAppInstallLocation(Package pkg) { 311 // Initial implementation: 312 // Package size = code size + cache size + data size 313 // If code size > 1 MB, install on SD card. 314 // Else install on internal NAND flash, unless space on NAND is less than 10% 315 316 if (pkg == null) { 317 return ERR_LOC; 318 } 319 320 StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath()); 321 StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 322 323 long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() * 324 (long)internalFlashStats.getBlockSize(); 325 long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() * 326 (long)internalFlashStats.getBlockSize(); 327 long availSDSize = (long)sdcardStats.getAvailableBlocks() * 328 (long)sdcardStats.getBlockSize(); 329 330 double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize; 331 332 final String archiveFilePath = pkg.mScanPath; 333 File apkFile = new File(archiveFilePath); 334 long pkgLen = apkFile.length(); 335 336 boolean auto = true; 337 // To make final copy 338 long reqInstallSize = pkgLen; 339 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 340 long reqInternalSize = 0; 341 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 342 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize); 343 boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk && 344 (reqInternalSize < availInternalFlashSize); 345 boolean fitsOnInt = intThresholdOk && intAvailOk; 346 347 // Consider application flags preferences as well... 348 boolean installOnlyOnSd = (pkg.installLocation == 349 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); 350 boolean installOnlyInternal = (pkg.installLocation == 351 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); 352 if (installOnlyInternal) { 353 // If set explicitly in manifest, 354 // let that override everything else 355 auto = false; 356 } else if (installOnlyOnSd){ 357 // Check if this can be accommodated on the sdcard 358 if (fitsOnSd) { 359 auto = false; 360 } 361 } else { 362 // Check if user option is enabled 363 boolean setInstallLoc = Settings.System.getInt(getApplicationContext() 364 .getContentResolver(), 365 Settings.System.SET_INSTALL_LOCATION, 0) != 0; 366 if (setInstallLoc) { 367 // Pick user preference 368 int installPreference = Settings.System.getInt(getApplicationContext() 369 .getContentResolver(), 370 Settings.System.DEFAULT_INSTALL_LOCATION, 371 PackageInfo.INSTALL_LOCATION_AUTO); 372 if (installPreference == 1) { 373 installOnlyInternal = true; 374 auto = false; 375 } else if (installPreference == 2) { 376 installOnlyOnSd = true; 377 auto = false; 378 } 379 } 380 } 381 if (!auto) { 382 if (installOnlyOnSd) { 383 return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC; 384 } else if (installOnlyInternal){ 385 // Check on internal flash 386 return fitsOnInt ? 0 : ERR_LOC; 387 } 388 } 389 // Try to install internally 390 if (fitsOnInt) { 391 return 0; 392 } 393 // Try the sdcard now. 394 if (fitsOnSd) { 395 return PackageManager.INSTALL_EXTERNAL; 396 } 397 // Return error code 398 return ERR_LOC; 399 } 400} 401