DefaultContainerService.java revision 1ebd74acf9977daa42133507e970dab88e08f0ef
10c1bc742181ded4930842b46e9507372f0b1b963James Dong/* 20c1bc742181ded4930842b46e9507372f0b1b963James Dong * Copyright (C) 2010 The Android Open Source Project 30c1bc742181ded4930842b46e9507372f0b1b963James Dong * 40c1bc742181ded4930842b46e9507372f0b1b963James Dong * Licensed under the Apache License, Version 2.0 (the "License"); 50c1bc742181ded4930842b46e9507372f0b1b963James Dong * you may not use this file except in compliance with the License. 60c1bc742181ded4930842b46e9507372f0b1b963James Dong * You may obtain a copy of the License at 70c1bc742181ded4930842b46e9507372f0b1b963James Dong * 80c1bc742181ded4930842b46e9507372f0b1b963James Dong * http://www.apache.org/licenses/LICENSE-2.0 90c1bc742181ded4930842b46e9507372f0b1b963James Dong * 100c1bc742181ded4930842b46e9507372f0b1b963James Dong * Unless required by applicable law or agreed to in writing, software 110c1bc742181ded4930842b46e9507372f0b1b963James Dong * distributed under the License is distributed on an "AS IS" BASIS, 120c1bc742181ded4930842b46e9507372f0b1b963James Dong * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130c1bc742181ded4930842b46e9507372f0b1b963James Dong * See the License for the specific language governing permissions and 140c1bc742181ded4930842b46e9507372f0b1b963James Dong * limitations under the License. 150c1bc742181ded4930842b46e9507372f0b1b963James Dong */ 160c1bc742181ded4930842b46e9507372f0b1b963James Dong 170c1bc742181ded4930842b46e9507372f0b1b963James Dongpackage com.android.defcontainer; 180c1bc742181ded4930842b46e9507372f0b1b963James Dong 190c1bc742181ded4930842b46e9507372f0b1b963James Dongimport com.android.internal.app.IMediaContainerService; 200c1bc742181ded4930842b46e9507372f0b1b963James Dongimport com.android.internal.content.NativeLibraryHelper; 210c1bc742181ded4930842b46e9507372f0b1b963James Dongimport com.android.internal.content.PackageHelper; 220c1bc742181ded4930842b46e9507372f0b1b963James Dong 230c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.Intent; 240c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.pm.IPackageManager; 250c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.pm.PackageInfo; 260c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.pm.PackageInfoLite; 270c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.pm.PackageManager; 280c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.pm.PackageParser; 290c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.res.ObbInfo; 300c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.res.ObbScanner; 310c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.net.Uri; 320c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.Environment; 330c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.IBinder; 340c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.ParcelFileDescriptor; 350c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.Process; 360c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.RemoteException; 370c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.ServiceManager; 380c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.StatFs; 390c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.app.IntentService; 400c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.util.DisplayMetrics; 410c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.util.Slog; 420c1bc742181ded4930842b46e9507372f0b1b963James Dong 430c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.File; 440c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.FileInputStream; 450c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.FileNotFoundException; 460c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.FileOutputStream; 470c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.IOException; 480c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.InputStream; 490c1bc742181ded4930842b46e9507372f0b1b963James Dong 500c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.os.FileUtils; 510c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.provider.Settings; 520c1bc742181ded4930842b46e9507372f0b1b963James Dong 530c1bc742181ded4930842b46e9507372f0b1b963James Dong/* 540c1bc742181ded4930842b46e9507372f0b1b963James Dong * This service copies a downloaded apk to a file passed in as 550c1bc742181ded4930842b46e9507372f0b1b963James Dong * a ParcelFileDescriptor or to a newly created container specified 560c1bc742181ded4930842b46e9507372f0b1b963James Dong * by parameters. The DownloadManager gives access to this process 570c1bc742181ded4930842b46e9507372f0b1b963James Dong * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 580c1bc742181ded4930842b46e9507372f0b1b963James Dong * permission to access apks downloaded via the download manager. 590c1bc742181ded4930842b46e9507372f0b1b963James Dong */ 600c1bc742181ded4930842b46e9507372f0b1b963James Dongpublic class DefaultContainerService extends IntentService { 610c1bc742181ded4930842b46e9507372f0b1b963James Dong private static final String TAG = "DefContainer"; 620c1bc742181ded4930842b46e9507372f0b1b963James Dong private static final boolean localLOGV = true; 630c1bc742181ded4930842b46e9507372f0b1b963James Dong 640c1bc742181ded4930842b46e9507372f0b1b963James Dong private static final String LIB_DIR_NAME = "lib"; 650c1bc742181ded4930842b46e9507372f0b1b963James Dong 660c1bc742181ded4930842b46e9507372f0b1b963James Dong private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 670c1bc742181ded4930842b46e9507372f0b1b963James Dong /* 680c1bc742181ded4930842b46e9507372f0b1b963James Dong * Creates a new container and copies resource there. 690c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param paackageURI the uri of resource to be copied. Can be either 700c1bc742181ded4930842b46e9507372f0b1b963James Dong * a content uri or a file uri 710c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param cid the id of the secure container that should 720c1bc742181ded4930842b46e9507372f0b1b963James Dong * be used for creating a secure container into which the resource 730c1bc742181ded4930842b46e9507372f0b1b963James Dong * will be copied. 740c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param key Refers to key used for encrypting the secure container 750c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param resFileName Name of the target resource file(relative to newly 760c1bc742181ded4930842b46e9507372f0b1b963James Dong * created secure container) 770c1bc742181ded4930842b46e9507372f0b1b963James Dong * @return Returns the new cache path where the resource has been copied into 780c1bc742181ded4930842b46e9507372f0b1b963James Dong * 790c1bc742181ded4930842b46e9507372f0b1b963James Dong */ 800c1bc742181ded4930842b46e9507372f0b1b963James Dong public String copyResourceToContainer(final Uri packageURI, 810c1bc742181ded4930842b46e9507372f0b1b963James Dong final String cid, 820c1bc742181ded4930842b46e9507372f0b1b963James Dong final String key, final String resFileName) { 830c1bc742181ded4930842b46e9507372f0b1b963James Dong if (packageURI == null || cid == null) { 840c1bc742181ded4930842b46e9507372f0b1b963James Dong return null; 850c1bc742181ded4930842b46e9507372f0b1b963James Dong } 860c1bc742181ded4930842b46e9507372f0b1b963James Dong return copyResourceInner(packageURI, cid, key, resFileName); 870c1bc742181ded4930842b46e9507372f0b1b963James Dong } 880c1bc742181ded4930842b46e9507372f0b1b963James Dong 890c1bc742181ded4930842b46e9507372f0b1b963James Dong /* 900c1bc742181ded4930842b46e9507372f0b1b963James Dong * Copy specified resource to output stream 910c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param packageURI the uri of resource to be copied. Should be a 920c1bc742181ded4930842b46e9507372f0b1b963James Dong * file uri 930c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param outStream Remote file descriptor to be used for copying 940c1bc742181ded4930842b46e9507372f0b1b963James Dong * @return Returns true if copy succeded or false otherwise. 950c1bc742181ded4930842b46e9507372f0b1b963James Dong */ 960c1bc742181ded4930842b46e9507372f0b1b963James Dong public boolean copyResource(final Uri packageURI, 970c1bc742181ded4930842b46e9507372f0b1b963James Dong ParcelFileDescriptor outStream) { 980c1bc742181ded4930842b46e9507372f0b1b963James Dong if (packageURI == null || outStream == null) { 990c1bc742181ded4930842b46e9507372f0b1b963James Dong return false; 1000c1bc742181ded4930842b46e9507372f0b1b963James Dong } 1010c1bc742181ded4930842b46e9507372f0b1b963James Dong ParcelFileDescriptor.AutoCloseOutputStream 1020c1bc742181ded4930842b46e9507372f0b1b963James Dong autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 1030c1bc742181ded4930842b46e9507372f0b1b963James Dong return copyFile(packageURI, autoOut); 1040c1bc742181ded4930842b46e9507372f0b1b963James Dong } 1050c1bc742181ded4930842b46e9507372f0b1b963James Dong 1060c1bc742181ded4930842b46e9507372f0b1b963James Dong /* 1070c1bc742181ded4930842b46e9507372f0b1b963James Dong * Determine the recommended install location for package 1080c1bc742181ded4930842b46e9507372f0b1b963James Dong * specified by file uri location. 1090c1bc742181ded4930842b46e9507372f0b1b963James Dong * @param fileUri the uri of resource to be copied. Should be a 1100c1bc742181ded4930842b46e9507372f0b1b963James Dong * file uri 1110c1bc742181ded4930842b46e9507372f0b1b963James Dong * @return Returns PackageInfoLite object containing 1120c1bc742181ded4930842b46e9507372f0b1b963James Dong * the package info and recommended app location. 1130c1bc742181ded4930842b46e9507372f0b1b963James Dong */ 1140c1bc742181ded4930842b46e9507372f0b1b963James Dong public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) { 1150c1bc742181ded4930842b46e9507372f0b1b963James Dong PackageInfoLite ret = new PackageInfoLite(); 1160c1bc742181ded4930842b46e9507372f0b1b963James Dong if (fileUri == null) { 1170c1bc742181ded4930842b46e9507372f0b1b963James Dong Slog.i(TAG, "Invalid package uri " + fileUri); 1180c1bc742181ded4930842b46e9507372f0b1b963James Dong ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 1190c1bc742181ded4930842b46e9507372f0b1b963James Dong return ret; 1200c1bc742181ded4930842b46e9507372f0b1b963James Dong } 121 String scheme = fileUri.getScheme(); 122 if (scheme != null && !scheme.equals("file")) { 123 Slog.w(TAG, "Falling back to installing on internal storage only"); 124 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL; 125 return ret; 126 } 127 String archiveFilePath = fileUri.getPath(); 128 DisplayMetrics metrics = new DisplayMetrics(); 129 metrics.setToDefaults(); 130 131 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0); 132 if (pkg == null) { 133 Slog.w(TAG, "Failed to parse package"); 134 135 final File apkFile = new File(archiveFilePath); 136 if (!apkFile.exists()) { 137 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 138 } else { 139 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 140 } 141 142 return ret; 143 } 144 ret.packageName = pkg.packageName; 145 ret.installLocation = pkg.installLocation; 146 147 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, 148 archiveFilePath, flags, threshold); 149 150 return ret; 151 } 152 153 @Override 154 public boolean checkInternalFreeStorage(Uri packageUri, long threshold) 155 throws RemoteException { 156 final File apkFile = new File(packageUri.getPath()); 157 try { 158 return isUnderInternalThreshold(apkFile, threshold); 159 } catch (FileNotFoundException e) { 160 return true; 161 } 162 } 163 164 @Override 165 public boolean checkExternalFreeStorage(Uri packageUri) throws RemoteException { 166 final File apkFile = new File(packageUri.getPath()); 167 try { 168 return isUnderExternalThreshold(apkFile); 169 } catch (FileNotFoundException e) { 170 return true; 171 } 172 } 173 174 public ObbInfo getObbInfo(String filename) { 175 try { 176 return ObbScanner.getObbInfo(filename); 177 } catch (IOException e) { 178 Slog.d(TAG, "Couldn't get OBB info for " + filename); 179 return null; 180 } 181 } 182 183 @Override 184 public long calculateDirectorySize(String path) throws RemoteException { 185 final File directory = new File(path); 186 if (directory.exists() && directory.isDirectory()) { 187 return MeasurementUtils.measureDirectory(path); 188 } else { 189 return 0L; 190 } 191 } 192 }; 193 194 public DefaultContainerService() { 195 super("DefaultContainerService"); 196 setIntentRedelivery(true); 197 } 198 199 @Override 200 protected void onHandleIntent(Intent intent) { 201 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 202 IPackageManager pm = IPackageManager.Stub.asInterface( 203 ServiceManager.getService("package")); 204 String pkg = null; 205 try { 206 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 207 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 208 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 209 eraseFiles(Environment.getExternalStorageAppObbDirectory(pkg)); 210 } 211 } catch (RemoteException e) { 212 } 213 } 214 } 215 216 void eraseFiles(File path) { 217 if (path.isDirectory()) { 218 String[] files = path.list(); 219 if (files != null) { 220 for (String file : files) { 221 eraseFiles(new File(path, file)); 222 } 223 } 224 } 225 path.delete(); 226 } 227 228 public IBinder onBind(Intent intent) { 229 return mBinder; 230 } 231 232 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { 233 // Make sure the sdcard is mounted. 234 String status = Environment.getExternalStorageState(); 235 if (!status.equals(Environment.MEDIA_MOUNTED)) { 236 Slog.w(TAG, "Make sure sdcard is mounted."); 237 return null; 238 } 239 240 // The .apk file 241 String codePath = packageURI.getPath(); 242 File codeFile = new File(codePath); 243 244 // Calculate size of container needed to hold base APK. 245 int sizeMb; 246 try { 247 sizeMb = calculateContainerSize(codeFile); 248 } catch (FileNotFoundException e) { 249 Slog.w(TAG, "File does not exist when trying to copy " + codeFile.getPath()); 250 return null; 251 } 252 253 // Create new container 254 final String newCachePath; 255 if ((newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid())) == null) { 256 Slog.e(TAG, "Failed to create container " + newCid); 257 return null; 258 } 259 260 if (localLOGV) { 261 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath); 262 } 263 264 final File resFile = new File(newCachePath, resFileName); 265 if (FileUtils.copyFile(new File(codePath), resFile)) { 266 if (localLOGV) { 267 Slog.i(TAG, "Copied " + codePath + " to " + resFile); 268 } 269 } else { 270 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile); 271 // Clean up container 272 PackageHelper.destroySdDir(newCid); 273 return null; 274 } 275 276 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); 277 if (sharedLibraryDir.mkdir()) { 278 int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir); 279 if (ret != PackageManager.INSTALL_SUCCEEDED) { 280 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath()); 281 PackageHelper.destroySdDir(newCid); 282 return null; 283 } 284 } else { 285 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath()); 286 PackageHelper.destroySdDir(newCid); 287 return null; 288 } 289 290 if (!PackageHelper.finalizeSdDir(newCid)) { 291 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath); 292 // Clean up container 293 PackageHelper.destroySdDir(newCid); 294 return null; 295 } 296 297 if (localLOGV) { 298 Slog.i(TAG, "Finalized container " + newCid); 299 } 300 301 if (PackageHelper.isContainerMounted(newCid)) { 302 if (localLOGV) { 303 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath); 304 } 305 306 // Force a gc to avoid being killed. 307 Runtime.getRuntime().gc(); 308 PackageHelper.unMountSdDir(newCid); 309 } else { 310 if (localLOGV) { 311 Slog.i(TAG, "Container " + newCid + " not mounted"); 312 } 313 } 314 315 return newCachePath; 316 } 317 318 private static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 319 try { 320 byte[] buffer = new byte[4096]; 321 int bytesRead; 322 while ((bytesRead = inputStream.read(buffer)) >= 0) { 323 out.write(buffer, 0, bytesRead); 324 } 325 return true; 326 } catch (IOException e) { 327 Slog.i(TAG, "Exception : " + e + " when copying file"); 328 return false; 329 } 330 } 331 332 private static boolean copyToFile(File srcFile, FileOutputStream out) { 333 InputStream inputStream = null; 334 try { 335 inputStream = new FileInputStream(srcFile); 336 return copyToFile(inputStream, out); 337 } catch (IOException e) { 338 return false; 339 } finally { 340 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 341 } 342 } 343 344 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 345 String scheme = pPackageURI.getScheme(); 346 if (scheme == null || scheme.equals("file")) { 347 final File srcPackageFile = new File(pPackageURI.getPath()); 348 // We copy the source package file to a temp file and then rename it to the 349 // destination file in order to eliminate a window where the package directory 350 // scanner notices the new package file but it's not completely copied yet. 351 if (!copyToFile(srcPackageFile, outStream)) { 352 Slog.e(TAG, "Couldn't copy file: " + srcPackageFile); 353 return false; 354 } 355 } else if (scheme.equals("content")) { 356 ParcelFileDescriptor fd = null; 357 try { 358 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 359 } catch (FileNotFoundException e) { 360 Slog.e(TAG, 361 "Couldn't open file descriptor from download service. Failed with exception " 362 + e); 363 return false; 364 } 365 if (fd == null) { 366 Slog.e(TAG, "Couldn't open file descriptor from download service (null)."); 367 return false; 368 } else { 369 if (localLOGV) { 370 Slog.i(TAG, "Opened file descriptor from download service."); 371 } 372 ParcelFileDescriptor.AutoCloseInputStream 373 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 374 // We copy the source package file to a temp file and then rename it to the 375 // destination file in order to eliminate a window where the package directory 376 // scanner notices the new package file but it's not completely 377 // cop 378 if (!copyToFile(dlStream, outStream)) { 379 Slog.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 380 return false; 381 } 382 } 383 } else { 384 Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 385 return false; 386 } 387 return true; 388 } 389 390 private static final int PREFER_INTERNAL = 1; 391 private static final int PREFER_EXTERNAL = 2; 392 393 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags, 394 long threshold) { 395 int prefer; 396 boolean checkBoth = false; 397 398 check_inner : { 399 /* 400 * Explicit install flags should override the manifest settings. 401 */ 402 if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { 403 /* 404 * Forward-locked applications cannot be installed on SD card, 405 * so only allow checking internal storage. 406 */ 407 prefer = PREFER_INTERNAL; 408 break check_inner; 409 } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { 410 prefer = PREFER_INTERNAL; 411 break check_inner; 412 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { 413 prefer = PREFER_EXTERNAL; 414 break check_inner; 415 } 416 417 /* No install flags. Check for manifest option. */ 418 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 419 prefer = PREFER_INTERNAL; 420 break check_inner; 421 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 422 prefer = PREFER_EXTERNAL; 423 checkBoth = true; 424 break check_inner; 425 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 426 // We default to preferring internal storage. 427 prefer = PREFER_INTERNAL; 428 checkBoth = true; 429 break check_inner; 430 } 431 432 // Pick user preference 433 int installPreference = Settings.System.getInt(getApplicationContext() 434 .getContentResolver(), 435 Settings.Secure.DEFAULT_INSTALL_LOCATION, 436 PackageHelper.APP_INSTALL_AUTO); 437 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { 438 prefer = PREFER_INTERNAL; 439 break check_inner; 440 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { 441 prefer = PREFER_EXTERNAL; 442 break check_inner; 443 } 444 445 /* 446 * Fall back to default policy of internal-only if nothing else is 447 * specified. 448 */ 449 prefer = PREFER_INTERNAL; 450 } 451 452 final boolean emulated = Environment.isExternalStorageEmulated(); 453 454 final File apkFile = new File(archiveFilePath); 455 456 boolean fitsOnInternal = false; 457 if (checkBoth || prefer == PREFER_INTERNAL) { 458 try { 459 fitsOnInternal = isUnderInternalThreshold(apkFile, threshold); 460 } catch (FileNotFoundException e) { 461 return PackageHelper.RECOMMEND_FAILED_INVALID_URI; 462 } 463 } 464 465 boolean fitsOnSd = false; 466 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { 467 try { 468 fitsOnSd = isUnderExternalThreshold(apkFile); 469 } catch (FileNotFoundException e) { 470 return PackageHelper.RECOMMEND_FAILED_INVALID_URI; 471 } 472 } 473 474 if (prefer == PREFER_INTERNAL) { 475 if (fitsOnInternal) { 476 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 477 } 478 } else if (!emulated && prefer == PREFER_EXTERNAL) { 479 if (fitsOnSd) { 480 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 481 } 482 } 483 484 if (checkBoth) { 485 if (fitsOnInternal) { 486 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 487 } else if (!emulated && fitsOnSd) { 488 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 489 } 490 } 491 492 /* 493 * If they requested to be on the external media by default, return that 494 * the media was unavailable. Otherwise, indicate there was insufficient 495 * storage space available. 496 */ 497 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) 498 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 499 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 500 } else { 501 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 502 } 503 } 504 505 /** 506 * Measure a file to see if it fits within the free space threshold. 507 * 508 * @param apkFile file to check 509 * @param threshold byte threshold to compare against 510 * @return true if file fits under threshold 511 * @throws FileNotFoundException when APK does not exist 512 */ 513 private boolean isUnderInternalThreshold(File apkFile, long threshold) 514 throws FileNotFoundException { 515 final long size = apkFile.length(); 516 if (size == 0 && !apkFile.exists()) { 517 throw new FileNotFoundException(); 518 } 519 520 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 521 final long availInternalSize = (long) internalStats.getAvailableBlocks() 522 * (long) internalStats.getBlockSize(); 523 524 return (availInternalSize - size) > threshold; 525 } 526 527 528 /** 529 * Measure a file to see if it fits in the external free space. 530 * 531 * @param apkFile file to check 532 * @return true if file fits 533 * @throws IOException when file does not exist 534 */ 535 private boolean isUnderExternalThreshold(File apkFile) throws FileNotFoundException { 536 if (Environment.isExternalStorageEmulated()) { 537 return false; 538 } 539 540 final int sizeMb = calculateContainerSize(apkFile); 541 542 final int availSdMb; 543 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 544 StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 545 long availSdSize = (long) (sdStats.getAvailableBlocks() * sdStats.getBlockSize()); 546 availSdMb = (int) (availSdSize >> 20); 547 } else { 548 availSdMb = -1; 549 } 550 551 return availSdMb > sizeMb; 552 } 553 554 /** 555 * Calculate the container size for an APK. Takes into account the 556 * 557 * @param apkFile file from which to calculate size 558 * @return size in megabytes (2^20 bytes) 559 * @throws FileNotFoundException when file does not exist 560 */ 561 private int calculateContainerSize(File apkFile) throws FileNotFoundException { 562 // Calculate size of container needed to hold base APK. 563 long sizeBytes = apkFile.length(); 564 if (sizeBytes == 0 && !apkFile.exists()) { 565 throw new FileNotFoundException(); 566 } 567 568 // Check all the native files that need to be copied and add that to the 569 // container size. 570 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile); 571 572 int sizeMb = (int) (sizeBytes >> 20); 573 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { 574 sizeMb++; 575 } 576 577 /* 578 * Add buffer size because we don't have a good way to determine the 579 * real FAT size. Your FAT size varies with how many directory entries 580 * you need, how big the whole filesystem is, and other such headaches. 581 */ 582 sizeMb++; 583 584 return sizeMb; 585 } 586} 587