blob: 8e030e504965cc9a5c7c771a375895e29ddb4a94 [file] [log] [blame]
Suchi Amalapurapuc028be42010-01-25 12:19:12 -08001package com.android.defcontainer;
2
3import com.android.internal.app.IMediaContainerService;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -08004import com.android.internal.content.PackageHelper;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -08005import android.content.Intent;
Dianne Hackborne83cefce2010-02-04 17:38:14 -08006import android.content.pm.IPackageManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -08007import android.content.pm.PackageInfo;
Dianne Hackborne83cefce2010-02-04 17:38:14 -08008import android.content.pm.PackageManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -08009import android.content.pm.PackageParser;
10import android.content.pm.PackageParser.Package;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080011import android.net.Uri;
12import android.os.Debug;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080013import android.os.Environment;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080014import android.os.IBinder;
San Mehatb1043402010-02-05 08:26:50 -080015import android.os.storage.IMountService;
16import android.os.storage.StorageResultCode;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080017import android.os.ParcelFileDescriptor;
18import android.os.Process;
19import android.os.RemoteException;
20import android.os.ServiceManager;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080021import android.os.StatFs;
Dianne Hackborne83cefce2010-02-04 17:38:14 -080022import android.app.IntentService;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080023import android.app.Service;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080024import android.util.DisplayMetrics;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080025import android.util.Log;
26
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileNotFoundException;
30import java.io.FileOutputStream;
31import java.io.IOException;
32import java.io.InputStream;
33import java.io.OutputStream;
34
35import android.os.FileUtils;
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080036import android.provider.Settings;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080037
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080038/*
39 * This service copies a downloaded apk to a file passed in as
40 * a ParcelFileDescriptor or to a newly created container specified
41 * by parameters. The DownloadManager gives access to this process
42 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
43 * permission to access apks downloaded via the download manager.
44 */
Dianne Hackborne83cefce2010-02-04 17:38:14 -080045public class DefaultContainerService extends IntentService {
Suchi Amalapurapuc028be42010-01-25 12:19:12 -080046 private static final String TAG = "DefContainer";
47 private static final boolean localLOGV = false;
48
49 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
50 /*
51 * Creates a new container and copies resource there.
52 * @param paackageURI the uri of resource to be copied. Can be either
53 * a content uri or a file uri
54 * @param containerId the id of the secure container that should
55 * be used for creating a secure container into which the resource
56 * will be copied.
57 * @param key Refers to key used for encrypting the secure container
58 * @param resFileName Name of the target resource file(relative to newly
59 * created secure container)
60 * @return Returns the new cache path where the resource has been copied into
61 *
62 */
63 public String copyResourceToContainer(final Uri packageURI,
64 final String containerId,
65 final String key, final String resFileName) {
66 if (packageURI == null || containerId == null) {
67 return null;
68 }
69 return copyResourceInner(packageURI, containerId, key, resFileName);
70 }
71
72 /*
73 * Copy specified resource to output stream
74 * @param packageURI the uri of resource to be copied. Should be a
75 * file uri
76 * @param outStream Remote file descriptor to be used for copying
77 * @return Returns true if copy succeded or false otherwise.
78 */
79 public boolean copyResource(final Uri packageURI,
80 ParcelFileDescriptor outStream) {
81 if (packageURI == null || outStream == null) {
82 return false;
83 }
84 ParcelFileDescriptor.AutoCloseOutputStream
85 autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
86 return copyFile(packageURI, autoOut);
87 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -080088
89 /*
90 * Determine the recommended install location for package
91 * specified by file uri location.
92 * @param fileUri the uri of resource to be copied. Should be a
93 * file uri
94 * @return Returns
95 * PackageHelper.RECOMMEND_INSTALL_INTERNAL to install on internal storage
96 * PackageHelper.RECOMMEND_INSTALL_EXTERNAL to install on external media
97 * PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE for storage errors
98 * PackageHelper.RECOMMEND_FAILED_INVALID_APK for parse errors.
99 */
100 public int getRecommendedInstallLocation(final Uri fileUri) {
101 if (!fileUri.getScheme().equals("file")) {
102 Log.w(TAG, "Falling back to installing on internal storage only");
103 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
104 }
105 final String archiveFilePath = fileUri.getPath();
106 PackageParser packageParser = new PackageParser(archiveFilePath);
107 File sourceFile = new File(archiveFilePath);
108 DisplayMetrics metrics = new DisplayMetrics();
109 metrics.setToDefaults();
110 PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
111 if (pkg == null) {
112 Log.w(TAG, "Failed to parse package");
113 return PackageHelper.RECOMMEND_FAILED_INVALID_APK;
114 }
115 int loc = recommendAppInstallLocation(pkg);
116 if (loc == PackageManager.INSTALL_EXTERNAL) {
117 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
118 } else if (loc == ERR_LOC) {
119 Log.i(TAG, "Failed to install insufficient storage");
120 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
121 } else {
122 // Implies install on internal storage.
123 return 0;
124 }
125 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800126 };
127
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800128 public DefaultContainerService() {
129 super("DefaultContainerService");
130 setIntentRedelivery(true);
131 }
132
133 @Override
134 protected void onHandleIntent(Intent intent) {
135 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
136 IPackageManager pm = IPackageManager.Stub.asInterface(
137 ServiceManager.getService("package"));
138 String pkg = null;
139 try {
140 while ((pkg=pm.nextPackageToClean(pkg)) != null) {
141 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg));
142 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg));
143 }
144 } catch (RemoteException e) {
145 }
146 }
147 }
148
149 void eraseFiles(File path) {
150 if (path.isDirectory()) {
151 String[] files = path.list();
152 if (files != null) {
153 for (String file : files) {
154 eraseFiles(new File(path, file));
155 }
156 }
157 }
Dianne Hackborne83cefce2010-02-04 17:38:14 -0800158 path.delete();
159 }
160
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800161 public IBinder onBind(Intent intent) {
162 return mBinder;
163 }
164
165 private IMountService getMountService() {
166 return IMountService.Stub.asInterface(ServiceManager.getService("mount"));
167 }
168
169 private String copyResourceInner(Uri packageURI, String newCacheId, String key, String resFileName) {
170 // Create new container at newCachePath
171 String codePath = packageURI.getPath();
172 String newCachePath = null;
173 final int CREATE_FAILED = 1;
174 final int COPY_FAILED = 2;
175 final int FINALIZE_FAILED = 3;
176 final int PASS = 4;
177 int errCode = CREATE_FAILED;
178 // Create new container
179 if ((newCachePath = createSdDir(packageURI, newCacheId, key)) != null) {
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800180 if (localLOGV) Log.i(TAG, "Created container for " + newCacheId
181 + " at path : " + newCachePath);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800182 File resFile = new File(newCachePath, resFileName);
183 errCode = COPY_FAILED;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800184 // Copy file from codePath
185 if (FileUtils.copyFile(new File(codePath), resFile)) {
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800186 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800187 errCode = FINALIZE_FAILED;
188 if (finalizeSdDir(newCacheId)) {
189 errCode = PASS;
190 }
191 }
192 }
193 // Print error based on errCode
194 String errMsg = "";
195 switch (errCode) {
196 case CREATE_FAILED:
197 errMsg = "CREATE_FAILED";
198 break;
199 case COPY_FAILED:
200 errMsg = "COPY_FAILED";
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800201 if (localLOGV) Log.i(TAG, "Destroying " + newCacheId +
202 " at path " + newCachePath + " after " + errMsg);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800203 destroySdDir(newCacheId);
204 break;
205 case FINALIZE_FAILED:
206 errMsg = "FINALIZE_FAILED";
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800207 if (localLOGV) Log.i(TAG, "Destroying " + newCacheId +
208 " at path " + newCachePath + " after " + errMsg);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800209 destroySdDir(newCacheId);
210 break;
211 default:
212 errMsg = "PASS";
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800213 if (localLOGV) Log.i(TAG, "Unmounting " + newCacheId +
214 " at path " + newCachePath + " after " + errMsg);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800215 unMountSdDir(newCacheId);
216 break;
217 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800218 if (errCode != PASS) {
219 return null;
220 }
221 return newCachePath;
222 }
223
224 private String createSdDir(final Uri packageURI,
225 String containerId, String sdEncKey) {
226 File tmpPackageFile = new File(packageURI.getPath());
227 // Create mount point via MountService
228 IMountService mountService = getMountService();
229 long len = tmpPackageFile.length();
230 int mbLen = (int) (len/(1024*1024));
231 if ((len - (mbLen * 1024 * 1024)) > 0) {
232 mbLen++;
233 }
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800234 if (localLOGV) Log.i(TAG, "mbLen=" + mbLen);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800235 String cachePath = null;
236 int ownerUid = Process.myUid();
237 try {
San Mehat63d15ee2010-01-29 05:35:53 -0800238 int rc = mountService.createSecureContainer(
239 containerId, mbLen, "vfat", sdEncKey, ownerUid);
240
San Mehatb1043402010-02-05 08:26:50 -0800241 if (rc != StorageResultCode.OperationSucceeded) {
San Mehat63d15ee2010-01-29 05:35:53 -0800242 Log.e(TAG, String.format("Container creation failed (%d)", rc));
243
244 // XXX: This destroy should not be necessary
245 rc = mountService.destroySecureContainer(containerId);
San Mehatb1043402010-02-05 08:26:50 -0800246 if (rc != StorageResultCode.OperationSucceeded) {
San Mehat63d15ee2010-01-29 05:35:53 -0800247 Log.e(TAG, String.format("Container creation-cleanup failed (%d)", rc));
248 return null;
249 }
250
251 // XXX: Does this ever actually succeed?
252 rc = mountService.createSecureContainer(
253 containerId, mbLen, "vfat", sdEncKey, ownerUid);
San Mehatb1043402010-02-05 08:26:50 -0800254 if (rc != StorageResultCode.OperationSucceeded) {
San Mehat63d15ee2010-01-29 05:35:53 -0800255 Log.e(TAG, String.format("Container creation retry failed (%d)", rc));
256 }
257 }
258
259 cachePath = mountService.getSecureContainerPath(containerId);
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800260 if (localLOGV) Log.i(TAG, "Trying to create secure container for "
261 + containerId + ", cachePath =" + cachePath);
262 return cachePath;
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800263 } catch(RemoteException e) {
San Mehat63d15ee2010-01-29 05:35:53 -0800264 Log.e(TAG, "MountService not running?");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800265 return null;
266 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800267 }
268
269 private boolean destroySdDir(String containerId) {
270 try {
271 // We need to destroy right away
272 getMountService().destroySecureContainer(containerId);
273 return true;
274 } catch (IllegalStateException e) {
275 Log.i(TAG, "Failed to destroy container : " + containerId);
276 } catch(RemoteException e) {
San Mehat63d15ee2010-01-29 05:35:53 -0800277 Log.e(TAG, "MountService not running?");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800278 }
279 return false;
280 }
281
282 private boolean finalizeSdDir(String containerId){
283 try {
284 getMountService().finalizeSecureContainer(containerId);
285 return true;
286 } catch (IllegalStateException e) {
287 Log.i(TAG, "Failed to finalize container for pkg : " + containerId);
288 } catch(RemoteException e) {
San Mehat63d15ee2010-01-29 05:35:53 -0800289 Log.e(TAG, "MountService not running?");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800290 }
291 return false;
292 }
293
294 private boolean unMountSdDir(String containerId) {
295 try {
296 getMountService().unmountSecureContainer(containerId);
297 return true;
298 } catch (IllegalStateException e) {
299 Log.e(TAG, "Failed to unmount id: " + containerId + " with exception " + e);
300 } catch(RemoteException e) {
San Mehat63d15ee2010-01-29 05:35:53 -0800301 Log.e(TAG, "MountService not running?");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800302 }
303 return false;
304 }
305
306 private String mountSdDir(String containerId, String key) {
307 try {
San Mehat63d15ee2010-01-29 05:35:53 -0800308 int rc = getMountService().mountSecureContainer(containerId, key, Process.myUid());
San Mehatb1043402010-02-05 08:26:50 -0800309 if (rc == StorageResultCode.OperationSucceeded) {
San Mehat63d15ee2010-01-29 05:35:53 -0800310 return getMountService().getSecureContainerPath(containerId);
311 } else {
312 Log.e(TAG, String.format("Failed to mount id %s with rc %d ", containerId, rc));
313 }
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800314 } catch(RemoteException e) {
San Mehat63d15ee2010-01-29 05:35:53 -0800315 Log.e(TAG, "MountService not running?");
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800316 }
317 return null;
318 }
319
320 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) {
321 try {
322 byte[] buffer = new byte[4096];
323 int bytesRead;
324 while ((bytesRead = inputStream.read(buffer)) >= 0) {
325 out.write(buffer, 0, bytesRead);
326 }
327 return true;
328 } catch (IOException e) {
329 Log.i(TAG, "Exception : " + e + " when copying file");
330 return false;
331 }
332 }
333
334 public static boolean copyToFile(File srcFile, FileOutputStream out) {
335 InputStream inputStream = null;
336 try {
337 inputStream = new FileInputStream(srcFile);
338 return copyToFile(inputStream, out);
339 } catch (IOException e) {
340 return false;
341 } finally {
342 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {}
343 }
344 }
345
346 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) {
347 if (pPackageURI.getScheme().equals("file")) {
348 final File srcPackageFile = new File(pPackageURI.getPath());
349 // We copy the source package file to a temp file and then rename it to the
350 // destination file in order to eliminate a window where the package directory
351 // scanner notices the new package file but it's not completely copied yet.
352 if (!copyToFile(srcPackageFile, outStream)) {
353 Log.e(TAG, "Couldn't copy file: " + srcPackageFile);
354 return false;
355 }
356 } else if (pPackageURI.getScheme().equals("content")) {
357 ParcelFileDescriptor fd = null;
358 try {
359 fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
360 } catch (FileNotFoundException e) {
361 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e);
362 return false;
363 }
364 if (fd == null) {
365 Log.e(TAG, "Couldn't open file descriptor from download service (null).");
366 return false;
367 } else {
368 if (localLOGV) {
369 Log.v(TAG, "Opened file descriptor from download service.");
370 }
371 ParcelFileDescriptor.AutoCloseInputStream
372 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
373 // We copy the source package file to a temp file and then rename it to the
374 // destination file in order to eliminate a window where the package directory
375 // scanner notices the new package file but it's not completely copied yet.
376 if (!copyToFile(dlStream, outStream)) {
377 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file.");
378 return false;
379 }
380 }
381 } else {
382 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
383 return false;
384 }
385 return true;
386 }
Suchi Amalapurapu5b993ce2010-02-12 09:43:29 -0800387
388 // Constants related to app heuristics
389 // No-installation limit for internal flash: 10% or less space available
390 private static final double LOW_NAND_FLASH_TRESHOLD = 0.1;
391
392 // SD-to-internal app size threshold: currently set to 1 MB
393 private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024);
394 private static final int ERR_LOC = -1;
395
396 public int recommendAppInstallLocation(Package pkg) {
397 // Initial implementation:
398 // Package size = code size + cache size + data size
399 // If code size > 1 MB, install on SD card.
400 // Else install on internal NAND flash, unless space on NAND is less than 10%
401
402 if (pkg == null) {
403 return ERR_LOC;
404 }
405
406 StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath());
407 StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
408
409 long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() *
410 (long)internalFlashStats.getBlockSize();
411 long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() *
412 (long)internalFlashStats.getBlockSize();
413 long availSDSize = (long)sdcardStats.getAvailableBlocks() *
414 (long)sdcardStats.getBlockSize();
415
416 double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize;
417
418 final String archiveFilePath = pkg.mScanPath;
419 File apkFile = new File(archiveFilePath);
420 long pkgLen = apkFile.length();
421
422 boolean auto = true;
423 // To make final copy
424 long reqInstallSize = pkgLen;
425 // For dex files
426 long reqInternalSize = 1 * pkgLen;
427 boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
428 boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize);
429 boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk &&
430 (reqInternalSize < availInternalFlashSize);
431 boolean fitsOnInt = intThresholdOk && intAvailOk;
432
433 // Consider application flags preferences as well...
434 boolean installOnlyOnSd = (pkg.installLocation ==
435 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
436 boolean installOnlyInternal = (pkg.installLocation ==
437 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
438 if (installOnlyInternal) {
439 // If set explicitly in manifest,
440 // let that override everything else
441 auto = false;
442 } else if (installOnlyOnSd){
443 // Check if this can be accommodated on the sdcard
444 if (fitsOnSd) {
445 auto = false;
446 }
447 } else {
448 // Check if user option is enabled
449 boolean setInstallLoc = Settings.System.getInt(getApplicationContext()
450 .getContentResolver(),
451 Settings.System.SET_INSTALL_LOCATION, 0) != 0;
452 if (setInstallLoc) {
453 // Pick user preference
454 int installPreference = Settings.System.getInt(getApplicationContext()
455 .getContentResolver(),
456 Settings.System.DEFAULT_INSTALL_LOCATION,
457 PackageInfo.INSTALL_LOCATION_AUTO);
458 if (installPreference == 1) {
459 installOnlyInternal = true;
460 auto = false;
461 } else if (installPreference == 2) {
462 installOnlyOnSd = true;
463 auto = false;
464 }
465 }
466 }
467 if (!auto) {
468 if (installOnlyOnSd) {
469 return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC;
470 } else if (installOnlyInternal){
471 // Check on internal flash
472 return fitsOnInt ? 0 : ERR_LOC;
473 }
474 }
475 // Try to install internally
476 if (fitsOnInt) {
477 return 0;
478 }
479 // Try the sdcard now.
480 if (fitsOnSd) {
481 return PackageManager.INSTALL_EXTERNAL;
482 }
483 // Return error code
484 return ERR_LOC;
485 }
486
Suchi Amalapurapuc028be42010-01-25 12:19:12 -0800487}