blob: 6343d0af990b225d20d20db1ac7064f2ba4f9194 [file] [log] [blame]
Kenny Root15a4d2f2010-03-11 18:20:12 -08001/*
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 Amalapurapuc028be42010-01-25 12:19:12 -080017package com.android.defcontainer;
18
Kenny Rootf5121a92011-08-10 16:23:32 -070019import android.app.IntentService;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080020import android.content.Intent;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070021import android.content.pm.ContainerEncryptionParams;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080022import android.content.pm.IPackageManager;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070023import android.content.pm.LimitedLengthInputStream;
Jeff Sharkey63d0a062013-03-01 16:12:55 -080024import android.content.pm.MacAuthenticatedInputStream;
Dianne Hackborn7767eac2012-08-23 18:25:40 -070025import android.content.pm.PackageCleanItem;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080026import android.content.pm.PackageInfo;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -080027import android.content.pm.PackageInfoLite;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080028import android.content.pm.PackageManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080029import android.content.pm.PackageParser;
Kenny Roota02b8b02010-08-05 16:14:17 -070030import android.content.res.ObbInfo;
31import android.content.res.ObbScanner;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080032import android.net.Uri;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080033import android.os.Environment;
Jeff Sharkey752cd922012-09-23 16:25:12 -070034import android.os.Environment.UserEnvironment;
Kenny Rootf5121a92011-08-10 16:23:32 -070035import android.os.FileUtils;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080036import android.os.IBinder;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080037import android.os.ParcelFileDescriptor;
38import android.os.Process;
39import android.os.RemoteException;
40import android.os.ServiceManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080041import android.os.StatFs;
Jeff Sharkey63d0a062013-03-01 16:12:55 -080042import android.os.SystemClock;
Kenny Rootf5121a92011-08-10 16:23:32 -070043import android.provider.Settings;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080044import android.util.DisplayMetrics;
Jeff Sharkey63d0a062013-03-01 16:12:55 -080045import android.util.Log;
Kenny Root1ebd74a2011-08-03 15:09:44 -070046import android.util.Slog;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080047
Jeff Sharkey63d0a062013-03-01 16:12:55 -080048import com.android.internal.app.IMediaContainerService;
49import com.android.internal.content.NativeLibraryHelper;
50import com.android.internal.content.PackageHelper;
51
Kenny Rootf5121a92011-08-10 16:23:32 -070052import java.io.BufferedInputStream;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080053import java.io.File;
54import java.io.FileInputStream;
55import java.io.FileNotFoundException;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080056import java.io.IOException;
57import java.io.InputStream;
Kenny Rootf5121a92011-08-10 16:23:32 -070058import java.io.OutputStream;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070059import java.security.DigestException;
60import java.security.GeneralSecurityException;
61import java.security.InvalidAlgorithmParameterException;
62import java.security.InvalidKeyException;
63import java.security.NoSuchAlgorithmException;
64
65import javax.crypto.Cipher;
66import javax.crypto.CipherInputStream;
67import javax.crypto.Mac;
68import javax.crypto.NoSuchPaddingException;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080069
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070070import libcore.io.ErrnoException;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070071import libcore.io.IoUtils;
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070072import libcore.io.Libcore;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070073import libcore.io.Streams;
Elliott Hughesbefd0b12013-07-09 14:46:18 -070074import libcore.io.StructStatVfs;
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070075
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080076/*
77 * This service copies a downloaded apk to a file passed in as
78 * a ParcelFileDescriptor or to a newly created container specified
79 * by parameters. The DownloadManager gives access to this process
80 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
81 * permission to access apks downloaded via the download manager.
82 */
Dianne Hackborne83cefce2010-02-04 17:38:14 -080083public class DefaultContainerService extends IntentService {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080084 private static final String TAG = "DefContainer";
Dianne Hackborn40e9f292012-11-27 19:12:23 -080085 private static final boolean localLOGV = false;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080086
Kenny Root85387d72010-08-26 10:13:11 -070087 private static final String LIB_DIR_NAME = "lib";
88
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080089 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070090 /**
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080091 * Creates a new container and copies resource there.
92 * @param paackageURI the uri of resource to be copied. Can be either
93 * a content uri or a file uri
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080094 * @param cid the id of the secure container that should
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080095 * be used for creating a secure container into which the resource
96 * will be copied.
97 * @param key Refers to key used for encrypting the secure container
98 * @param resFileName Name of the target resource file(relative to newly
99 * created secure container)
100 * @return Returns the new cache path where the resource has been copied into
101 *
102 */
Kenny Root6dceb882012-04-12 14:23:49 -0700103 public String copyResourceToContainer(final Uri packageURI, final String cid,
104 final String key, final String resFileName, final String publicResFileName,
105 boolean isExternal, boolean isForwardLocked) {
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800106 if (packageURI == null || cid == null) {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800107 return null;
108 }
Kenny Root6dceb882012-04-12 14:23:49 -0700109
110 return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName,
111 isExternal, isForwardLocked);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800112 }
113
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700114 /**
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800115 * Copy specified resource to output stream
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700116 *
Kenny Rootf5121a92011-08-10 16:23:32 -0700117 * @param packageURI the uri of resource to be copied. Should be a file
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700118 * uri
119 * @param encryptionParams parameters describing the encryption used for
120 * this file
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800121 * @param outStream Remote file descriptor to be used for copying
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700122 * @return returns status code according to those in
123 * {@link PackageManager}
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800124 */
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700125 public int copyResource(final Uri packageURI, ContainerEncryptionParams encryptionParams,
126 ParcelFileDescriptor outStream) {
Kenny Rootf5121a92011-08-10 16:23:32 -0700127 if (packageURI == null || outStream == null) {
128 return PackageManager.INSTALL_FAILED_INVALID_URI;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800129 }
Kenny Rootf5121a92011-08-10 16:23:32 -0700130
131 ParcelFileDescriptor.AutoCloseOutputStream autoOut
132 = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
133
134 try {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700135 copyFile(packageURI, autoOut, encryptionParams);
Kenny Rootf5121a92011-08-10 16:23:32 -0700136 return PackageManager.INSTALL_SUCCEEDED;
137 } catch (FileNotFoundException e) {
138 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " FNF: "
139 + e.getMessage());
140 return PackageManager.INSTALL_FAILED_INVALID_URI;
141 } catch (IOException e) {
142 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: "
143 + e.getMessage());
144 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700145 } catch (DigestException e) {
146 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " Security: "
147 + e.getMessage());
148 return PackageManager.INSTALL_FAILED_INVALID_APK;
Jeff Sharkey5adb34f2013-04-25 11:35:12 -0700149 } finally {
150 IoUtils.closeQuietly(autoOut);
Kenny Rootf5121a92011-08-10 16:23:32 -0700151 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800152 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800153
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700154 /**
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800155 * Determine the recommended install location for package
156 * specified by file uri location.
157 * @param fileUri the uri of resource to be copied. Should be a
158 * file uri
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800159 * @return Returns PackageInfoLite object containing
160 * the package info and recommended app location.
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800161 */
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700162 public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
163 long threshold) {
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800164 PackageInfoLite ret = new PackageInfoLite();
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700165
166 if (packagePath == null) {
167 Slog.i(TAG, "Invalid package file " + packagePath);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800168 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
169 return ret;
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800170 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700171
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800172 DisplayMetrics metrics = new DisplayMetrics();
173 metrics.setToDefaults();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700174
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700175 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800176 if (pkg == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700177 Slog.w(TAG, "Failed to parse package");
178
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700179 final File apkFile = new File(packagePath);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700180 if (!apkFile.exists()) {
181 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
182 } else {
183 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
184 }
185
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800186 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800187 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700188
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800189 ret.packageName = pkg.packageName;
Dianne Hackborn7767eac2012-08-23 18:25:40 -0700190 ret.versionCode = pkg.versionCode;
Kenny Root930d3af2010-07-30 16:52:29 -0700191 ret.installLocation = pkg.installLocation;
Kenny Root05ca4c92011-09-15 10:36:25 -0700192 ret.verifiers = pkg.verifiers;
Kenny Root1ebd74a2011-08-03 15:09:44 -0700193
Kenny Root62e1b4e2011-03-14 17:13:39 -0700194 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700195 packagePath, flags, threshold);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700196
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800197 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800198 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800199
Kenny Root62e1b4e2011-03-14 17:13:39 -0700200 @Override
Kenny Root6dceb882012-04-12 14:23:49 -0700201 public boolean checkInternalFreeStorage(Uri packageUri, boolean isForwardLocked,
202 long threshold) throws RemoteException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700203 final File apkFile = new File(packageUri.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700204 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700205 return isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
206 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700207 return true;
208 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700209 }
210
211 @Override
Kenny Root6dceb882012-04-12 14:23:49 -0700212 public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked)
213 throws RemoteException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700214 final File apkFile = new File(packageUri.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700215 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700216 return isUnderExternalThreshold(apkFile, isForwardLocked);
217 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700218 return true;
219 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800220 }
Kenny Roota02b8b02010-08-05 16:14:17 -0700221
222 public ObbInfo getObbInfo(String filename) {
Kenny Root05105f72010-09-22 17:29:43 -0700223 try {
224 return ObbScanner.getObbInfo(filename);
225 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700226 Slog.d(TAG, "Couldn't get OBB info for " + filename);
Kenny Root05105f72010-09-22 17:29:43 -0700227 return null;
228 }
Kenny Roota02b8b02010-08-05 16:14:17 -0700229 }
Kenny Rootaa183e22010-12-02 18:00:38 -0800230
231 @Override
Kenny Root366949c2011-01-14 17:18:14 -0800232 public long calculateDirectorySize(String path) throws RemoteException {
Kenny Roota69b7eb2012-05-14 14:47:06 -0700233 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
234
Jeff Sharkey63d0a062013-03-01 16:12:55 -0800235 final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
236 if (dir.exists() && dir.isDirectory()) {
237 final String targetPath = dir.getAbsolutePath();
238 return MeasurementUtils.measureDirectory(targetPath);
Kenny Root366949c2011-01-14 17:18:14 -0800239 } else {
240 return 0L;
241 }
Kenny Rootaa183e22010-12-02 18:00:38 -0800242 }
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -0700243
244 @Override
245 public long[] getFileSystemStats(String path) {
Kenny Roota69b7eb2012-05-14 14:47:06 -0700246 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
247
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -0700248 try {
Elliott Hughesbefd0b12013-07-09 14:46:18 -0700249 final StructStatVfs stat = Libcore.os.statvfs(path);
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -0700250 final long totalSize = stat.f_blocks * stat.f_bsize;
251 final long availSize = stat.f_bavail * stat.f_bsize;
252 return new long[] { totalSize, availSize };
253 } catch (ErrnoException e) {
254 throw new IllegalStateException(e);
255 }
256 }
Dianne Hackborn183ce022012-06-29 15:00:21 -0700257
258 @Override
259 public void clearDirectory(String path) throws RemoteException {
260 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
261
262 final File directory = new File(path);
263 if (directory.exists() && directory.isDirectory()) {
264 eraseFiles(directory);
265 }
266 }
Kenny Rootcea37432012-10-18 14:57:33 -0700267
268 @Override
269 public long calculateInstalledSize(String packagePath, boolean isForwardLocked)
270 throws RemoteException {
271 final File packageFile = new File(packagePath);
272 try {
273 return calculateContainerSize(packageFile, isForwardLocked) * 1024 * 1024;
274 } catch (IOException e) {
275 /*
276 * Okay, something failed, so let's just estimate it to be 2x
277 * the file size. Note this will be 0 if the file doesn't exist.
278 */
279 return packageFile.length() * 2;
280 }
281 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800282 };
283
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800284 public DefaultContainerService() {
285 super("DefaultContainerService");
286 setIntentRedelivery(true);
287 }
288
289 @Override
290 protected void onHandleIntent(Intent intent) {
291 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
Jeff Sharkey752cd922012-09-23 16:25:12 -0700292 final IPackageManager pm = IPackageManager.Stub.asInterface(
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800293 ServiceManager.getService("package"));
Jeff Sharkey752cd922012-09-23 16:25:12 -0700294 PackageCleanItem item = null;
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800295 try {
Jeff Sharkey752cd922012-09-23 16:25:12 -0700296 while ((item = pm.nextPackageToClean(item)) != null) {
297 final UserEnvironment userEnv = new UserEnvironment(item.userId);
298 eraseFiles(userEnv.getExternalStorageAppDataDirectory(item.packageName));
299 eraseFiles(userEnv.getExternalStorageAppMediaDirectory(item.packageName));
300 if (item.andCode) {
301 eraseFiles(userEnv.getExternalStorageAppObbDirectory(item.packageName));
Dianne Hackborn7767eac2012-08-23 18:25:40 -0700302 }
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800303 }
304 } catch (RemoteException e) {
305 }
306 }
307 }
308
309 void eraseFiles(File path) {
310 if (path.isDirectory()) {
311 String[] files = path.list();
312 if (files != null) {
313 for (String file : files) {
314 eraseFiles(new File(path, file));
315 }
316 }
317 }
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800318 path.delete();
319 }
320
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800321 public IBinder onBind(Intent intent) {
322 return mBinder;
323 }
324
Kenny Root6dceb882012-04-12 14:23:49 -0700325 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName,
326 String publicResFileName, boolean isExternal, boolean isForwardLocked) {
327
328 if (isExternal) {
329 // Make sure the sdcard is mounted.
330 String status = Environment.getExternalStorageState();
331 if (!status.equals(Environment.MEDIA_MOUNTED)) {
332 Slog.w(TAG, "Make sure sdcard is mounted.");
333 return null;
334 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800335 }
Kenny Root85387d72010-08-26 10:13:11 -0700336
337 // The .apk file
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800338 String codePath = packageURI.getPath();
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800339 File codeFile = new File(codePath);
Kenny Root85387d72010-08-26 10:13:11 -0700340
341 // Calculate size of container needed to hold base APK.
Kenny Root6dceb882012-04-12 14:23:49 -0700342 final int sizeMb;
Kenny Root1ebd74a2011-08-03 15:09:44 -0700343 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700344 sizeMb = calculateContainerSize(codeFile, isForwardLocked);
345 } catch (IOException e) {
346 Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700347 return null;
348 }
Kenny Root85387d72010-08-26 10:13:11 -0700349
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800350 // Create new container
Kenny Root6dceb882012-04-12 14:23:49 -0700351 final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(),
352 isExternal);
353 if (newCachePath == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700354 Slog.e(TAG, "Failed to create container " + newCid);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800355 return null;
356 }
Kenny Root85387d72010-08-26 10:13:11 -0700357
Kenny Root1ebd74a2011-08-03 15:09:44 -0700358 if (localLOGV) {
359 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
360 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700361
Kenny Root1ebd74a2011-08-03 15:09:44 -0700362 final File resFile = new File(newCachePath, resFileName);
363 if (FileUtils.copyFile(new File(codePath), resFile)) {
364 if (localLOGV) {
365 Slog.i(TAG, "Copied " + codePath + " to " + resFile);
Kenny Root85387d72010-08-26 10:13:11 -0700366 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700367 } else {
368 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile);
369 // Clean up container
Kenny Root85387d72010-08-26 10:13:11 -0700370 PackageHelper.destroySdDir(newCid);
371 return null;
372 }
373
Kenny Rootbf023582012-05-02 16:56:15 -0700374 try {
375 Libcore.os.chmod(resFile.getAbsolutePath(), 0640);
376 } catch (ErrnoException e) {
377 Slog.e(TAG, "Could not chown APK: " + e.getMessage());
378 PackageHelper.destroySdDir(newCid);
379 return null;
380 }
381
Kenny Root6dceb882012-04-12 14:23:49 -0700382 if (isForwardLocked) {
383 File publicZipFile = new File(newCachePath, publicResFileName);
384 try {
385 PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile);
386 if (localLOGV) {
387 Slog.i(TAG, "Copied resources to " + publicZipFile);
388 }
389 } catch (IOException e) {
390 Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": "
391 + e.getMessage());
392 PackageHelper.destroySdDir(newCid);
393 return null;
394 }
395
396 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700397 Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644);
398 } catch (ErrnoException e) {
Kenny Rootbf023582012-05-02 16:56:15 -0700399 Slog.e(TAG, "Could not chown public resource file: " + e.getMessage());
Kenny Root6dceb882012-04-12 14:23:49 -0700400 PackageHelper.destroySdDir(newCid);
401 return null;
402 }
403 }
404
Kenny Root1ebd74a2011-08-03 15:09:44 -0700405 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
406 if (sharedLibraryDir.mkdir()) {
407 int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
408 if (ret != PackageManager.INSTALL_SUCCEEDED) {
409 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
410 PackageHelper.destroySdDir(newCid);
411 return null;
412 }
413 } else {
414 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath());
415 PackageHelper.destroySdDir(newCid);
416 return null;
417 }
418
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800419 if (!PackageHelper.finalizeSdDir(newCid)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700420 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800421 // Clean up container
422 PackageHelper.destroySdDir(newCid);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700423 return null;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800424 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700425
426 if (localLOGV) {
427 Slog.i(TAG, "Finalized container " + newCid);
428 }
429
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800430 if (PackageHelper.isContainerMounted(newCid)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700431 if (localLOGV) {
432 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath);
433 }
434
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800435 // Force a gc to avoid being killed.
436 Runtime.getRuntime().gc();
437 PackageHelper.unMountSdDir(newCid);
438 } else {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700439 if (localLOGV) {
440 Slog.i(TAG, "Container " + newCid + " not mounted");
441 }
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800442 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700443
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800444 return newCachePath;
445 }
446
Kenny Rootf5121a92011-08-10 16:23:32 -0700447 private static void copyToFile(InputStream inputStream, OutputStream out) throws IOException {
448 byte[] buffer = new byte[16384];
449 int bytesRead;
450 while ((bytesRead = inputStream.read(buffer)) >= 0) {
451 out.write(buffer, 0, bytesRead);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800452 }
453 }
454
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700455 private void copyFile(Uri pPackageURI, OutputStream outStream,
456 ContainerEncryptionParams encryptionParams) throws FileNotFoundException, IOException,
457 DigestException {
458 String scheme = pPackageURI.getScheme();
459 InputStream inStream = null;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800460 try {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700461 if (scheme == null || scheme.equals("file")) {
462 final InputStream is = new FileInputStream(new File(pPackageURI.getPath()));
463 inStream = new BufferedInputStream(is);
464 } else if (scheme.equals("content")) {
465 final ParcelFileDescriptor fd;
466 try {
467 fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
468 } catch (FileNotFoundException e) {
469 Slog.e(TAG, "Couldn't open file descriptor from download service. "
470 + "Failed with exception " + e);
471 throw e;
472 }
473
474 if (fd == null) {
475 Slog.e(TAG, "Provider returned no file descriptor for " +
476 pPackageURI.toString());
477 throw new FileNotFoundException("provider returned no file descriptor");
478 } else {
479 if (localLOGV) {
480 Slog.i(TAG, "Opened file descriptor from download service.");
481 }
482 inStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
483 }
484 } else {
485 Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
486 throw new FileNotFoundException("Package URI is not 'file:' or 'content:'");
487 }
488
489 /*
490 * If this resource is encrypted, get the decrypted stream version
491 * of it.
492 */
493 ApkContainer container = new ApkContainer(inStream, encryptionParams);
494
495 try {
496 /*
497 * We copy the source package file to a temp file and then
498 * rename it to the destination file in order to eliminate a
499 * window where the package directory scanner notices the new
500 * package file but it's not completely copied yet.
501 */
502 copyToFile(container.getInputStream(), outStream);
503
504 if (!container.isAuthenticated()) {
505 throw new DigestException();
506 }
507 } catch (GeneralSecurityException e) {
508 throw new DigestException("A problem occured copying the file.");
509 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800510 } finally {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700511 IoUtils.closeQuietly(inStream);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800512 }
513 }
514
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700515 private static class ApkContainer {
Kenny Root103d5302012-05-10 10:21:06 -0700516 private static final int MAX_AUTHENTICATED_DATA_SIZE = 16384;
517
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700518 private final InputStream mInStream;
Kenny Rootf5121a92011-08-10 16:23:32 -0700519
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700520 private MacAuthenticatedInputStream mAuthenticatedStream;
521
522 private byte[] mTag;
523
524 public ApkContainer(InputStream inStream, ContainerEncryptionParams encryptionParams)
525 throws IOException {
526 if (encryptionParams == null) {
527 mInStream = inStream;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800528 } else {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700529 mInStream = getDecryptedStream(inStream, encryptionParams);
530 mTag = encryptionParams.getMacTag();
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800531 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800532 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700533
534 public boolean isAuthenticated() {
535 if (mAuthenticatedStream == null) {
536 return true;
537 }
538
539 return mAuthenticatedStream.isTagEqual(mTag);
540 }
541
542 private Mac getMacInstance(ContainerEncryptionParams encryptionParams) throws IOException {
543 final Mac m;
544 try {
545 final String macAlgo = encryptionParams.getMacAlgorithm();
546
547 if (macAlgo != null) {
548 m = Mac.getInstance(macAlgo);
549 m.init(encryptionParams.getMacKey(), encryptionParams.getMacSpec());
550 } else {
551 m = null;
552 }
553
554 return m;
555 } catch (NoSuchAlgorithmException e) {
556 throw new IOException(e);
557 } catch (InvalidKeyException e) {
558 throw new IOException(e);
559 } catch (InvalidAlgorithmParameterException e) {
560 throw new IOException(e);
561 }
562 }
563
564 public InputStream getInputStream() {
565 return mInStream;
566 }
567
568 private InputStream getDecryptedStream(InputStream inStream,
569 ContainerEncryptionParams encryptionParams) throws IOException {
570 final Cipher c;
571 try {
572 c = Cipher.getInstance(encryptionParams.getEncryptionAlgorithm());
573 c.init(Cipher.DECRYPT_MODE, encryptionParams.getEncryptionKey(),
574 encryptionParams.getEncryptionSpec());
575 } catch (NoSuchAlgorithmException e) {
576 throw new IOException(e);
577 } catch (NoSuchPaddingException e) {
578 throw new IOException(e);
579 } catch (InvalidKeyException e) {
580 throw new IOException(e);
581 } catch (InvalidAlgorithmParameterException e) {
582 throw new IOException(e);
583 }
584
Kenny Root103d5302012-05-10 10:21:06 -0700585 final long encStart = encryptionParams.getEncryptedDataStart();
586 final long end = encryptionParams.getDataEnd();
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700587 if (end < encStart) {
588 throw new IOException("end <= encStart");
589 }
590
591 final Mac mac = getMacInstance(encryptionParams);
592 if (mac != null) {
Kenny Root103d5302012-05-10 10:21:06 -0700593 final long macStart = encryptionParams.getAuthenticatedDataStart();
594 if (macStart >= Integer.MAX_VALUE) {
595 throw new IOException("macStart >= Integer.MAX_VALUE");
596 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700597
Kenny Root103d5302012-05-10 10:21:06 -0700598 final long furtherOffset;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700599 if (macStart >= 0 && encStart >= 0 && macStart < encStart) {
600 /*
601 * If there is authenticated data at the beginning, read
602 * that into our MAC first.
603 */
Kenny Root103d5302012-05-10 10:21:06 -0700604 final long authenticatedLengthLong = encStart - macStart;
605 if (authenticatedLengthLong > MAX_AUTHENTICATED_DATA_SIZE) {
606 throw new IOException("authenticated data is too long");
607 }
608 final int authenticatedLength = (int) authenticatedLengthLong;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700609
Kenny Root103d5302012-05-10 10:21:06 -0700610 final byte[] authenticatedData = new byte[(int) authenticatedLength];
611
612 Streams.readFully(inStream, authenticatedData, (int) macStart,
613 authenticatedLength);
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700614 mac.update(authenticatedData, 0, authenticatedLength);
615
616 furtherOffset = 0;
617 } else {
618 /*
619 * No authenticated data at the beginning. Just skip the
620 * required number of bytes to the beginning of the stream.
621 */
622 if (encStart > 0) {
623 furtherOffset = encStart;
624 } else {
625 furtherOffset = 0;
626 }
627 }
628
629 /*
630 * If there is data at the end of the stream we want to ignore,
631 * wrap this in a LimitedLengthInputStream.
632 */
633 if (furtherOffset >= 0 && end > furtherOffset) {
634 inStream = new LimitedLengthInputStream(inStream, furtherOffset, end - encStart);
635 } else if (furtherOffset > 0) {
636 inStream.skip(furtherOffset);
637 }
638
639 mAuthenticatedStream = new MacAuthenticatedInputStream(inStream, mac);
640
641 inStream = mAuthenticatedStream;
642 } else {
643 if (encStart >= 0) {
644 if (end > encStart) {
645 inStream = new LimitedLengthInputStream(inStream, encStart, end - encStart);
646 } else {
647 inStream.skip(encStart);
648 }
649 }
650 }
651
652 return new CipherInputStream(inStream, c);
653 }
654
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800655 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800656
Kenny Root62e1b4e2011-03-14 17:13:39 -0700657 private static final int PREFER_INTERNAL = 1;
658 private static final int PREFER_EXTERNAL = 2;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800659
Kenny Root62e1b4e2011-03-14 17:13:39 -0700660 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
661 long threshold) {
662 int prefer;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700663 boolean checkBoth = false;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700664
Kenny Root6dceb882012-04-12 14:23:49 -0700665 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
666
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700667 check_inner : {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700668 /*
669 * Explicit install flags should override the manifest settings.
670 */
Kenny Root6dceb882012-04-12 14:23:49 -0700671 if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700672 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700673 break check_inner;
674 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700675 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700676 break check_inner;
677 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700678
679 /* No install flags. Check for manifest option. */
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700680 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700681 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700682 break check_inner;
683 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700684 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700685 checkBoth = true;
686 break check_inner;
687 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700688 // We default to preferring internal storage.
689 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700690 checkBoth = true;
691 break check_inner;
692 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700693
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700694 // Pick user preference
Jeff Sharkey625239a2012-09-26 22:03:49 -0700695 int installPreference = Settings.Global.getInt(getApplicationContext()
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700696 .getContentResolver(),
Jeff Sharkey625239a2012-09-26 22:03:49 -0700697 Settings.Global.DEFAULT_INSTALL_LOCATION,
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700698 PackageHelper.APP_INSTALL_AUTO);
699 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700700 prefer = PREFER_INTERNAL;
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700701 break check_inner;
702 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700703 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700704 break check_inner;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700705 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700706
707 /*
708 * Fall back to default policy of internal-only if nothing else is
709 * specified.
710 */
711 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700712 }
713
Kenny Root62e1b4e2011-03-14 17:13:39 -0700714 final boolean emulated = Environment.isExternalStorageEmulated();
715
716 final File apkFile = new File(archiveFilePath);
717
718 boolean fitsOnInternal = false;
719 if (checkBoth || prefer == PREFER_INTERNAL) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700720 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700721 fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
722 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700723 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
724 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800725 }
726
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700727 boolean fitsOnSd = false;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700728 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700729 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700730 fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
731 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700732 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
733 }
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700734 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700735
736 if (prefer == PREFER_INTERNAL) {
737 if (fitsOnInternal) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700738 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
739 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700740 } else if (!emulated && prefer == PREFER_EXTERNAL) {
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800741 if (fitsOnSd) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700742 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800743 }
744 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700745
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700746 if (checkBoth) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700747 if (fitsOnInternal) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700748 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700749 } else if (!emulated && fitsOnSd) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700750 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800751 }
752 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700753
754 /*
755 * If they requested to be on the external media by default, return that
756 * the media was unavailable. Otherwise, indicate there was insufficient
757 * storage space available.
758 */
759 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
760 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700761 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700762 } else {
763 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800764 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800765 }
766
Kenny Root1ebd74a2011-08-03 15:09:44 -0700767 /**
768 * Measure a file to see if it fits within the free space threshold.
769 *
770 * @param apkFile file to check
771 * @param threshold byte threshold to compare against
772 * @return true if file fits under threshold
773 * @throws FileNotFoundException when APK does not exist
774 */
Kenny Root6dceb882012-04-12 14:23:49 -0700775 private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)
776 throws IOException {
777 long size = apkFile.length();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700778 if (size == 0 && !apkFile.exists()) {
779 throw new FileNotFoundException();
780 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800781
Kenny Root6dceb882012-04-12 14:23:49 -0700782 if (isForwardLocked) {
783 size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);
784 }
785
Kenny Root62e1b4e2011-03-14 17:13:39 -0700786 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
787 final long availInternalSize = (long) internalStats.getAvailableBlocks()
788 * (long) internalStats.getBlockSize();
789
790 return (availInternalSize - size) > threshold;
791 }
792
793
Kenny Root1ebd74a2011-08-03 15:09:44 -0700794 /**
795 * Measure a file to see if it fits in the external free space.
796 *
797 * @param apkFile file to check
798 * @return true if file fits
799 * @throws IOException when file does not exist
800 */
Kenny Root6dceb882012-04-12 14:23:49 -0700801 private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked)
802 throws IOException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700803 if (Environment.isExternalStorageEmulated()) {
804 return false;
805 }
806
Kenny Root6dceb882012-04-12 14:23:49 -0700807 final int sizeMb = calculateContainerSize(apkFile, isForwardLocked);
Kenny Root62e1b4e2011-03-14 17:13:39 -0700808
809 final int availSdMb;
810 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Kenny Root61942c52011-08-15 12:46:04 -0700811 final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
812 final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
813 availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700814 } else {
815 availSdMb = -1;
816 }
817
818 return availSdMb > sizeMb;
819 }
820
821 /**
822 * Calculate the container size for an APK. Takes into account the
823 *
824 * @param apkFile file from which to calculate size
825 * @return size in megabytes (2^20 bytes)
Kenny Root6dceb882012-04-12 14:23:49 -0700826 * @throws IOException when there is a problem reading the file
Kenny Root62e1b4e2011-03-14 17:13:39 -0700827 */
Kenny Root6dceb882012-04-12 14:23:49 -0700828 private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700829 // Calculate size of container needed to hold base APK.
830 long sizeBytes = apkFile.length();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700831 if (sizeBytes == 0 && !apkFile.exists()) {
832 throw new FileNotFoundException();
833 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700834
835 // Check all the native files that need to be copied and add that to the
836 // container size.
Kenny Root66269ea2011-07-12 14:14:01 -0700837 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile);
Kenny Root62e1b4e2011-03-14 17:13:39 -0700838
Kenny Root6dceb882012-04-12 14:23:49 -0700839 if (forwardLocked) {
840 sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null);
841 }
842
Kenny Root62e1b4e2011-03-14 17:13:39 -0700843 int sizeMb = (int) (sizeBytes >> 20);
844 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
845 sizeMb++;
846 }
847
848 /*
849 * Add buffer size because we don't have a good way to determine the
850 * real FAT size. Your FAT size varies with how many directory entries
851 * you need, how big the whole filesystem is, and other such headaches.
852 */
853 sizeMb++;
854
855 return sizeMb;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800856 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800857}