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