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