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