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