• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.defcontainer;
18 
19 import com.android.internal.app.IMediaContainerService;
20 import com.android.internal.content.NativeLibraryHelper;
21 import com.android.internal.content.PackageHelper;
22 
23 import android.content.Intent;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageInfoLite;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageParser;
29 import android.content.res.ObbInfo;
30 import android.content.res.ObbScanner;
31 import android.net.Uri;
32 import android.os.Environment;
33 import android.os.IBinder;
34 import android.os.ParcelFileDescriptor;
35 import android.os.Process;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.StatFs;
39 import android.app.IntentService;
40 import android.util.DisplayMetrics;
41 import android.util.Log;
42 import android.util.Pair;
43 
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.zip.ZipEntry;
53 import java.util.zip.ZipException;
54 import java.util.zip.ZipFile;
55 
56 import android.os.FileUtils;
57 import android.provider.Settings;
58 
59 /*
60  * This service copies a downloaded apk to a file passed in as
61  * a ParcelFileDescriptor or to a newly created container specified
62  * by parameters. The DownloadManager gives access to this process
63  * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER
64  * permission to access apks downloaded via the download manager.
65  */
66 public class DefaultContainerService extends IntentService {
67     private static final String TAG = "DefContainer";
68     private static final boolean localLOGV = true;
69 
70     private static final String LIB_DIR_NAME = "lib";
71 
72     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
73         /*
74          * Creates a new container and copies resource there.
75          * @param paackageURI the uri of resource to be copied. Can be either
76          * a content uri or a file uri
77          * @param cid the id of the secure container that should
78          * be used for creating a secure container into which the resource
79          * will be copied.
80          * @param key Refers to key used for encrypting the secure container
81          * @param resFileName Name of the target resource file(relative to newly
82          * created secure container)
83          * @return Returns the new cache path where the resource has been copied into
84          *
85          */
86         public String copyResourceToContainer(final Uri packageURI,
87                 final String cid,
88                 final String key, final String resFileName) {
89             if (packageURI == null || cid == null) {
90                 return null;
91             }
92             return copyResourceInner(packageURI, cid, key, resFileName);
93         }
94 
95         /*
96          * Copy specified resource to output stream
97          * @param packageURI the uri of resource to be copied. Should be a
98          * file uri
99          * @param outStream Remote file descriptor to be used for copying
100          * @return Returns true if copy succeded or false otherwise.
101          */
102         public boolean copyResource(final Uri packageURI,
103                 ParcelFileDescriptor outStream) {
104             if (packageURI == null ||  outStream == null) {
105                 return false;
106             }
107             ParcelFileDescriptor.AutoCloseOutputStream
108             autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
109             return copyFile(packageURI, autoOut);
110         }
111 
112         /*
113          * Determine the recommended install location for package
114          * specified by file uri location.
115          * @param fileUri the uri of resource to be copied. Should be a
116          * file uri
117          * @return Returns PackageInfoLite object containing
118          * the package info and recommended app location.
119          */
120         public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags) {
121             PackageInfoLite ret = new PackageInfoLite();
122             if (fileUri == null) {
123                 Log.i(TAG, "Invalid package uri " + fileUri);
124                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
125                 return ret;
126             }
127             String scheme = fileUri.getScheme();
128             if (scheme != null && !scheme.equals("file")) {
129                 Log.w(TAG, "Falling back to installing on internal storage only");
130                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL;
131                 return ret;
132             }
133             String archiveFilePath = fileUri.getPath();
134             PackageParser packageParser = new PackageParser(archiveFilePath);
135             File sourceFile = new File(archiveFilePath);
136             DisplayMetrics metrics = new DisplayMetrics();
137             metrics.setToDefaults();
138             PackageParser.PackageLite pkg = packageParser.parsePackageLite(
139                     archiveFilePath, 0);
140             // Nuke the parser reference right away and force a gc
141             packageParser = null;
142             Runtime.getRuntime().gc();
143             if (pkg == null) {
144                 Log.w(TAG, "Failed to parse package");
145                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
146                 return ret;
147             }
148             ret.packageName = pkg.packageName;
149             ret.installLocation = pkg.installLocation;
150             ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags);
151             return ret;
152         }
153 
154         public boolean checkFreeStorage(boolean external, Uri fileUri) {
155             return checkFreeStorageInner(external, fileUri);
156         }
157 
158         public ObbInfo getObbInfo(String filename) {
159             try {
160                 return ObbScanner.getObbInfo(filename);
161             } catch (IOException e) {
162                 Log.d(TAG, "Couldn't get OBB info for " + filename);
163                 return null;
164             }
165         }
166     };
167 
DefaultContainerService()168     public DefaultContainerService() {
169         super("DefaultContainerService");
170         setIntentRedelivery(true);
171     }
172 
173     @Override
onHandleIntent(Intent intent)174     protected void onHandleIntent(Intent intent) {
175         if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
176             IPackageManager pm = IPackageManager.Stub.asInterface(
177                     ServiceManager.getService("package"));
178             String pkg = null;
179             try {
180                 while ((pkg=pm.nextPackageToClean(pkg)) != null) {
181                     eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg));
182                     eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg));
183                 }
184             } catch (RemoteException e) {
185             }
186         }
187     }
188 
eraseFiles(File path)189     void eraseFiles(File path) {
190         if (path.isDirectory()) {
191             String[] files = path.list();
192             if (files != null) {
193                 for (String file : files) {
194                     eraseFiles(new File(path, file));
195                 }
196             }
197         }
198         path.delete();
199     }
200 
onBind(Intent intent)201     public IBinder onBind(Intent intent) {
202         return mBinder;
203     }
204 
copyResourceInner(Uri packageURI, String newCid, String key, String resFileName)205     private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) {
206         // Make sure the sdcard is mounted.
207         String status = Environment.getExternalStorageState();
208         if (!status.equals(Environment.MEDIA_MOUNTED)) {
209             Log.w(TAG, "Make sure sdcard is mounted.");
210             return null;
211         }
212 
213         // The .apk file
214         String codePath = packageURI.getPath();
215         File codeFile = new File(codePath);
216 
217         // Calculate size of container needed to hold base APK.
218         long sizeBytes = codeFile.length();
219 
220         // Check all the native files that need to be copied and add that to the container size.
221         ZipFile zipFile;
222         List<Pair<ZipEntry, String>> nativeFiles;
223         try {
224             zipFile = new ZipFile(codeFile);
225 
226             nativeFiles = new LinkedList<Pair<ZipEntry, String>>();
227 
228             NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles);
229 
230             final int N = nativeFiles.size();
231             for (int i = 0; i < N; i++) {
232                 final Pair<ZipEntry, String> entry = nativeFiles.get(i);
233 
234                 /*
235                  * Note that PackageHelper.createSdDir adds a 1MB padding on
236                  * our claimed size, so we don't have to worry about block
237                  * alignment here.
238                  */
239                 sizeBytes += entry.first.getSize();
240             }
241         } catch (ZipException e) {
242             Log.w(TAG, "Failed to extract data from package file", e);
243             return null;
244         } catch (IOException e) {
245             Log.w(TAG, "Failed to cache package shared libs", e);
246             return null;
247         }
248 
249         // Create new container
250         String newCachePath = null;
251         if ((newCachePath = PackageHelper.createSdDir(sizeBytes, newCid, key, Process.myUid())) == null) {
252             Log.e(TAG, "Failed to create container " + newCid);
253             return null;
254         }
255         if (localLOGV)
256             Log.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
257         File resFile = new File(newCachePath, resFileName);
258         if (!FileUtils.copyFile(new File(codePath), resFile)) {
259             Log.e(TAG, "Failed to copy " + codePath + " to " + resFile);
260             // Clean up container
261             PackageHelper.destroySdDir(newCid);
262             return null;
263         }
264 
265         try {
266             File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
267             sharedLibraryDir.mkdir();
268 
269             final int N = nativeFiles.size();
270             for (int i = 0; i < N; i++) {
271                 final Pair<ZipEntry, String> entry = nativeFiles.get(i);
272 
273                 InputStream is = zipFile.getInputStream(entry.first);
274                 try {
275                     File destFile = new File(sharedLibraryDir, entry.second);
276                     if (!FileUtils.copyToFile(is, destFile)) {
277                         throw new IOException("Couldn't copy native binary "
278                                 + entry.first.getName() + " to " + entry.second);
279                     }
280                 } finally {
281                     is.close();
282                 }
283             }
284         } catch (IOException e) {
285             Log.e(TAG, "Couldn't copy native file to container", e);
286             PackageHelper.destroySdDir(newCid);
287             return null;
288         }
289 
290         if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile);
291         if (!PackageHelper.finalizeSdDir(newCid)) {
292             Log.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
293             // Clean up container
294             PackageHelper.destroySdDir(newCid);
295         }
296         if (localLOGV) Log.i(TAG, "Finalized container " + newCid);
297         if (PackageHelper.isContainerMounted(newCid)) {
298             if (localLOGV) Log.i(TAG, "Unmounting " + newCid +
299                     " at path " + newCachePath);
300             // Force a gc to avoid being killed.
301             Runtime.getRuntime().gc();
302             PackageHelper.unMountSdDir(newCid);
303         } else {
304             if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted");
305         }
306         return newCachePath;
307     }
308 
copyToFile(InputStream inputStream, FileOutputStream out)309     public static boolean copyToFile(InputStream inputStream, FileOutputStream out) {
310         try {
311             byte[] buffer = new byte[4096];
312             int bytesRead;
313             while ((bytesRead = inputStream.read(buffer)) >= 0) {
314                 out.write(buffer, 0, bytesRead);
315             }
316             return true;
317         } catch (IOException e) {
318             Log.i(TAG, "Exception : " + e + " when copying file");
319             return false;
320         }
321     }
322 
copyToFile(File srcFile, FileOutputStream out)323     public static boolean copyToFile(File srcFile, FileOutputStream out) {
324         InputStream inputStream = null;
325         try {
326             inputStream = new FileInputStream(srcFile);
327             return copyToFile(inputStream, out);
328         } catch (IOException e) {
329             return false;
330         } finally {
331             try { if (inputStream != null) inputStream.close(); } catch (IOException e) {}
332         }
333     }
334 
copyFile(Uri pPackageURI, FileOutputStream outStream)335     private  boolean copyFile(Uri pPackageURI, FileOutputStream outStream) {
336         String scheme = pPackageURI.getScheme();
337         if (scheme == null || scheme.equals("file")) {
338             final File srcPackageFile = new File(pPackageURI.getPath());
339             // We copy the source package file to a temp file and then rename it to the
340             // destination file in order to eliminate a window where the package directory
341             // scanner notices the new package file but it's not completely copied yet.
342             if (!copyToFile(srcPackageFile, outStream)) {
343                 Log.e(TAG, "Couldn't copy file: " + srcPackageFile);
344                 return false;
345             }
346         } else if (scheme.equals("content")) {
347             ParcelFileDescriptor fd = null;
348             try {
349                 fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
350             } catch (FileNotFoundException e) {
351                 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e);
352                 return false;
353             }
354             if (fd == null) {
355                 Log.e(TAG, "Couldn't open file descriptor from download service (null).");
356                 return false;
357             } else {
358                 if (localLOGV) {
359                     Log.v(TAG, "Opened file descriptor from download service.");
360                 }
361                 ParcelFileDescriptor.AutoCloseInputStream
362                 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
363                 // We copy the source package file to a temp file and then rename it to the
364                 // destination file in order to eliminate a window where the package directory
365                 // scanner notices the new package file but it's not completely copied yet.
366                 if (!copyToFile(dlStream, outStream)) {
367                     Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file.");
368                     return false;
369                 }
370             }
371         } else {
372             Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
373             return false;
374         }
375         return true;
376     }
377 
378     // Constants related to app heuristics
379     // No-installation limit for internal flash: 10% or less space available
380     private static final double LOW_NAND_FLASH_TRESHOLD = 0.1;
381 
382     // SD-to-internal app size threshold: currently set to 1 MB
383     private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024);
384     private static final int ERR_LOC = -1;
385 
recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags)386     private int recommendAppInstallLocation(int installLocation,
387             String archiveFilePath, int flags) {
388         boolean checkInt = false;
389         boolean checkExt = false;
390         boolean checkBoth = false;
391         check_inner : {
392             // Check flags.
393             if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
394                 // Check for forward locked app
395                 checkInt = true;
396                 break check_inner;
397             } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
398                 // Explicit flag to install internally.
399                 // Check internal storage and return
400                 checkInt = true;
401                 break check_inner;
402             } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
403                 // Explicit flag to install externally.
404                 // Check external storage and return
405                 checkExt = true;
406                 break check_inner;
407             }
408             // Check for manifest option
409             if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
410                 checkInt = true;
411                 break check_inner;
412             } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
413                 checkExt = true;
414                 checkBoth = true;
415                 break check_inner;
416             } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
417                 checkInt = true;
418                 checkBoth = true;
419                 break check_inner;
420             }
421             // Pick user preference
422             int installPreference = Settings.System.getInt(getApplicationContext()
423                     .getContentResolver(),
424                     Settings.Secure.DEFAULT_INSTALL_LOCATION,
425                     PackageHelper.APP_INSTALL_AUTO);
426             if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
427                 checkInt = true;
428                 break check_inner;
429             } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
430                 checkExt = true;
431                 break check_inner;
432             }
433             // Fall back to default policy if nothing else is specified.
434             checkInt = true;
435         }
436 
437         // Package size = code size + cache size + data size
438         // If code size > 1 MB, install on SD card.
439         // Else install on internal NAND flash, unless space on NAND is less than 10%
440         String status = Environment.getExternalStorageState();
441         long availSDSize = -1;
442         boolean mediaAvailable = false;
443         if (status.equals(Environment.MEDIA_MOUNTED)) {
444             StatFs sdStats = new StatFs(
445                     Environment.getExternalStorageDirectory().getPath());
446             availSDSize = (long)sdStats.getAvailableBlocks() *
447                     (long)sdStats.getBlockSize();
448             mediaAvailable = true;
449         }
450         StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
451         long totalInternalSize = (long)internalStats.getBlockCount() *
452                 (long)internalStats.getBlockSize();
453         long availInternalSize = (long)internalStats.getAvailableBlocks() *
454                 (long)internalStats.getBlockSize();
455 
456         double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
457 
458         File apkFile = new File(archiveFilePath);
459         long pkgLen = apkFile.length();
460 
461         // To make final copy
462         long reqInstallSize = pkgLen;
463         // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
464         long reqInternalSize = 0;
465         boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
466         boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
467         boolean fitsOnSd = false;
468         if (mediaAvailable && (reqInstallSize < availSDSize)) {
469             // If we do not have an internal size requirement
470             // don't do a threshold check.
471             if (reqInternalSize == 0) {
472                 fitsOnSd = true;
473             } else if ((reqInternalSize < availInternalSize) && intThresholdOk) {
474                 fitsOnSd = true;
475             }
476         }
477         boolean fitsOnInt = intThresholdOk && intAvailOk;
478         if (checkInt) {
479             // Check for internal memory availability
480             if (fitsOnInt) {
481                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
482             }
483         } else if (checkExt) {
484             if (fitsOnSd) {
485                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
486             }
487         }
488         if (checkBoth) {
489             // Check for internal first
490             if (fitsOnInt) {
491                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
492             }
493             // Check for external next
494             if (fitsOnSd) {
495                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
496             }
497         }
498         if ((checkExt || checkBoth) && !mediaAvailable) {
499             return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
500         }
501         return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
502     }
503 
checkFreeStorageInner(boolean external, Uri packageURI)504     private boolean checkFreeStorageInner(boolean external, Uri packageURI) {
505         File apkFile = new File(packageURI.getPath());
506         long size = apkFile.length();
507         if (external) {
508             String status = Environment.getExternalStorageState();
509             long availSDSize = -1;
510             if (status.equals(Environment.MEDIA_MOUNTED)) {
511                 StatFs sdStats = new StatFs(
512                         Environment.getExternalStorageDirectory().getPath());
513                 availSDSize = (long)sdStats.getAvailableBlocks() *
514                 (long)sdStats.getBlockSize();
515             }
516             return availSDSize > size;
517         }
518         StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
519         long totalInternalSize = (long)internalStats.getBlockCount() *
520         (long)internalStats.getBlockSize();
521         long availInternalSize = (long)internalStats.getAvailableBlocks() *
522         (long)internalStats.getBlockSize();
523 
524         double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
525         // To make final copy
526         long reqInstallSize = size;
527         // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
528         long reqInternalSize = 0;
529         boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
530         boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
531         return intThresholdOk && intAvailOk;
532     }
533 }
534