DefaultContainerService.java revision a02b8b05dd1e8b8cf169e1f89542ef835b11fc13
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.res.ObbInfo; 28import android.content.res.ObbScanner; 29import android.net.Uri; 30import android.os.Environment; 31import android.os.IBinder; 32import android.os.ParcelFileDescriptor; 33import android.os.Process; 34import android.os.RemoteException; 35import android.os.ServiceManager; 36import android.os.StatFs; 37import android.app.IntentService; 38import android.util.DisplayMetrics; 39import android.util.Log; 40 41import java.io.File; 42import java.io.FileInputStream; 43import java.io.FileNotFoundException; 44import java.io.FileOutputStream; 45import java.io.IOException; 46import java.io.InputStream; 47 48import android.os.FileUtils; 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, int flags) { 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 // Nuke the parser reference right away and force a gc 131 packageParser = null; 132 Runtime.getRuntime().gc(); 133 if (pkg == null) { 134 Log.w(TAG, "Failed to parse package"); 135 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 136 return ret; 137 } 138 ret.packageName = pkg.packageName; 139 ret.installLocation = pkg.installLocation; 140 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags); 141 return ret; 142 } 143 144 public boolean checkFreeStorage(boolean external, Uri fileUri) { 145 return checkFreeStorageInner(external, fileUri); 146 } 147 148 public ObbInfo getObbInfo(String filename) { 149 return ObbScanner.getObbInfo(filename); 150 } 151 }; 152 153 public DefaultContainerService() { 154 super("DefaultContainerService"); 155 setIntentRedelivery(true); 156 } 157 158 @Override 159 protected void onHandleIntent(Intent intent) { 160 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 161 IPackageManager pm = IPackageManager.Stub.asInterface( 162 ServiceManager.getService("package")); 163 String pkg = null; 164 try { 165 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 166 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 167 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 168 } 169 } catch (RemoteException e) { 170 } 171 } 172 } 173 174 void eraseFiles(File path) { 175 if (path.isDirectory()) { 176 String[] files = path.list(); 177 if (files != null) { 178 for (String file : files) { 179 eraseFiles(new File(path, file)); 180 } 181 } 182 } 183 path.delete(); 184 } 185 186 public IBinder onBind(Intent intent) { 187 return mBinder; 188 } 189 190 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { 191 // Make sure the sdcard is mounted. 192 String status = Environment.getExternalStorageState(); 193 if (!status.equals(Environment.MEDIA_MOUNTED)) { 194 Log.w(TAG, "Make sure sdcard is mounted."); 195 return null; 196 } 197 // Create new container at newCachePath 198 String codePath = packageURI.getPath(); 199 File codeFile = new File(codePath); 200 String newCachePath = null; 201 // Create new container 202 if ((newCachePath = PackageHelper.createSdDir(codeFile, 203 newCid, key, Process.myUid())) == null) { 204 Log.e(TAG, "Failed to create container " + newCid); 205 return null; 206 } 207 if (localLOGV) Log.i(TAG, "Created container for " + newCid 208 + " at path : " + newCachePath); 209 File resFile = new File(newCachePath, resFileName); 210 if (!FileUtils.copyFile(new File(codePath), resFile)) { 211 Log.e(TAG, "Failed to copy " + codePath + " to " + resFile); 212 // Clean up container 213 PackageHelper.destroySdDir(newCid); 214 return null; 215 } 216 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 217 if (!PackageHelper.finalizeSdDir(newCid)) { 218 Log.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath); 219 // Clean up container 220 PackageHelper.destroySdDir(newCid); 221 } 222 if (localLOGV) Log.i(TAG, "Finalized container " + newCid); 223 if (PackageHelper.isContainerMounted(newCid)) { 224 if (localLOGV) Log.i(TAG, "Unmounting " + newCid + 225 " at path " + newCachePath); 226 // Force a gc to avoid being killed. 227 Runtime.getRuntime().gc(); 228 PackageHelper.unMountSdDir(newCid); 229 } else { 230 if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); 231 } 232 return newCachePath; 233 } 234 235 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 236 try { 237 byte[] buffer = new byte[4096]; 238 int bytesRead; 239 while ((bytesRead = inputStream.read(buffer)) >= 0) { 240 out.write(buffer, 0, bytesRead); 241 } 242 return true; 243 } catch (IOException e) { 244 Log.i(TAG, "Exception : " + e + " when copying file"); 245 return false; 246 } 247 } 248 249 public static boolean copyToFile(File srcFile, FileOutputStream out) { 250 InputStream inputStream = null; 251 try { 252 inputStream = new FileInputStream(srcFile); 253 return copyToFile(inputStream, out); 254 } catch (IOException e) { 255 return false; 256 } finally { 257 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 258 } 259 } 260 261 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 262 String scheme = pPackageURI.getScheme(); 263 if (scheme == null || scheme.equals("file")) { 264 final File srcPackageFile = new File(pPackageURI.getPath()); 265 // We copy the source package file to a temp file and then rename it to the 266 // destination file in order to eliminate a window where the package directory 267 // scanner notices the new package file but it's not completely copied yet. 268 if (!copyToFile(srcPackageFile, outStream)) { 269 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 270 return false; 271 } 272 } else if (scheme.equals("content")) { 273 ParcelFileDescriptor fd = null; 274 try { 275 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 276 } catch (FileNotFoundException e) { 277 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 278 return false; 279 } 280 if (fd == null) { 281 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 282 return false; 283 } else { 284 if (localLOGV) { 285 Log.v(TAG, "Opened file descriptor from download service."); 286 } 287 ParcelFileDescriptor.AutoCloseInputStream 288 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 289 // We copy the source package file to a temp file and then rename it to the 290 // destination file in order to eliminate a window where the package directory 291 // scanner notices the new package file but it's not completely copied yet. 292 if (!copyToFile(dlStream, outStream)) { 293 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 294 return false; 295 } 296 } 297 } else { 298 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 299 return false; 300 } 301 return true; 302 } 303 304 // Constants related to app heuristics 305 // No-installation limit for internal flash: 10% or less space available 306 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; 307 308 // SD-to-internal app size threshold: currently set to 1 MB 309 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); 310 private static final int ERR_LOC = -1; 311 312 private int recommendAppInstallLocation(int installLocation, 313 String archiveFilePath, int flags) { 314 boolean checkInt = false; 315 boolean checkExt = false; 316 boolean checkBoth = false; 317 check_inner : { 318 // Check flags. 319 if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { 320 // Check for forward locked app 321 checkInt = true; 322 break check_inner; 323 } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { 324 // Explicit flag to install internally. 325 // Check internal storage and return 326 checkInt = true; 327 break check_inner; 328 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { 329 // Explicit flag to install externally. 330 // Check external storage and return 331 checkExt = true; 332 break check_inner; 333 } 334 // Check for manifest option 335 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 336 checkInt = true; 337 break check_inner; 338 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 339 checkExt = true; 340 checkBoth = true; 341 break check_inner; 342 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 343 checkInt = true; 344 checkBoth = true; 345 break check_inner; 346 } 347 // Pick user preference 348 int installPreference = Settings.System.getInt(getApplicationContext() 349 .getContentResolver(), 350 Settings.Secure.DEFAULT_INSTALL_LOCATION, 351 PackageHelper.APP_INSTALL_AUTO); 352 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { 353 checkInt = true; 354 break check_inner; 355 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { 356 checkExt = true; 357 break check_inner; 358 } 359 // Fall back to default policy if nothing else is specified. 360 checkInt = true; 361 } 362 363 // Package size = code size + cache size + data size 364 // If code size > 1 MB, install on SD card. 365 // Else install on internal NAND flash, unless space on NAND is less than 10% 366 String status = Environment.getExternalStorageState(); 367 long availSDSize = -1; 368 boolean mediaAvailable = false; 369 if (status.equals(Environment.MEDIA_MOUNTED)) { 370 StatFs sdStats = new StatFs( 371 Environment.getExternalStorageDirectory().getPath()); 372 availSDSize = (long)sdStats.getAvailableBlocks() * 373 (long)sdStats.getBlockSize(); 374 mediaAvailable = true; 375 } 376 StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 377 long totalInternalSize = (long)internalStats.getBlockCount() * 378 (long)internalStats.getBlockSize(); 379 long availInternalSize = (long)internalStats.getAvailableBlocks() * 380 (long)internalStats.getBlockSize(); 381 382 double pctNandFree = (double)availInternalSize / (double)totalInternalSize; 383 384 File apkFile = new File(archiveFilePath); 385 long pkgLen = apkFile.length(); 386 387 // To make final copy 388 long reqInstallSize = pkgLen; 389 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 390 long reqInternalSize = 0; 391 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 392 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); 393 boolean fitsOnSd = false; 394 if (mediaAvailable && (reqInstallSize < availSDSize)) { 395 // If we do not have an internal size requirement 396 // don't do a threshold check. 397 if (reqInternalSize == 0) { 398 fitsOnSd = true; 399 } else if ((reqInternalSize < availInternalSize) && intThresholdOk) { 400 fitsOnSd = true; 401 } 402 } 403 boolean fitsOnInt = intThresholdOk && intAvailOk; 404 if (checkInt) { 405 // Check for internal memory availability 406 if (fitsOnInt) { 407 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 408 } 409 } else if (checkExt) { 410 if (fitsOnSd) { 411 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 412 } 413 } 414 if (checkBoth) { 415 // Check for internal first 416 if (fitsOnInt) { 417 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 418 } 419 // Check for external next 420 if (fitsOnSd) { 421 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 422 } 423 } 424 if ((checkExt || checkBoth) && !mediaAvailable) { 425 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 426 } 427 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 428 } 429 430 private boolean checkFreeStorageInner(boolean external, Uri packageURI) { 431 File apkFile = new File(packageURI.getPath()); 432 long size = apkFile.length(); 433 if (external) { 434 String status = Environment.getExternalStorageState(); 435 long availSDSize = -1; 436 if (status.equals(Environment.MEDIA_MOUNTED)) { 437 StatFs sdStats = new StatFs( 438 Environment.getExternalStorageDirectory().getPath()); 439 availSDSize = (long)sdStats.getAvailableBlocks() * 440 (long)sdStats.getBlockSize(); 441 } 442 return availSDSize > size; 443 } 444 StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 445 long totalInternalSize = (long)internalStats.getBlockCount() * 446 (long)internalStats.getBlockSize(); 447 long availInternalSize = (long)internalStats.getAvailableBlocks() * 448 (long)internalStats.getBlockSize(); 449 450 double pctNandFree = (double)availInternalSize / (double)totalInternalSize; 451 // To make final copy 452 long reqInstallSize = size; 453 // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. 454 long reqInternalSize = 0; 455 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); 456 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); 457 return intThresholdOk && intAvailOk; 458 } 459} 460