DefaultContainerService.java revision 8946dd3355fc1dcbad872c0546e356474d4cc5de
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 = false; 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 PackageHelper.unMountSdDir(newCid); 215 } else { 216 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 217 } 218 break; 219 } 220 if (errCode != PASS) { 221 return null; 222 } 223 return newCachePath; 224 } 225 226 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 227 try { 228 byte[] buffer = new byte[4096]; 229 int bytesRead; 230 while ((bytesRead = inputStream.read(buffer)) >= 0) { 231 out.write(buffer, 0, bytesRead); 232 } 233 return true; 234 } catch (IOException e) { 235 Log.i(TAG, "Exception : " + e + " when copying file"); 236 return false; 237 } 238 } 239 240 public static boolean copyToFile(File srcFile, FileOutputStream out) { 241 InputStream inputStream = null; 242 try { 243 inputStream = new FileInputStream(srcFile); 244 return copyToFile(inputStream, out); 245 } catch (IOException e) { 246 return false; 247 } finally { 248 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 249 } 250 } 251 252 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 253 if (pPackageURI.getScheme().equals("file")) { 254 final File srcPackageFile = new File(pPackageURI.getPath()); 255 // We copy the source package file to a temp file and then rename it to the 256 // destination file in order to eliminate a window where the package directory 257 // scanner notices the new package file but it's not completely copied yet. 258 if (!copyToFile(srcPackageFile, outStream)) { 259 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 260 return false; 261 } 262 } else if (pPackageURI.getScheme().equals("content")) { 263 ParcelFileDescriptor fd = null; 264 try { 265 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 266 } catch (FileNotFoundException e) { 267 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 268 return false; 269 } 270 if (fd == null) { 271 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 272 return false; 273 } else { 274 if (localLOGV) { 275 Log.v(TAG, "Opened file descriptor from download service."); 276 } 277 ParcelFileDescriptor.AutoCloseInputStream 278 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 279 // We copy the source package file to a temp file and then rename it to the 280 // destination file in order to eliminate a window where the package directory 281 // scanner notices the new package file but it's not completely copied yet. 282 if (!copyToFile(dlStream, outStream)) { 283 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 284 return false; 285 } 286 } 287 } else { 288 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 289 return false; 290 } 291 return true; 292 } 293 294 // Constants related to app heuristics 295 // No-installation limit for internal flash: 10% or less space available 296 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 297 298 // SD-to-internal app size threshold: currently set to 1 MB 299 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 300 private static final int ERR_LOC = -1; 301 302 public int recommendAppInstallLocation(Package pkg) { 303 // Initial implementation: 304 // Package size = code size + cache size + data size 305 // If code size > 1 MB, install on SD card. 306 // Else install on internal NAND flash, unless space on NAND is less than 10% 307 308 if (pkg == null) { 309 return ERR_LOC; 310 } 311 312 StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath()); 313 StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 314 315 long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() * 316 (long)internalFlashStats.getBlockSize(); 317 long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() * 318 (long)internalFlashStats.getBlockSize(); 319 long availSDSize = (long)sdcardStats.getAvailableBlocks() * 320 (long)sdcardStats.getBlockSize(); 321 322 double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize; 323 324 final String archiveFilePath = pkg.mScanPath; 325 File apkFile = new File(archiveFilePath); 326 long pkgLen = apkFile.length(); 327 328 boolean auto = true; 329 // To make final copy 330 long reqInstallSize = pkgLen; 331 // For dex files 332 long reqInternalSize = 1 * pkgLen; 333 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 334 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize); 335 boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk && 336 (reqInternalSize < availInternalFlashSize); 337 boolean fitsOnInt = intThresholdOk && intAvailOk; 338 339 // Consider application flags preferences as well... 340 boolean installOnlyOnSd = (pkg.installLocation == 341 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); 342 boolean installOnlyInternal = (pkg.installLocation == 343 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); 344 if (installOnlyInternal) { 345 // If set explicitly in manifest, 346 // let that override everything else 347 auto = false; 348 } else if (installOnlyOnSd){ 349 // Check if this can be accommodated on the sdcard 350 if (fitsOnSd) { 351 auto = false; 352 } 353 } else { 354 // Check if user option is enabled 355 boolean setInstallLoc = Settings.System.getInt(getApplicationContext() 356 .getContentResolver(), 357 Settings.System.SET_INSTALL_LOCATION, 0) != 0; 358 if (setInstallLoc) { 359 // Pick user preference 360 int installPreference = Settings.System.getInt(getApplicationContext() 361 .getContentResolver(), 362 Settings.System.DEFAULT_INSTALL_LOCATION, 363 PackageInfo.INSTALL_LOCATION_AUTO); 364 if (installPreference == 1) { 365 installOnlyInternal = true; 366 auto = false; 367 } else if (installPreference == 2) { 368 installOnlyOnSd = true; 369 auto = false; 370 } 371 } 372 } 373 if (!auto) { 374 if (installOnlyOnSd) { 375 return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC; 376 } else if (installOnlyInternal){ 377 // Check on internal flash 378 return fitsOnInt ? 0 : ERR_LOC; 379 } 380 } 381 // Try to install internally 382 if (fitsOnInt) { 383 return 0; 384 } 385 // Try the sdcard now. 386 if (fitsOnSd) { 387 return PackageManager.INSTALL_EXTERNAL; 388 } 389 // Return error code 390 return ERR_LOC; 391 } 392} 393