blob: 731a09cfdcb74adbef862832904ba5d3e5469156 [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
19import com.android.internal.app.IMediaContainerService;
Kenny Root85387d72010-08-26 10:13:11 -070020import com.android.internal.content.NativeLibraryHelper;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080021import com.android.internal.content.PackageHelper;
Kenny Root85387d72010-08-26 10:13:11 -070022
Kenny Rootf5121a92011-08-10 16:23:32 -070023import android.app.IntentService;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080024import android.content.Intent;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070025import android.content.pm.MacAuthenticatedInputStream;
26import android.content.pm.ContainerEncryptionParams;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080027import android.content.pm.IPackageManager;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070028import android.content.pm.LimitedLengthInputStream;
Dianne Hackborn7767eac2012-08-23 18:25:40 -070029import android.content.pm.PackageCleanItem;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080030import android.content.pm.PackageInfo;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -080031import android.content.pm.PackageInfoLite;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080032import android.content.pm.PackageManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080033import android.content.pm.PackageParser;
Kenny Roota02b8b02010-08-05 16:14:17 -070034import android.content.res.ObbInfo;
35import android.content.res.ObbScanner;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080036import android.net.Uri;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080037import android.os.Environment;
Jeff Sharkey752cd922012-09-23 16:25:12 -070038import android.os.Environment.UserEnvironment;
Kenny Rootf5121a92011-08-10 16:23:32 -070039import android.os.FileUtils;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080040import android.os.IBinder;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080041import android.os.ParcelFileDescriptor;
42import android.os.Process;
43import android.os.RemoteException;
44import android.os.ServiceManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080045import android.os.StatFs;
Kenny Rootf5121a92011-08-10 16:23:32 -070046import android.provider.Settings;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080047import android.util.DisplayMetrics;
Kenny Root1ebd74a2011-08-03 15:09:44 -070048import android.util.Slog;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080049
Kenny Rootf5121a92011-08-10 16:23:32 -070050import java.io.BufferedInputStream;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080051import java.io.File;
52import java.io.FileInputStream;
53import java.io.FileNotFoundException;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080054import java.io.IOException;
55import java.io.InputStream;
Kenny Rootf5121a92011-08-10 16:23:32 -070056import java.io.OutputStream;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070057import java.security.DigestException;
58import java.security.GeneralSecurityException;
59import java.security.InvalidAlgorithmParameterException;
60import java.security.InvalidKeyException;
61import java.security.NoSuchAlgorithmException;
62
63import javax.crypto.Cipher;
64import javax.crypto.CipherInputStream;
65import javax.crypto.Mac;
66import javax.crypto.NoSuchPaddingException;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080067
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070068import libcore.io.ErrnoException;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070069import libcore.io.IoUtils;
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070070import libcore.io.Libcore;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070071import libcore.io.Streams;
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070072import libcore.io.StructStatFs;
73
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080074/*
75 * This service copies a downloaded apk to a file passed in as
76 * a ParcelFileDescriptor or to a newly created container specified
77 * by parameters. The DownloadManager gives access to this process
78 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
79 * permission to access apks downloaded via the download manager.
80 */
Dianne Hackborne83cefce2010-02-04 17:38:14 -080081public class DefaultContainerService extends IntentService {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080082 private static final String TAG = "DefContainer";
Dianne Hackborn40e9f292012-11-27 19:12:23 -080083 private static final boolean localLOGV = false;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080084
Kenny Root85387d72010-08-26 10:13:11 -070085 private static final String LIB_DIR_NAME = "lib";
86
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080087 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -070088 /**
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080089 * Creates a new container and copies resource there.
90 * @param paackageURI the uri of resource to be copied. Can be either
91 * a content uri or a file uri
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080092 * @param cid the id of the secure container that should
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080093 * be used for creating a secure container into which the resource
94 * will be copied.
95 * @param key Refers to key used for encrypting the secure container
96 * @param resFileName Name of the target resource file(relative to newly
97 * created secure container)
98 * @return Returns the new cache path where the resource has been copied into
99 *
100 */
Kenny Root6dceb882012-04-12 14:23:49 -0700101 public String copyResourceToContainer(final Uri packageURI, final String cid,
102 final String key, final String resFileName, final String publicResFileName,
103 boolean isExternal, boolean isForwardLocked) {
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800104 if (packageURI == null || cid == null) {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800105 return null;
106 }
Kenny Root6dceb882012-04-12 14:23:49 -0700107
108 return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName,
109 isExternal, isForwardLocked);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800110 }
111
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700112 /**
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800113 * Copy specified resource to output stream
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700114 *
Kenny Rootf5121a92011-08-10 16:23:32 -0700115 * @param packageURI the uri of resource to be copied. Should be a file
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700116 * uri
117 * @param encryptionParams parameters describing the encryption used for
118 * this file
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800119 * @param outStream Remote file descriptor to be used for copying
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700120 * @return returns status code according to those in
121 * {@link PackageManager}
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800122 */
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700123 public int copyResource(final Uri packageURI, ContainerEncryptionParams encryptionParams,
124 ParcelFileDescriptor outStream) {
Kenny Rootf5121a92011-08-10 16:23:32 -0700125 if (packageURI == null || outStream == null) {
126 return PackageManager.INSTALL_FAILED_INVALID_URI;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800127 }
Kenny Rootf5121a92011-08-10 16:23:32 -0700128
129 ParcelFileDescriptor.AutoCloseOutputStream autoOut
130 = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
131
132 try {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700133 copyFile(packageURI, autoOut, encryptionParams);
Kenny Rootf5121a92011-08-10 16:23:32 -0700134 return PackageManager.INSTALL_SUCCEEDED;
135 } catch (FileNotFoundException e) {
136 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " FNF: "
137 + e.getMessage());
138 return PackageManager.INSTALL_FAILED_INVALID_URI;
139 } catch (IOException e) {
140 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: "
141 + e.getMessage());
142 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700143 } catch (DigestException e) {
144 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " Security: "
145 + e.getMessage());
146 return PackageManager.INSTALL_FAILED_INVALID_APK;
Kenny Rootf5121a92011-08-10 16:23:32 -0700147 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800148 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800149
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700150 /**
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800151 * Determine the recommended install location for package
152 * specified by file uri location.
153 * @param fileUri the uri of resource to be copied. Should be a
154 * file uri
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800155 * @return Returns PackageInfoLite object containing
156 * the package info and recommended app location.
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800157 */
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700158 public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
159 long threshold) {
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800160 PackageInfoLite ret = new PackageInfoLite();
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700161
162 if (packagePath == null) {
163 Slog.i(TAG, "Invalid package file " + packagePath);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800164 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
165 return ret;
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800166 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700167
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800168 DisplayMetrics metrics = new DisplayMetrics();
169 metrics.setToDefaults();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700170
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700171 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800172 if (pkg == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700173 Slog.w(TAG, "Failed to parse package");
174
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700175 final File apkFile = new File(packagePath);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700176 if (!apkFile.exists()) {
177 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
178 } else {
179 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
180 }
181
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800182 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800183 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700184
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800185 ret.packageName = pkg.packageName;
Dianne Hackborn7767eac2012-08-23 18:25:40 -0700186 ret.versionCode = pkg.versionCode;
Kenny Root930d3af2010-07-30 16:52:29 -0700187 ret.installLocation = pkg.installLocation;
Kenny Root05ca4c92011-09-15 10:36:25 -0700188 ret.verifiers = pkg.verifiers;
Kenny Root1ebd74a2011-08-03 15:09:44 -0700189
Kenny Root62e1b4e2011-03-14 17:13:39 -0700190 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700191 packagePath, flags, threshold);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700192
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800193 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800194 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800195
Kenny Root62e1b4e2011-03-14 17:13:39 -0700196 @Override
Kenny Root6dceb882012-04-12 14:23:49 -0700197 public boolean checkInternalFreeStorage(Uri packageUri, boolean isForwardLocked,
198 long threshold) throws RemoteException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700199 final File apkFile = new File(packageUri.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700200 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700201 return isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
202 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700203 return true;
204 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700205 }
206
207 @Override
Kenny Root6dceb882012-04-12 14:23:49 -0700208 public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked)
209 throws RemoteException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700210 final File apkFile = new File(packageUri.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700211 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700212 return isUnderExternalThreshold(apkFile, isForwardLocked);
213 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700214 return true;
215 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800216 }
Kenny Roota02b8b02010-08-05 16:14:17 -0700217
218 public ObbInfo getObbInfo(String filename) {
Kenny Root05105f72010-09-22 17:29:43 -0700219 try {
220 return ObbScanner.getObbInfo(filename);
221 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700222 Slog.d(TAG, "Couldn't get OBB info for " + filename);
Kenny Root05105f72010-09-22 17:29:43 -0700223 return null;
224 }
Kenny Roota02b8b02010-08-05 16:14:17 -0700225 }
Kenny Rootaa183e22010-12-02 18:00:38 -0800226
227 @Override
Kenny Root366949c2011-01-14 17:18:14 -0800228 public long calculateDirectorySize(String path) throws RemoteException {
Kenny Roota69b7eb2012-05-14 14:47:06 -0700229 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
230
Kenny Root366949c2011-01-14 17:18:14 -0800231 final File directory = new File(path);
232 if (directory.exists() && directory.isDirectory()) {
233 return MeasurementUtils.measureDirectory(path);
234 } else {
235 return 0L;
236 }
Kenny Rootaa183e22010-12-02 18:00:38 -0800237 }
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -0700238
239 @Override
240 public long[] getFileSystemStats(String path) {
Kenny Roota69b7eb2012-05-14 14:47:06 -0700241 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
242
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -0700243 try {
244 final StructStatFs stat = Libcore.os.statfs(path);
245 final long totalSize = stat.f_blocks * stat.f_bsize;
246 final long availSize = stat.f_bavail * stat.f_bsize;
247 return new long[] { totalSize, availSize };
248 } catch (ErrnoException e) {
249 throw new IllegalStateException(e);
250 }
251 }
Dianne Hackborn183ce022012-06-29 15:00:21 -0700252
253 @Override
254 public void clearDirectory(String path) throws RemoteException {
255 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
256
257 final File directory = new File(path);
258 if (directory.exists() && directory.isDirectory()) {
259 eraseFiles(directory);
260 }
261 }
Kenny Rootcea37432012-10-18 14:57:33 -0700262
263 @Override
264 public long calculateInstalledSize(String packagePath, boolean isForwardLocked)
265 throws RemoteException {
266 final File packageFile = new File(packagePath);
267 try {
268 return calculateContainerSize(packageFile, isForwardLocked) * 1024 * 1024;
269 } catch (IOException e) {
270 /*
271 * Okay, something failed, so let's just estimate it to be 2x
272 * the file size. Note this will be 0 if the file doesn't exist.
273 */
274 return packageFile.length() * 2;
275 }
276 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800277 };
278
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800279 public DefaultContainerService() {
280 super("DefaultContainerService");
281 setIntentRedelivery(true);
282 }
283
284 @Override
285 protected void onHandleIntent(Intent intent) {
286 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
Jeff Sharkey752cd922012-09-23 16:25:12 -0700287 final IPackageManager pm = IPackageManager.Stub.asInterface(
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800288 ServiceManager.getService("package"));
Jeff Sharkey752cd922012-09-23 16:25:12 -0700289 PackageCleanItem item = null;
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800290 try {
Jeff Sharkey752cd922012-09-23 16:25:12 -0700291 while ((item = pm.nextPackageToClean(item)) != null) {
292 final UserEnvironment userEnv = new UserEnvironment(item.userId);
293 eraseFiles(userEnv.getExternalStorageAppDataDirectory(item.packageName));
294 eraseFiles(userEnv.getExternalStorageAppMediaDirectory(item.packageName));
295 if (item.andCode) {
296 eraseFiles(userEnv.getExternalStorageAppObbDirectory(item.packageName));
Dianne Hackborn7767eac2012-08-23 18:25:40 -0700297 }
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800298 }
299 } catch (RemoteException e) {
300 }
301 }
302 }
303
304 void eraseFiles(File path) {
305 if (path.isDirectory()) {
306 String[] files = path.list();
307 if (files != null) {
308 for (String file : files) {
309 eraseFiles(new File(path, file));
310 }
311 }
312 }
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800313 path.delete();
314 }
315
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800316 public IBinder onBind(Intent intent) {
317 return mBinder;
318 }
319
Kenny Root6dceb882012-04-12 14:23:49 -0700320 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName,
321 String publicResFileName, boolean isExternal, boolean isForwardLocked) {
322
323 if (isExternal) {
324 // Make sure the sdcard is mounted.
325 String status = Environment.getExternalStorageState();
326 if (!status.equals(Environment.MEDIA_MOUNTED)) {
327 Slog.w(TAG, "Make sure sdcard is mounted.");
328 return null;
329 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800330 }
Kenny Root85387d72010-08-26 10:13:11 -0700331
332 // The .apk file
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800333 String codePath = packageURI.getPath();
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800334 File codeFile = new File(codePath);
Kenny Root85387d72010-08-26 10:13:11 -0700335
336 // Calculate size of container needed to hold base APK.
Kenny Root6dceb882012-04-12 14:23:49 -0700337 final int sizeMb;
Kenny Root1ebd74a2011-08-03 15:09:44 -0700338 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700339 sizeMb = calculateContainerSize(codeFile, isForwardLocked);
340 } catch (IOException e) {
341 Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700342 return null;
343 }
Kenny Root85387d72010-08-26 10:13:11 -0700344
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800345 // Create new container
Kenny Root6dceb882012-04-12 14:23:49 -0700346 final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(),
347 isExternal);
348 if (newCachePath == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700349 Slog.e(TAG, "Failed to create container " + newCid);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800350 return null;
351 }
Kenny Root85387d72010-08-26 10:13:11 -0700352
Kenny Root1ebd74a2011-08-03 15:09:44 -0700353 if (localLOGV) {
354 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
355 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700356
Kenny Root1ebd74a2011-08-03 15:09:44 -0700357 final File resFile = new File(newCachePath, resFileName);
358 if (FileUtils.copyFile(new File(codePath), resFile)) {
359 if (localLOGV) {
360 Slog.i(TAG, "Copied " + codePath + " to " + resFile);
Kenny Root85387d72010-08-26 10:13:11 -0700361 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700362 } else {
363 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile);
364 // Clean up container
Kenny Root85387d72010-08-26 10:13:11 -0700365 PackageHelper.destroySdDir(newCid);
366 return null;
367 }
368
Kenny Rootbf023582012-05-02 16:56:15 -0700369 try {
370 Libcore.os.chmod(resFile.getAbsolutePath(), 0640);
371 } catch (ErrnoException e) {
372 Slog.e(TAG, "Could not chown APK: " + e.getMessage());
373 PackageHelper.destroySdDir(newCid);
374 return null;
375 }
376
Kenny Root6dceb882012-04-12 14:23:49 -0700377 if (isForwardLocked) {
378 File publicZipFile = new File(newCachePath, publicResFileName);
379 try {
380 PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile);
381 if (localLOGV) {
382 Slog.i(TAG, "Copied resources to " + publicZipFile);
383 }
384 } catch (IOException e) {
385 Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": "
386 + e.getMessage());
387 PackageHelper.destroySdDir(newCid);
388 return null;
389 }
390
391 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700392 Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644);
393 } catch (ErrnoException e) {
Kenny Rootbf023582012-05-02 16:56:15 -0700394 Slog.e(TAG, "Could not chown public resource file: " + e.getMessage());
Kenny Root6dceb882012-04-12 14:23:49 -0700395 PackageHelper.destroySdDir(newCid);
396 return null;
397 }
398 }
399
Kenny Root1ebd74a2011-08-03 15:09:44 -0700400 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
401 if (sharedLibraryDir.mkdir()) {
402 int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
403 if (ret != PackageManager.INSTALL_SUCCEEDED) {
404 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
405 PackageHelper.destroySdDir(newCid);
406 return null;
407 }
408 } else {
409 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath());
410 PackageHelper.destroySdDir(newCid);
411 return null;
412 }
413
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800414 if (!PackageHelper.finalizeSdDir(newCid)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700415 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800416 // Clean up container
417 PackageHelper.destroySdDir(newCid);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700418 return null;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800419 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700420
421 if (localLOGV) {
422 Slog.i(TAG, "Finalized container " + newCid);
423 }
424
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800425 if (PackageHelper.isContainerMounted(newCid)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700426 if (localLOGV) {
427 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath);
428 }
429
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800430 // Force a gc to avoid being killed.
431 Runtime.getRuntime().gc();
432 PackageHelper.unMountSdDir(newCid);
433 } else {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700434 if (localLOGV) {
435 Slog.i(TAG, "Container " + newCid + " not mounted");
436 }
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800437 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700438
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800439 return newCachePath;
440 }
441
Kenny Rootf5121a92011-08-10 16:23:32 -0700442 private static void copyToFile(InputStream inputStream, OutputStream out) throws IOException {
443 byte[] buffer = new byte[16384];
444 int bytesRead;
445 while ((bytesRead = inputStream.read(buffer)) >= 0) {
446 out.write(buffer, 0, bytesRead);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800447 }
448 }
449
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700450 private void copyFile(Uri pPackageURI, OutputStream outStream,
451 ContainerEncryptionParams encryptionParams) throws FileNotFoundException, IOException,
452 DigestException {
453 String scheme = pPackageURI.getScheme();
454 InputStream inStream = null;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800455 try {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700456 if (scheme == null || scheme.equals("file")) {
457 final InputStream is = new FileInputStream(new File(pPackageURI.getPath()));
458 inStream = new BufferedInputStream(is);
459 } else if (scheme.equals("content")) {
460 final ParcelFileDescriptor fd;
461 try {
462 fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
463 } catch (FileNotFoundException e) {
464 Slog.e(TAG, "Couldn't open file descriptor from download service. "
465 + "Failed with exception " + e);
466 throw e;
467 }
468
469 if (fd == null) {
470 Slog.e(TAG, "Provider returned no file descriptor for " +
471 pPackageURI.toString());
472 throw new FileNotFoundException("provider returned no file descriptor");
473 } else {
474 if (localLOGV) {
475 Slog.i(TAG, "Opened file descriptor from download service.");
476 }
477 inStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
478 }
479 } else {
480 Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
481 throw new FileNotFoundException("Package URI is not 'file:' or 'content:'");
482 }
483
484 /*
485 * If this resource is encrypted, get the decrypted stream version
486 * of it.
487 */
488 ApkContainer container = new ApkContainer(inStream, encryptionParams);
489
490 try {
491 /*
492 * We copy the source package file to a temp file and then
493 * rename it to the destination file in order to eliminate a
494 * window where the package directory scanner notices the new
495 * package file but it's not completely copied yet.
496 */
497 copyToFile(container.getInputStream(), outStream);
498
499 if (!container.isAuthenticated()) {
500 throw new DigestException();
501 }
502 } catch (GeneralSecurityException e) {
503 throw new DigestException("A problem occured copying the file.");
504 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800505 } finally {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700506 IoUtils.closeQuietly(inStream);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800507 }
508 }
509
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700510 private static class ApkContainer {
Kenny Root103d5302012-05-10 10:21:06 -0700511 private static final int MAX_AUTHENTICATED_DATA_SIZE = 16384;
512
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700513 private final InputStream mInStream;
Kenny Rootf5121a92011-08-10 16:23:32 -0700514
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700515 private MacAuthenticatedInputStream mAuthenticatedStream;
516
517 private byte[] mTag;
518
519 public ApkContainer(InputStream inStream, ContainerEncryptionParams encryptionParams)
520 throws IOException {
521 if (encryptionParams == null) {
522 mInStream = inStream;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800523 } else {
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700524 mInStream = getDecryptedStream(inStream, encryptionParams);
525 mTag = encryptionParams.getMacTag();
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800526 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800527 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700528
529 public boolean isAuthenticated() {
530 if (mAuthenticatedStream == null) {
531 return true;
532 }
533
534 return mAuthenticatedStream.isTagEqual(mTag);
535 }
536
537 private Mac getMacInstance(ContainerEncryptionParams encryptionParams) throws IOException {
538 final Mac m;
539 try {
540 final String macAlgo = encryptionParams.getMacAlgorithm();
541
542 if (macAlgo != null) {
543 m = Mac.getInstance(macAlgo);
544 m.init(encryptionParams.getMacKey(), encryptionParams.getMacSpec());
545 } else {
546 m = null;
547 }
548
549 return m;
550 } catch (NoSuchAlgorithmException e) {
551 throw new IOException(e);
552 } catch (InvalidKeyException e) {
553 throw new IOException(e);
554 } catch (InvalidAlgorithmParameterException e) {
555 throw new IOException(e);
556 }
557 }
558
559 public InputStream getInputStream() {
560 return mInStream;
561 }
562
563 private InputStream getDecryptedStream(InputStream inStream,
564 ContainerEncryptionParams encryptionParams) throws IOException {
565 final Cipher c;
566 try {
567 c = Cipher.getInstance(encryptionParams.getEncryptionAlgorithm());
568 c.init(Cipher.DECRYPT_MODE, encryptionParams.getEncryptionKey(),
569 encryptionParams.getEncryptionSpec());
570 } catch (NoSuchAlgorithmException e) {
571 throw new IOException(e);
572 } catch (NoSuchPaddingException e) {
573 throw new IOException(e);
574 } catch (InvalidKeyException e) {
575 throw new IOException(e);
576 } catch (InvalidAlgorithmParameterException e) {
577 throw new IOException(e);
578 }
579
Kenny Root103d5302012-05-10 10:21:06 -0700580 final long encStart = encryptionParams.getEncryptedDataStart();
581 final long end = encryptionParams.getDataEnd();
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700582 if (end < encStart) {
583 throw new IOException("end <= encStart");
584 }
585
586 final Mac mac = getMacInstance(encryptionParams);
587 if (mac != null) {
Kenny Root103d5302012-05-10 10:21:06 -0700588 final long macStart = encryptionParams.getAuthenticatedDataStart();
589 if (macStart >= Integer.MAX_VALUE) {
590 throw new IOException("macStart >= Integer.MAX_VALUE");
591 }
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700592
Kenny Root103d5302012-05-10 10:21:06 -0700593 final long furtherOffset;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700594 if (macStart >= 0 && encStart >= 0 && macStart < encStart) {
595 /*
596 * If there is authenticated data at the beginning, read
597 * that into our MAC first.
598 */
Kenny Root103d5302012-05-10 10:21:06 -0700599 final long authenticatedLengthLong = encStart - macStart;
600 if (authenticatedLengthLong > MAX_AUTHENTICATED_DATA_SIZE) {
601 throw new IOException("authenticated data is too long");
602 }
603 final int authenticatedLength = (int) authenticatedLengthLong;
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700604
Kenny Root103d5302012-05-10 10:21:06 -0700605 final byte[] authenticatedData = new byte[(int) authenticatedLength];
606
607 Streams.readFully(inStream, authenticatedData, (int) macStart,
608 authenticatedLength);
Anonymous Cowardceb1b0b2012-04-24 10:35:16 -0700609 mac.update(authenticatedData, 0, authenticatedLength);
610
611 furtherOffset = 0;
612 } else {
613 /*
614 * No authenticated data at the beginning. Just skip the
615 * required number of bytes to the beginning of the stream.
616 */
617 if (encStart > 0) {
618 furtherOffset = encStart;
619 } else {
620 furtherOffset = 0;
621 }
622 }
623
624 /*
625 * If there is data at the end of the stream we want to ignore,
626 * wrap this in a LimitedLengthInputStream.
627 */
628 if (furtherOffset >= 0 && end > furtherOffset) {
629 inStream = new LimitedLengthInputStream(inStream, furtherOffset, end - encStart);
630 } else if (furtherOffset > 0) {
631 inStream.skip(furtherOffset);
632 }
633
634 mAuthenticatedStream = new MacAuthenticatedInputStream(inStream, mac);
635
636 inStream = mAuthenticatedStream;
637 } else {
638 if (encStart >= 0) {
639 if (end > encStart) {
640 inStream = new LimitedLengthInputStream(inStream, encStart, end - encStart);
641 } else {
642 inStream.skip(encStart);
643 }
644 }
645 }
646
647 return new CipherInputStream(inStream, c);
648 }
649
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800650 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800651
Kenny Root62e1b4e2011-03-14 17:13:39 -0700652 private static final int PREFER_INTERNAL = 1;
653 private static final int PREFER_EXTERNAL = 2;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800654
Kenny Root62e1b4e2011-03-14 17:13:39 -0700655 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
656 long threshold) {
657 int prefer;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700658 boolean checkBoth = false;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700659
Kenny Root6dceb882012-04-12 14:23:49 -0700660 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
661
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700662 check_inner : {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700663 /*
664 * Explicit install flags should override the manifest settings.
665 */
Kenny Root6dceb882012-04-12 14:23:49 -0700666 if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700667 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700668 break check_inner;
669 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700670 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700671 break check_inner;
672 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700673
674 /* No install flags. Check for manifest option. */
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700675 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700676 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700677 break check_inner;
678 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700679 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700680 checkBoth = true;
681 break check_inner;
682 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700683 // We default to preferring internal storage.
684 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700685 checkBoth = true;
686 break check_inner;
687 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700688
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700689 // Pick user preference
Jeff Sharkey625239a2012-09-26 22:03:49 -0700690 int installPreference = Settings.Global.getInt(getApplicationContext()
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700691 .getContentResolver(),
Jeff Sharkey625239a2012-09-26 22:03:49 -0700692 Settings.Global.DEFAULT_INSTALL_LOCATION,
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700693 PackageHelper.APP_INSTALL_AUTO);
694 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700695 prefer = PREFER_INTERNAL;
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700696 break check_inner;
697 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700698 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700699 break check_inner;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700700 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700701
702 /*
703 * Fall back to default policy of internal-only if nothing else is
704 * specified.
705 */
706 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700707 }
708
Kenny Root62e1b4e2011-03-14 17:13:39 -0700709 final boolean emulated = Environment.isExternalStorageEmulated();
710
711 final File apkFile = new File(archiveFilePath);
712
713 boolean fitsOnInternal = false;
714 if (checkBoth || prefer == PREFER_INTERNAL) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700715 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700716 fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
717 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700718 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
719 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800720 }
721
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700722 boolean fitsOnSd = false;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700723 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700724 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700725 fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
726 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700727 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
728 }
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700729 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700730
731 if (prefer == PREFER_INTERNAL) {
732 if (fitsOnInternal) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700733 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
734 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700735 } else if (!emulated && prefer == PREFER_EXTERNAL) {
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800736 if (fitsOnSd) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700737 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800738 }
739 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700740
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700741 if (checkBoth) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700742 if (fitsOnInternal) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700743 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700744 } else if (!emulated && fitsOnSd) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700745 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800746 }
747 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700748
749 /*
750 * If they requested to be on the external media by default, return that
751 * the media was unavailable. Otherwise, indicate there was insufficient
752 * storage space available.
753 */
754 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
755 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700756 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700757 } else {
758 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800759 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800760 }
761
Kenny Root1ebd74a2011-08-03 15:09:44 -0700762 /**
763 * Measure a file to see if it fits within the free space threshold.
764 *
765 * @param apkFile file to check
766 * @param threshold byte threshold to compare against
767 * @return true if file fits under threshold
768 * @throws FileNotFoundException when APK does not exist
769 */
Kenny Root6dceb882012-04-12 14:23:49 -0700770 private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)
771 throws IOException {
772 long size = apkFile.length();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700773 if (size == 0 && !apkFile.exists()) {
774 throw new FileNotFoundException();
775 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800776
Kenny Root6dceb882012-04-12 14:23:49 -0700777 if (isForwardLocked) {
778 size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);
779 }
780
Kenny Root62e1b4e2011-03-14 17:13:39 -0700781 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
782 final long availInternalSize = (long) internalStats.getAvailableBlocks()
783 * (long) internalStats.getBlockSize();
784
785 return (availInternalSize - size) > threshold;
786 }
787
788
Kenny Root1ebd74a2011-08-03 15:09:44 -0700789 /**
790 * Measure a file to see if it fits in the external free space.
791 *
792 * @param apkFile file to check
793 * @return true if file fits
794 * @throws IOException when file does not exist
795 */
Kenny Root6dceb882012-04-12 14:23:49 -0700796 private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked)
797 throws IOException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700798 if (Environment.isExternalStorageEmulated()) {
799 return false;
800 }
801
Kenny Root6dceb882012-04-12 14:23:49 -0700802 final int sizeMb = calculateContainerSize(apkFile, isForwardLocked);
Kenny Root62e1b4e2011-03-14 17:13:39 -0700803
804 final int availSdMb;
805 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Kenny Root61942c52011-08-15 12:46:04 -0700806 final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
807 final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
808 availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700809 } else {
810 availSdMb = -1;
811 }
812
813 return availSdMb > sizeMb;
814 }
815
816 /**
817 * Calculate the container size for an APK. Takes into account the
818 *
819 * @param apkFile file from which to calculate size
820 * @return size in megabytes (2^20 bytes)
Kenny Root6dceb882012-04-12 14:23:49 -0700821 * @throws IOException when there is a problem reading the file
Kenny Root62e1b4e2011-03-14 17:13:39 -0700822 */
Kenny Root6dceb882012-04-12 14:23:49 -0700823 private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700824 // Calculate size of container needed to hold base APK.
825 long sizeBytes = apkFile.length();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700826 if (sizeBytes == 0 && !apkFile.exists()) {
827 throw new FileNotFoundException();
828 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700829
830 // Check all the native files that need to be copied and add that to the
831 // container size.
Kenny Root66269ea2011-07-12 14:14:01 -0700832 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile);
Kenny Root62e1b4e2011-03-14 17:13:39 -0700833
Kenny Root6dceb882012-04-12 14:23:49 -0700834 if (forwardLocked) {
835 sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null);
836 }
837
Kenny Root62e1b4e2011-03-14 17:13:39 -0700838 int sizeMb = (int) (sizeBytes >> 20);
839 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
840 sizeMb++;
841 }
842
843 /*
844 * Add buffer size because we don't have a good way to determine the
845 * real FAT size. Your FAT size varies with how many directory entries
846 * you need, how big the whole filesystem is, and other such headaches.
847 */
848 sizeMb++;
849
850 return sizeMb;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800851 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800852}