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