DefaultContainerService.java revision 9b10ef5fe85e9d29721ff0cd15161f960d38a8db
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 // Create new container 170 if ((newCachePath = PackageHelper.createSdDir(codeFile, 171 newCid, key, Process.myUid())) == null) { 172 Log.e(TAG, "Failed creating container " + newCid); 173 return null; 174 } 175 if (localLOGV) Log.i(TAG, "Created container for " + newCid 176 + " at path : " + newCachePath); 177 File resFile = new File(newCachePath, resFileName); 178 // Copy file from codePath 179 if (!FileUtils.copyFile(new File(codePath), resFile)) { 180 Log.e(TAG, "Failed to copy " + codePath + " to " + resFile); 181 // Clean up created container 182 PackageHelper.destroySdDir(newCid); 183 return null; 184 } 185 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 186 // Finalize container now 187 if (!PackageHelper.finalizeSdDir(newCid)) { 188 Log.e(TAG, "Failed to finalize " + newCid + " at cache path " + newCachePath); 189 // Clean up created container 190 PackageHelper.destroySdDir(newCid); 191 return null; 192 } 193 if (localLOGV) Log.i(TAG, "Finalized container " + newCid); 194 // Force a gc to avoid being killed. 195 Runtime.getRuntime().gc(); 196 // Unmount container 197 if (PackageHelper.isContainerMounted(newCid)) { 198 if (localLOGV) Log.i(TAG, "Unmounting " + newCid + 199 " at path " + newCachePath); 200 PackageHelper.unMountSdDir(newCid); 201 } else { 202 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 203 } 204 return newCachePath; 205 } 206 207 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 208 try { 209 byte[] buffer = new byte[4096]; 210 int bytesRead; 211 while ((bytesRead = inputStream.read(buffer)) >= 0) { 212 out.write(buffer, 0, bytesRead); 213 } 214 return true; 215 } catch (IOException e) { 216 Log.i(TAG, "Exception : " + e + " when copying file"); 217 return false; 218 } 219 } 220 221 public static boolean copyToFile(File srcFile, FileOutputStream out) { 222 InputStream inputStream = null; 223 try { 224 inputStream = new FileInputStream(srcFile); 225 return copyToFile(inputStream, out); 226 } catch (IOException e) { 227 return false; 228 } finally { 229 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 230 } 231 } 232 233 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 234 if (pPackageURI.getScheme().equals("file")) { 235 final File srcPackageFile = new File(pPackageURI.getPath()); 236 // We copy the source package file to a temp file and then rename it to the 237 // destination file in order to eliminate a window where the package directory 238 // scanner notices the new package file but it's not completely copied yet. 239 if (!copyToFile(srcPackageFile, outStream)) { 240 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 241 return false; 242 } 243 } else if (pPackageURI.getScheme().equals("content")) { 244 ParcelFileDescriptor fd = null; 245 try { 246 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 247 } catch (FileNotFoundException e) { 248 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 249 return false; 250 } 251 if (fd == null) { 252 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 253 return false; 254 } else { 255 if (localLOGV) { 256 Log.v(TAG, "Opened file descriptor from download service."); 257 } 258 ParcelFileDescriptor.AutoCloseInputStream 259 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 260 // We copy the source package file to a temp file and then rename it to the 261 // destination file in order to eliminate a window where the package directory 262 // scanner notices the new package file but it's not completely copied yet. 263 if (!copyToFile(dlStream, outStream)) { 264 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 265 return false; 266 } 267 } 268 } else { 269 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 270 return false; 271 } 272 return true; 273 } 274 275 // Constants related to app heuristics 276 // No-installation limit for internal flash: 10% or less space available 277 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 278 279 // SD-to-internal app size threshold: currently set to 1 MB 280 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 281 private static final int ERR_LOC = -1; 282 283 public int recommendAppInstallLocation(Package pkg) { 284 // Initial implementation: 285 // Package size = code size + cache size + data size 286 // If code size > 1 MB, install on SD card. 287 // Else install on internal NAND flash, unless space on NAND is less than 10% 288 289 if (pkg == null) { 290 return ERR_LOC; 291 } 292 293 StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath()); 294 StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 295 296 long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() * 297 (long)internalFlashStats.getBlockSize(); 298 long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() * 299 (long)internalFlashStats.getBlockSize(); 300 long availSDSize = (long)sdcardStats.getAvailableBlocks() * 301 (long)sdcardStats.getBlockSize(); 302 303 double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize; 304 305 final String archiveFilePath = pkg.mScanPath; 306 File apkFile = new File(archiveFilePath); 307 long pkgLen = apkFile.length(); 308 309 boolean auto = true; 310 // To make final copy 311 long reqInstallSize = pkgLen; 312 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 313 long reqInternalSize = 0; 314 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 315 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize); 316 boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk && 317 (reqInternalSize < availInternalFlashSize); 318 boolean fitsOnInt = intThresholdOk && intAvailOk; 319 320 // Consider application flags preferences as well... 321 boolean installOnlyOnSd = (pkg.installLocation == 322 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); 323 boolean installOnlyInternal = (pkg.installLocation == 324 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); 325 if (installOnlyInternal) { 326 // If set explicitly in manifest, 327 // let that override everything else 328 auto = false; 329 } else if (installOnlyOnSd){ 330 // Check if this can be accommodated on the sdcard 331 if (fitsOnSd) { 332 auto = false; 333 } 334 } else { 335 // Check if user option is enabled 336 boolean setInstallLoc = Settings.System.getInt(getApplicationContext() 337 .getContentResolver(), 338 Settings.System.SET_INSTALL_LOCATION, 0) != 0; 339 if (setInstallLoc) { 340 // Pick user preference 341 int installPreference = Settings.System.getInt(getApplicationContext() 342 .getContentResolver(), 343 Settings.System.DEFAULT_INSTALL_LOCATION, 344 PackageInfo.INSTALL_LOCATION_AUTO); 345 if (installPreference == 1) { 346 installOnlyInternal = true; 347 auto = false; 348 } else if (installPreference == 2) { 349 installOnlyOnSd = true; 350 auto = false; 351 } 352 } 353 } 354 if (!auto) { 355 if (installOnlyOnSd) { 356 return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC; 357 } else if (installOnlyInternal){ 358 // Check on internal flash 359 return fitsOnInt ? 0 : ERR_LOC; 360 } 361 } 362 // Try to install internally 363 if (fitsOnInt) { 364 return 0; 365 } 366 // Try the sdcard now. 367 if (fitsOnSd) { 368 return PackageManager.INSTALL_EXTERNAL; 369 } 370 // Return error code 371 return ERR_LOC; 372 } 373} 374