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