blob: c709e4012deb63595db5950264a054fad4ec3491 [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;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080025import android.content.pm.IPackageManager;
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;
Kenny Rootf5121a92011-08-10 16:23:32 -070034import android.os.FileUtils;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080035import android.os.IBinder;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080036import android.os.ParcelFileDescriptor;
37import android.os.Process;
38import android.os.RemoteException;
39import android.os.ServiceManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080040import android.os.StatFs;
Kenny Rootf5121a92011-08-10 16:23:32 -070041import android.provider.Settings;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080042import android.util.DisplayMetrics;
Kenny Root1ebd74a2011-08-03 15:09:44 -070043import android.util.Slog;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080044
Kenny Rootf5121a92011-08-10 16:23:32 -070045import java.io.BufferedInputStream;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080046import java.io.File;
47import java.io.FileInputStream;
48import java.io.FileNotFoundException;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080049import java.io.IOException;
50import java.io.InputStream;
Kenny Rootf5121a92011-08-10 16:23:32 -070051import java.io.OutputStream;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080052
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -070053import libcore.io.ErrnoException;
54import libcore.io.Libcore;
55import libcore.io.StructStatFs;
56
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080057/*
58 * This service copies a downloaded apk to a file passed in as
59 * a ParcelFileDescriptor or to a newly created container specified
60 * by parameters. The DownloadManager gives access to this process
61 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
62 * permission to access apks downloaded via the download manager.
63 */
Dianne Hackborne83cefce2010-02-04 17:38:14 -080064public class DefaultContainerService extends IntentService {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080065 private static final String TAG = "DefContainer";
Suchi Amalapurapucf6eaea2010-02-23 19:37:45 -080066 private static final boolean localLOGV = true;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080067
Kenny Root85387d72010-08-26 10:13:11 -070068 private static final String LIB_DIR_NAME = "lib";
69
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080070 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
71 /*
72 * Creates a new container and copies resource there.
73 * @param paackageURI the uri of resource to be copied. Can be either
74 * a content uri or a file uri
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080075 * @param cid the id of the secure container that should
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080076 * be used for creating a secure container into which the resource
77 * will be copied.
78 * @param key Refers to key used for encrypting the secure container
79 * @param resFileName Name of the target resource file(relative to newly
80 * created secure container)
81 * @return Returns the new cache path where the resource has been copied into
82 *
83 */
Kenny Root6dceb882012-04-12 14:23:49 -070084 public String copyResourceToContainer(final Uri packageURI, final String cid,
85 final String key, final String resFileName, final String publicResFileName,
86 boolean isExternal, boolean isForwardLocked) {
Suchi Amalapurapu679bba32010-02-16 11:52:44 -080087 if (packageURI == null || cid == null) {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080088 return null;
89 }
Kenny Root6dceb882012-04-12 14:23:49 -070090
91 return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName,
92 isExternal, isForwardLocked);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080093 }
94
95 /*
96 * Copy specified resource to output stream
Kenny Rootf5121a92011-08-10 16:23:32 -070097 * @param packageURI the uri of resource to be copied. Should be a file
98 * uri
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080099 * @param outStream Remote file descriptor to be used for copying
Kenny Rootf5121a92011-08-10 16:23:32 -0700100 * @return returns status code according to those in {@link
101 * PackageManager}
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800102 */
Kenny Rootf5121a92011-08-10 16:23:32 -0700103 public int copyResource(final Uri packageURI, ParcelFileDescriptor outStream) {
104 if (packageURI == null || outStream == null) {
105 return PackageManager.INSTALL_FAILED_INVALID_URI;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800106 }
Kenny Rootf5121a92011-08-10 16:23:32 -0700107
108 ParcelFileDescriptor.AutoCloseOutputStream autoOut
109 = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
110
111 try {
112 copyFile(packageURI, autoOut);
113 return PackageManager.INSTALL_SUCCEEDED;
114 } catch (FileNotFoundException e) {
115 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " FNF: "
116 + e.getMessage());
117 return PackageManager.INSTALL_FAILED_INVALID_URI;
118 } catch (IOException e) {
119 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: "
120 + e.getMessage());
121 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
122 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800123 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800124
125 /*
126 * Determine the recommended install location for package
127 * specified by file uri location.
128 * @param fileUri the uri of resource to be copied. Should be a
129 * file uri
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800130 * @return Returns PackageInfoLite object containing
131 * the package info and recommended app location.
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800132 */
Kenny Root62e1b4e2011-03-14 17:13:39 -0700133 public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) {
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800134 PackageInfoLite ret = new PackageInfoLite();
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800135 if (fileUri == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700136 Slog.i(TAG, "Invalid package uri " + fileUri);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800137 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
138 return ret;
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800139 }
140 String scheme = fileUri.getScheme();
141 if (scheme != null && !scheme.equals("file")) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700142 Slog.w(TAG, "Falling back to installing on internal storage only");
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800143 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL;
144 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800145 }
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800146 String archiveFilePath = fileUri.getPath();
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800147 DisplayMetrics metrics = new DisplayMetrics();
148 metrics.setToDefaults();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700149
Kenny Root62e1b4e2011-03-14 17:13:39 -0700150 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0);
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800151 if (pkg == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700152 Slog.w(TAG, "Failed to parse package");
153
154 final File apkFile = new File(archiveFilePath);
155 if (!apkFile.exists()) {
156 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
157 } else {
158 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
159 }
160
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800161 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800162 }
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800163 ret.packageName = pkg.packageName;
Kenny Root930d3af2010-07-30 16:52:29 -0700164 ret.installLocation = pkg.installLocation;
Kenny Root05ca4c92011-09-15 10:36:25 -0700165 ret.verifiers = pkg.verifiers;
Kenny Root1ebd74a2011-08-03 15:09:44 -0700166
Kenny Root62e1b4e2011-03-14 17:13:39 -0700167 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
168 archiveFilePath, flags, threshold);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700169
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800170 return ret;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800171 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800172
Kenny Root62e1b4e2011-03-14 17:13:39 -0700173 @Override
Kenny Root6dceb882012-04-12 14:23:49 -0700174 public boolean checkInternalFreeStorage(Uri packageUri, boolean isForwardLocked,
175 long threshold) throws RemoteException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700176 final File apkFile = new File(packageUri.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700177 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700178 return isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
179 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700180 return true;
181 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700182 }
183
184 @Override
Kenny Root6dceb882012-04-12 14:23:49 -0700185 public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked)
186 throws RemoteException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700187 final File apkFile = new File(packageUri.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700188 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700189 return isUnderExternalThreshold(apkFile, isForwardLocked);
190 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700191 return true;
192 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800193 }
Kenny Roota02b8b02010-08-05 16:14:17 -0700194
195 public ObbInfo getObbInfo(String filename) {
Kenny Root05105f72010-09-22 17:29:43 -0700196 try {
197 return ObbScanner.getObbInfo(filename);
198 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700199 Slog.d(TAG, "Couldn't get OBB info for " + filename);
Kenny Root05105f72010-09-22 17:29:43 -0700200 return null;
201 }
Kenny Roota02b8b02010-08-05 16:14:17 -0700202 }
Kenny Rootaa183e22010-12-02 18:00:38 -0800203
204 @Override
Kenny Root366949c2011-01-14 17:18:14 -0800205 public long calculateDirectorySize(String path) throws RemoteException {
206 final File directory = new File(path);
207 if (directory.exists() && directory.isDirectory()) {
208 return MeasurementUtils.measureDirectory(path);
209 } else {
210 return 0L;
211 }
Kenny Rootaa183e22010-12-02 18:00:38 -0800212 }
Jeff Sharkey9cbe986a2012-04-22 18:56:43 -0700213
214 @Override
215 public long[] getFileSystemStats(String path) {
216 try {
217 final StructStatFs stat = Libcore.os.statfs(path);
218 final long totalSize = stat.f_blocks * stat.f_bsize;
219 final long availSize = stat.f_bavail * stat.f_bsize;
220 return new long[] { totalSize, availSize };
221 } catch (ErrnoException e) {
222 throw new IllegalStateException(e);
223 }
224 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800225 };
226
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800227 public DefaultContainerService() {
228 super("DefaultContainerService");
229 setIntentRedelivery(true);
230 }
231
232 @Override
233 protected void onHandleIntent(Intent intent) {
234 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
235 IPackageManager pm = IPackageManager.Stub.asInterface(
236 ServiceManager.getService("package"));
237 String pkg = null;
238 try {
239 while ((pkg=pm.nextPackageToClean(pkg)) != null) {
240 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg));
241 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg));
Kenny Root300c13a2011-01-18 13:04:40 -0800242 eraseFiles(Environment.getExternalStorageAppObbDirectory(pkg));
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800243 }
244 } catch (RemoteException e) {
245 }
246 }
247 }
248
249 void eraseFiles(File path) {
250 if (path.isDirectory()) {
251 String[] files = path.list();
252 if (files != null) {
253 for (String file : files) {
254 eraseFiles(new File(path, file));
255 }
256 }
257 }
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800258 path.delete();
259 }
260
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800261 public IBinder onBind(Intent intent) {
262 return mBinder;
263 }
264
Kenny Root6dceb882012-04-12 14:23:49 -0700265 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName,
266 String publicResFileName, boolean isExternal, boolean isForwardLocked) {
267
268 if (isExternal) {
269 // Make sure the sdcard is mounted.
270 String status = Environment.getExternalStorageState();
271 if (!status.equals(Environment.MEDIA_MOUNTED)) {
272 Slog.w(TAG, "Make sure sdcard is mounted.");
273 return null;
274 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800275 }
Kenny Root85387d72010-08-26 10:13:11 -0700276
277 // The .apk file
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800278 String codePath = packageURI.getPath();
Suchi Amalapurapu679bba32010-02-16 11:52:44 -0800279 File codeFile = new File(codePath);
Kenny Root85387d72010-08-26 10:13:11 -0700280
281 // Calculate size of container needed to hold base APK.
Kenny Root6dceb882012-04-12 14:23:49 -0700282 final int sizeMb;
Kenny Root1ebd74a2011-08-03 15:09:44 -0700283 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700284 sizeMb = calculateContainerSize(codeFile, isForwardLocked);
285 } catch (IOException e) {
286 Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath());
Kenny Root1ebd74a2011-08-03 15:09:44 -0700287 return null;
288 }
Kenny Root85387d72010-08-26 10:13:11 -0700289
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800290 // Create new container
Kenny Root6dceb882012-04-12 14:23:49 -0700291 final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(),
292 isExternal);
293 if (newCachePath == null) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700294 Slog.e(TAG, "Failed to create container " + newCid);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800295 return null;
296 }
Kenny Root85387d72010-08-26 10:13:11 -0700297
Kenny Root1ebd74a2011-08-03 15:09:44 -0700298 if (localLOGV) {
299 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
300 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700301
Kenny Root1ebd74a2011-08-03 15:09:44 -0700302 final File resFile = new File(newCachePath, resFileName);
303 if (FileUtils.copyFile(new File(codePath), resFile)) {
304 if (localLOGV) {
305 Slog.i(TAG, "Copied " + codePath + " to " + resFile);
Kenny Root85387d72010-08-26 10:13:11 -0700306 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700307 } else {
308 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile);
309 // Clean up container
Kenny Root85387d72010-08-26 10:13:11 -0700310 PackageHelper.destroySdDir(newCid);
311 return null;
312 }
313
Kenny Rootbf023582012-05-02 16:56:15 -0700314 try {
315 Libcore.os.chmod(resFile.getAbsolutePath(), 0640);
316 } catch (ErrnoException e) {
317 Slog.e(TAG, "Could not chown APK: " + e.getMessage());
318 PackageHelper.destroySdDir(newCid);
319 return null;
320 }
321
Kenny Root6dceb882012-04-12 14:23:49 -0700322 if (isForwardLocked) {
323 File publicZipFile = new File(newCachePath, publicResFileName);
324 try {
325 PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile);
326 if (localLOGV) {
327 Slog.i(TAG, "Copied resources to " + publicZipFile);
328 }
329 } catch (IOException e) {
330 Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": "
331 + e.getMessage());
332 PackageHelper.destroySdDir(newCid);
333 return null;
334 }
335
336 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700337 Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644);
338 } catch (ErrnoException e) {
Kenny Rootbf023582012-05-02 16:56:15 -0700339 Slog.e(TAG, "Could not chown public resource file: " + e.getMessage());
Kenny Root6dceb882012-04-12 14:23:49 -0700340 PackageHelper.destroySdDir(newCid);
341 return null;
342 }
343 }
344
Kenny Root1ebd74a2011-08-03 15:09:44 -0700345 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
346 if (sharedLibraryDir.mkdir()) {
347 int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
348 if (ret != PackageManager.INSTALL_SUCCEEDED) {
349 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
350 PackageHelper.destroySdDir(newCid);
351 return null;
352 }
353 } else {
354 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath());
355 PackageHelper.destroySdDir(newCid);
356 return null;
357 }
358
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800359 if (!PackageHelper.finalizeSdDir(newCid)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700360 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800361 // Clean up container
362 PackageHelper.destroySdDir(newCid);
Kenny Root1ebd74a2011-08-03 15:09:44 -0700363 return null;
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800364 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700365
366 if (localLOGV) {
367 Slog.i(TAG, "Finalized container " + newCid);
368 }
369
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800370 if (PackageHelper.isContainerMounted(newCid)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700371 if (localLOGV) {
372 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath);
373 }
374
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800375 // Force a gc to avoid being killed.
376 Runtime.getRuntime().gc();
377 PackageHelper.unMountSdDir(newCid);
378 } else {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700379 if (localLOGV) {
380 Slog.i(TAG, "Container " + newCid + " not mounted");
381 }
Suchi Amalapurapua2b6c372010-03-05 17:40:11 -0800382 }
Kenny Root1ebd74a2011-08-03 15:09:44 -0700383
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800384 return newCachePath;
385 }
386
Kenny Rootf5121a92011-08-10 16:23:32 -0700387 private static void copyToFile(InputStream inputStream, OutputStream out) throws IOException {
388 byte[] buffer = new byte[16384];
389 int bytesRead;
390 while ((bytesRead = inputStream.read(buffer)) >= 0) {
391 out.write(buffer, 0, bytesRead);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800392 }
393 }
394
Kenny Rootf5121a92011-08-10 16:23:32 -0700395 private static void copyToFile(File srcFile, OutputStream out)
396 throws FileNotFoundException, IOException {
397 InputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile));
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800398 try {
Kenny Rootf5121a92011-08-10 16:23:32 -0700399 copyToFile(inputStream, out);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800400 } finally {
Kenny Rootf5121a92011-08-10 16:23:32 -0700401 try { inputStream.close(); } catch (IOException e) {}
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800402 }
403 }
404
Kenny Rootf5121a92011-08-10 16:23:32 -0700405 private void copyFile(Uri pPackageURI, OutputStream outStream) throws FileNotFoundException,
406 IOException {
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800407 String scheme = pPackageURI.getScheme();
408 if (scheme == null || scheme.equals("file")) {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800409 final File srcPackageFile = new File(pPackageURI.getPath());
410 // We copy the source package file to a temp file and then rename it to the
411 // destination file in order to eliminate a window where the package directory
412 // scanner notices the new package file but it's not completely copied yet.
Kenny Rootf5121a92011-08-10 16:23:32 -0700413 copyToFile(srcPackageFile, outStream);
Suchi Amalapurapu3602f762010-03-03 17:29:33 -0800414 } else if (scheme.equals("content")) {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800415 ParcelFileDescriptor fd = null;
416 try {
417 fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
418 } catch (FileNotFoundException e) {
Kenny Rootf5121a92011-08-10 16:23:32 -0700419 Slog.e(TAG, "Couldn't open file descriptor from download service. "
420 + "Failed with exception " + e);
421 throw e;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800422 }
Kenny Rootf5121a92011-08-10 16:23:32 -0700423
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800424 if (fd == null) {
Kenny Rootf5121a92011-08-10 16:23:32 -0700425 Slog.e(TAG, "Provider returned no file descriptor for " + pPackageURI.toString());
426 throw new FileNotFoundException("provider returned no file descriptor");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800427 } else {
428 if (localLOGV) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700429 Slog.i(TAG, "Opened file descriptor from download service.");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800430 }
Kenny Rootf5121a92011-08-10 16:23:32 -0700431 ParcelFileDescriptor.AutoCloseInputStream dlStream
432 = new ParcelFileDescriptor.AutoCloseInputStream(fd);
433
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800434 // We copy the source package file to a temp file and then rename it to the
435 // destination file in order to eliminate a window where the package directory
Kenny Root1ebd74a2011-08-03 15:09:44 -0700436 // scanner notices the new package file but it's not completely
Kenny Rootf5121a92011-08-10 16:23:32 -0700437 // copied
438 copyToFile(dlStream, outStream);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800439 }
440 } else {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700441 Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
Kenny Rootf5121a92011-08-10 16:23:32 -0700442 throw new FileNotFoundException("Package URI is not 'file:' or 'content:'");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800443 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800444 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800445
Kenny Root62e1b4e2011-03-14 17:13:39 -0700446 private static final int PREFER_INTERNAL = 1;
447 private static final int PREFER_EXTERNAL = 2;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800448
Kenny Root62e1b4e2011-03-14 17:13:39 -0700449 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
450 long threshold) {
451 int prefer;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700452 boolean checkBoth = false;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700453
Kenny Root6dceb882012-04-12 14:23:49 -0700454 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
455
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700456 check_inner : {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700457 /*
458 * Explicit install flags should override the manifest settings.
459 */
Kenny Root6dceb882012-04-12 14:23:49 -0700460 if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700461 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700462 break check_inner;
463 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700464 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700465 break check_inner;
466 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700467
468 /* No install flags. Check for manifest option. */
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700469 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700470 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700471 break check_inner;
472 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700473 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700474 checkBoth = true;
475 break check_inner;
476 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700477 // We default to preferring internal storage.
478 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700479 checkBoth = true;
480 break check_inner;
481 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700482
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700483 // Pick user preference
484 int installPreference = Settings.System.getInt(getApplicationContext()
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700485 .getContentResolver(),
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700486 Settings.Secure.DEFAULT_INSTALL_LOCATION,
487 PackageHelper.APP_INSTALL_AUTO);
488 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700489 prefer = PREFER_INTERNAL;
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700490 break check_inner;
491 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700492 prefer = PREFER_EXTERNAL;
Suchi Amalapurapu40e47252010-04-07 16:15:50 -0700493 break check_inner;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700494 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700495
496 /*
497 * Fall back to default policy of internal-only if nothing else is
498 * specified.
499 */
500 prefer = PREFER_INTERNAL;
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700501 }
502
Kenny Root62e1b4e2011-03-14 17:13:39 -0700503 final boolean emulated = Environment.isExternalStorageEmulated();
504
505 final File apkFile = new File(archiveFilePath);
506
507 boolean fitsOnInternal = false;
508 if (checkBoth || prefer == PREFER_INTERNAL) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700509 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700510 fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
511 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700512 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
513 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800514 }
515
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700516 boolean fitsOnSd = false;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700517 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700518 try {
Kenny Root6dceb882012-04-12 14:23:49 -0700519 fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
520 } catch (IOException e) {
Kenny Root1ebd74a2011-08-03 15:09:44 -0700521 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
522 }
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700523 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700524
525 if (prefer == PREFER_INTERNAL) {
526 if (fitsOnInternal) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700527 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
528 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700529 } else if (!emulated && prefer == PREFER_EXTERNAL) {
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800530 if (fitsOnSd) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700531 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800532 }
533 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700534
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700535 if (checkBoth) {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700536 if (fitsOnInternal) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700537 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700538 } else if (!emulated && fitsOnSd) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700539 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800540 }
541 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700542
543 /*
544 * If they requested to be on the external media by default, return that
545 * the media was unavailable. Otherwise, indicate there was insufficient
546 * storage space available.
547 */
548 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
549 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Suchi Amalapurapu14b6abd2010-03-17 08:37:04 -0700550 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700551 } else {
552 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800553 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800554 }
555
Kenny Root1ebd74a2011-08-03 15:09:44 -0700556 /**
557 * Measure a file to see if it fits within the free space threshold.
558 *
559 * @param apkFile file to check
560 * @param threshold byte threshold to compare against
561 * @return true if file fits under threshold
562 * @throws FileNotFoundException when APK does not exist
563 */
Kenny Root6dceb882012-04-12 14:23:49 -0700564 private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)
565 throws IOException {
566 long size = apkFile.length();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700567 if (size == 0 && !apkFile.exists()) {
568 throw new FileNotFoundException();
569 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800570
Kenny Root6dceb882012-04-12 14:23:49 -0700571 if (isForwardLocked) {
572 size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);
573 }
574
Kenny Root62e1b4e2011-03-14 17:13:39 -0700575 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
576 final long availInternalSize = (long) internalStats.getAvailableBlocks()
577 * (long) internalStats.getBlockSize();
578
579 return (availInternalSize - size) > threshold;
580 }
581
582
Kenny Root1ebd74a2011-08-03 15:09:44 -0700583 /**
584 * Measure a file to see if it fits in the external free space.
585 *
586 * @param apkFile file to check
587 * @return true if file fits
588 * @throws IOException when file does not exist
589 */
Kenny Root6dceb882012-04-12 14:23:49 -0700590 private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked)
591 throws IOException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700592 if (Environment.isExternalStorageEmulated()) {
593 return false;
594 }
595
Kenny Root6dceb882012-04-12 14:23:49 -0700596 final int sizeMb = calculateContainerSize(apkFile, isForwardLocked);
Kenny Root62e1b4e2011-03-14 17:13:39 -0700597
598 final int availSdMb;
599 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Kenny Root61942c52011-08-15 12:46:04 -0700600 final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
601 final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
602 availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
Kenny Root62e1b4e2011-03-14 17:13:39 -0700603 } else {
604 availSdMb = -1;
605 }
606
607 return availSdMb > sizeMb;
608 }
609
610 /**
611 * Calculate the container size for an APK. Takes into account the
612 *
613 * @param apkFile file from which to calculate size
614 * @return size in megabytes (2^20 bytes)
Kenny Root6dceb882012-04-12 14:23:49 -0700615 * @throws IOException when there is a problem reading the file
Kenny Root62e1b4e2011-03-14 17:13:39 -0700616 */
Kenny Root6dceb882012-04-12 14:23:49 -0700617 private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
Kenny Root62e1b4e2011-03-14 17:13:39 -0700618 // Calculate size of container needed to hold base APK.
619 long sizeBytes = apkFile.length();
Kenny Root1ebd74a2011-08-03 15:09:44 -0700620 if (sizeBytes == 0 && !apkFile.exists()) {
621 throw new FileNotFoundException();
622 }
Kenny Root62e1b4e2011-03-14 17:13:39 -0700623
624 // Check all the native files that need to be copied and add that to the
625 // container size.
Kenny Root66269ea2011-07-12 14:14:01 -0700626 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile);
Kenny Root62e1b4e2011-03-14 17:13:39 -0700627
Kenny Root6dceb882012-04-12 14:23:49 -0700628 if (forwardLocked) {
629 sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null);
630 }
631
Kenny Root62e1b4e2011-03-14 17:13:39 -0700632 int sizeMb = (int) (sizeBytes >> 20);
633 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
634 sizeMb++;
635 }
636
637 /*
638 * Add buffer size because we don't have a good way to determine the
639 * real FAT size. Your FAT size varies with how many directory entries
640 * you need, how big the whole filesystem is, and other such headaches.
641 */
642 sizeMb++;
643
644 return sizeMb;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800645 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800646}