Kenny Root | 15a4d2f | 2010-03-11 18:20:12 -0800 | [diff] [blame] | 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 | |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 17 | package com.android.defcontainer; |
| 18 | |
| 19 | import com.android.internal.app.IMediaContainerService; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 20 | import com.android.internal.content.PackageHelper; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 21 | import android.content.Intent; |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 22 | import android.content.pm.IPackageManager; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 23 | import android.content.pm.PackageInfo; |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 24 | import android.content.pm.PackageInfoLite; |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 25 | import android.content.pm.PackageManager; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 26 | import android.content.pm.PackageParser; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 27 | import android.net.Uri; |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 28 | import android.os.Environment; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 29 | import android.os.IBinder; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 30 | import android.os.ParcelFileDescriptor; |
| 31 | import android.os.Process; |
| 32 | import android.os.RemoteException; |
| 33 | import android.os.ServiceManager; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 34 | import android.os.StatFs; |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 35 | import android.app.IntentService; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 36 | import android.util.DisplayMetrics; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 37 | import android.util.Log; |
| 38 | |
| 39 | import java.io.File; |
| 40 | import java.io.FileInputStream; |
| 41 | import java.io.FileNotFoundException; |
| 42 | import java.io.FileOutputStream; |
| 43 | import java.io.IOException; |
| 44 | import java.io.InputStream; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 45 | |
| 46 | import android.os.FileUtils; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 47 | import android.provider.Settings; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 48 | |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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 | */ |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 56 | public class DefaultContainerService extends IntentService { |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 57 | private static final String TAG = "DefContainer"; |
Suchi Amalapurapu | cf6eaea | 2010-02-23 19:37:45 -0800 | [diff] [blame] | 58 | private static final boolean localLOGV = true; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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 |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 65 | * @param cid the id of the secure container that should |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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, |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 75 | final String cid, |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 76 | final String key, final String resFileName) { |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 77 | if (packageURI == null || cid == null) { |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 78 | return null; |
| 79 | } |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 80 | return copyResourceInner(packageURI, cid, key, resFileName); |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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 | } |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 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 |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 105 | * @return Returns PackageInfoLite object containing |
| 106 | * the package info and recommended app location. |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 107 | */ |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 108 | public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags) { |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 109 | PackageInfoLite ret = new PackageInfoLite(); |
Suchi Amalapurapu | 3602f76 | 2010-03-03 17:29:33 -0800 | [diff] [blame] | 110 | if (fileUri == null) { |
| 111 | Log.i(TAG, "Invalid package uri " + fileUri); |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 112 | ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; |
| 113 | return ret; |
Suchi Amalapurapu | 3602f76 | 2010-03-03 17:29:33 -0800 | [diff] [blame] | 114 | } |
| 115 | String scheme = fileUri.getScheme(); |
| 116 | if (scheme != null && !scheme.equals("file")) { |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 117 | Log.w(TAG, "Falling back to installing on internal storage only"); |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 118 | ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL; |
| 119 | return ret; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 120 | } |
Suchi Amalapurapu | 3602f76 | 2010-03-03 17:29:33 -0800 | [diff] [blame] | 121 | String archiveFilePath = fileUri.getPath(); |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 122 | PackageParser packageParser = new PackageParser(archiveFilePath); |
| 123 | File sourceFile = new File(archiveFilePath); |
| 124 | DisplayMetrics metrics = new DisplayMetrics(); |
| 125 | metrics.setToDefaults(); |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 126 | PackageParser.PackageLite pkg = packageParser.parsePackageLite( |
| 127 | archiveFilePath, 0); |
| 128 | ret.packageName = pkg.packageName; |
| 129 | ret.installLocation = pkg.installLocation; |
Suchi Amalapurapu | 8946dd3 | 2010-02-19 09:19:34 -0800 | [diff] [blame] | 130 | // Nuke the parser reference right away and force a gc |
Suchi Amalapurapu | 8946dd3 | 2010-02-19 09:19:34 -0800 | [diff] [blame] | 131 | packageParser = null; |
Suchi Amalapurapu | 8a9ab24 | 2010-03-11 16:49:16 -0800 | [diff] [blame] | 132 | Runtime.getRuntime().gc(); |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 133 | if (pkg == null) { |
| 134 | Log.w(TAG, "Failed to parse package"); |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 135 | ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; |
| 136 | return ret; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 137 | } |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 138 | ret.packageName = pkg.packageName; |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 139 | ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags); |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 140 | return ret; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 141 | } |
Suchi Amalapurapu | 8a9ab24 | 2010-03-11 16:49:16 -0800 | [diff] [blame] | 142 | |
| 143 | public boolean checkFreeStorage(boolean external, Uri fileUri) { |
| 144 | return checkFreeStorageInner(external, fileUri); |
| 145 | } |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 146 | }; |
| 147 | |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 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 | } |
Dianne Hackborn | e83cefce | 2010-02-04 17:38:14 -0800 | [diff] [blame] | 178 | path.delete(); |
| 179 | } |
| 180 | |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 181 | public IBinder onBind(Intent intent) { |
| 182 | return mBinder; |
| 183 | } |
| 184 | |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 185 | private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) { |
Suchi Amalapurapu | 8a9ab24 | 2010-03-11 16:49:16 -0800 | [diff] [blame] | 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 | } |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 192 | // Create new container at newCachePath |
| 193 | String codePath = packageURI.getPath(); |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 194 | File codeFile = new File(codePath); |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 195 | String newCachePath = null; |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 196 | // Create new container |
Suchi Amalapurapu | 679bba3 | 2010-02-16 11:52:44 -0800 | [diff] [blame] | 197 | if ((newCachePath = PackageHelper.createSdDir(codeFile, |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 198 | newCid, key, Process.myUid())) == null) { |
| 199 | Log.e(TAG, "Failed to create container " + newCid); |
Suchi Amalapurapu | 9b10ef5 | 2010-03-03 09:45:24 -0800 | [diff] [blame] | 200 | return null; |
| 201 | } |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 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 | } |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 227 | return newCachePath; |
| 228 | } |
| 229 | |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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) { |
Suchi Amalapurapu | 3602f76 | 2010-03-03 17:29:33 -0800 | [diff] [blame] | 257 | String scheme = pPackageURI.getScheme(); |
| 258 | if (scheme == null || scheme.equals("file")) { |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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 | } |
Suchi Amalapurapu | 3602f76 | 2010-03-03 17:29:33 -0800 | [diff] [blame] | 267 | } else if (scheme.equals("content")) { |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 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 | } |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 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 | |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 307 | private int recommendAppInstallLocation(int installLocation, |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 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 | // Check if user option is enabled |
| 343 | boolean setInstallLoc = Settings.System.getInt(getApplicationContext() |
| 344 | .getContentResolver(), |
| 345 | Settings.System.SET_INSTALL_LOCATION, 0) != 0; |
| 346 | if (setInstallLoc) { |
| 347 | // Pick user preference |
| 348 | int installPreference = Settings.System.getInt(getApplicationContext() |
| 349 | .getContentResolver(), |
| 350 | Settings.System.DEFAULT_INSTALL_LOCATION, |
| 351 | PackageHelper.APP_INSTALL_AUTO); |
| 352 | if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { |
| 353 | checkInt = true; |
| 354 | checkBoth = true; |
| 355 | break check_inner; |
| 356 | } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { |
| 357 | checkExt = true; |
| 358 | checkBoth = true; |
| 359 | break check_inner; |
| 360 | } |
| 361 | } |
| 362 | // Fall back to default policy if nothing else is specified. |
| 363 | checkInt = true; |
| 364 | checkBoth = true; |
| 365 | } |
| 366 | |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 367 | // Package size = code size + cache size + data size |
| 368 | // If code size > 1 MB, install on SD card. |
| 369 | // Else install on internal NAND flash, unless space on NAND is less than 10% |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 370 | String status = Environment.getExternalStorageState(); |
| 371 | long availSDSize = -1; |
Suchi Amalapurapu | 8a9ab24 | 2010-03-11 16:49:16 -0800 | [diff] [blame] | 372 | boolean mediaAvailable = false; |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 373 | if (status.equals(Environment.MEDIA_MOUNTED)) { |
| 374 | StatFs sdStats = new StatFs( |
| 375 | Environment.getExternalStorageDirectory().getPath()); |
| 376 | availSDSize = (long)sdStats.getAvailableBlocks() * |
| 377 | (long)sdStats.getBlockSize(); |
Suchi Amalapurapu | 8a9ab24 | 2010-03-11 16:49:16 -0800 | [diff] [blame] | 378 | mediaAvailable = true; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 379 | } |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 380 | StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); |
| 381 | long totalInternalSize = (long)internalStats.getBlockCount() * |
| 382 | (long)internalStats.getBlockSize(); |
| 383 | long availInternalSize = (long)internalStats.getAvailableBlocks() * |
| 384 | (long)internalStats.getBlockSize(); |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 385 | |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 386 | double pctNandFree = (double)availInternalSize / (double)totalInternalSize; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 387 | |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 388 | File apkFile = new File(archiveFilePath); |
| 389 | long pkgLen = apkFile.length(); |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 390 | |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 391 | // To make final copy |
| 392 | long reqInstallSize = pkgLen; |
Suchi Amalapurapu | cf6eaea | 2010-02-23 19:37:45 -0800 | [diff] [blame] | 393 | // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. |
| 394 | long reqInternalSize = 0; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 395 | boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); |
Suchi Amalapurapu | a2b6c37 | 2010-03-05 17:40:11 -0800 | [diff] [blame] | 396 | boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 397 | boolean fitsOnSd = false; |
| 398 | if (mediaAvailable && (reqInstallSize < availSDSize)) { |
| 399 | // If we do not have an internal size requirement |
| 400 | // don't do a threshold check. |
| 401 | if (reqInternalSize == 0) { |
| 402 | fitsOnSd = true; |
| 403 | } else if ((reqInternalSize < availInternalSize) && intThresholdOk) { |
| 404 | fitsOnSd = true; |
| 405 | } |
| 406 | } |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 407 | boolean fitsOnInt = intThresholdOk && intAvailOk; |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 408 | if (checkInt) { |
| 409 | // Check for internal memory availability |
| 410 | if (fitsOnInt) { |
| 411 | return PackageHelper.RECOMMEND_INSTALL_INTERNAL; |
| 412 | } |
| 413 | } else if (checkExt) { |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 414 | if (fitsOnSd) { |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 415 | return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 416 | } |
| 417 | } |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 418 | if (checkBoth) { |
| 419 | // Check for internal first |
| 420 | if (fitsOnInt) { |
| 421 | return PackageHelper.RECOMMEND_INSTALL_INTERNAL; |
| 422 | } |
| 423 | // Check for external next |
| 424 | if (fitsOnSd) { |
| 425 | return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 426 | } |
| 427 | } |
Suchi Amalapurapu | 14b6abd | 2010-03-17 08:37:04 -0700 | [diff] [blame^] | 428 | if (checkExt || checkBoth && !mediaAvailable) { |
| 429 | return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 430 | } |
Suchi Amalapurapu | 8a9ab24 | 2010-03-11 16:49:16 -0800 | [diff] [blame] | 431 | return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; |
| 432 | } |
| 433 | |
| 434 | private boolean checkFreeStorageInner(boolean external, Uri packageURI) { |
| 435 | File apkFile = new File(packageURI.getPath()); |
| 436 | long size = apkFile.length(); |
| 437 | if (external) { |
| 438 | String status = Environment.getExternalStorageState(); |
| 439 | long availSDSize = -1; |
| 440 | if (status.equals(Environment.MEDIA_MOUNTED)) { |
| 441 | StatFs sdStats = new StatFs( |
| 442 | Environment.getExternalStorageDirectory().getPath()); |
| 443 | availSDSize = (long)sdStats.getAvailableBlocks() * |
| 444 | (long)sdStats.getBlockSize(); |
| 445 | } |
| 446 | return availSDSize > size; |
| 447 | } |
| 448 | StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); |
| 449 | long totalInternalSize = (long)internalStats.getBlockCount() * |
| 450 | (long)internalStats.getBlockSize(); |
| 451 | long availInternalSize = (long)internalStats.getAvailableBlocks() * |
| 452 | (long)internalStats.getBlockSize(); |
| 453 | |
| 454 | double pctNandFree = (double)availInternalSize / (double)totalInternalSize; |
| 455 | // To make final copy |
| 456 | long reqInstallSize = size; |
| 457 | // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. |
| 458 | long reqInternalSize = 0; |
| 459 | boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); |
| 460 | boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); |
| 461 | return intThresholdOk && intAvailOk; |
Suchi Amalapurapu | 5b993ce | 2010-02-12 09:43:29 -0800 | [diff] [blame] | 462 | } |
Suchi Amalapurapu | c028be4 | 2010-01-25 12:19:12 -0800 | [diff] [blame] | 463 | } |