Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2009 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.internal.content;
     18 
     19 import static android.net.TrafficStats.MB_IN_BYTES;
     20 
     21 import android.content.Context;
     22 import android.content.pm.ApplicationInfo;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PackageManager.NameNotFoundException;
     26 import android.content.pm.PackageParser.PackageLite;
     27 import android.os.Environment;
     28 import android.os.Environment.UserEnvironment;
     29 import android.os.FileUtils;
     30 import android.os.IBinder;
     31 import android.os.RemoteException;
     32 import android.os.ServiceManager;
     33 import android.os.UserHandle;
     34 import android.os.storage.IMountService;
     35 import android.os.storage.StorageManager;
     36 import android.os.storage.StorageResultCode;
     37 import android.util.Log;
     38 
     39 import libcore.io.IoUtils;
     40 
     41 import java.io.File;
     42 import java.io.FileOutputStream;
     43 import java.io.IOException;
     44 import java.io.InputStream;
     45 import java.util.Collections;
     46 import java.util.zip.ZipEntry;
     47 import java.util.zip.ZipFile;
     48 import java.util.zip.ZipOutputStream;
     49 
     50 /**
     51  * Constants used internally between the PackageManager
     52  * and media container service transports.
     53  * Some utility methods to invoke MountService api.
     54  */
     55 public class PackageHelper {
     56     public static final int RECOMMEND_INSTALL_INTERNAL = 1;
     57     public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
     58     public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
     59     public static final int RECOMMEND_FAILED_INVALID_APK = -2;
     60     public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
     61     public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
     62     public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
     63     public static final int RECOMMEND_FAILED_INVALID_URI = -6;
     64     public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
     65 
     66     private static final boolean localLOGV = false;
     67     private static final String TAG = "PackageHelper";
     68     // App installation location settings values
     69     public static final int APP_INSTALL_AUTO = 0;
     70     public static final int APP_INSTALL_INTERNAL = 1;
     71     public static final int APP_INSTALL_EXTERNAL = 2;
     72 
     73     public static IMountService getMountService() throws RemoteException {
     74         IBinder service = ServiceManager.getService("mount");
     75         if (service != null) {
     76             return IMountService.Stub.asInterface(service);
     77         } else {
     78             Log.e(TAG, "Can't get mount service");
     79             throw new RemoteException("Could not contact mount service");
     80         }
     81     }
     82 
     83     public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
     84             boolean isExternal) {
     85         // Round up to nearest MB, plus another MB for filesystem overhead
     86         final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
     87         try {
     88             IMountService mountService = getMountService();
     89 
     90             if (localLOGV)
     91                 Log.i(TAG, "Size of container " + sizeMb + " MB");
     92 
     93             int rc = mountService.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
     94                     isExternal);
     95             if (rc != StorageResultCode.OperationSucceeded) {
     96                 Log.e(TAG, "Failed to create secure container " + cid);
     97                 return null;
     98             }
     99             String cachePath = mountService.getSecureContainerPath(cid);
    100             if (localLOGV) Log.i(TAG, "Created secure container " + cid +
    101                     " at " + cachePath);
    102                 return cachePath;
    103         } catch (RemoteException e) {
    104             Log.e(TAG, "MountService running?");
    105         }
    106         return null;
    107     }
    108 
    109     public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
    110         // Round up to nearest MB, plus another MB for filesystem overhead
    111         final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
    112         try {
    113             IMountService mountService = getMountService();
    114             int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey);
    115             if (rc == StorageResultCode.OperationSucceeded) {
    116                 return true;
    117             }
    118         } catch (RemoteException e) {
    119             Log.e(TAG, "MountService running?");
    120         }
    121         Log.e(TAG, "Failed to create secure container " + cid);
    122         return false;
    123     }
    124 
    125     public static String mountSdDir(String cid, String key, int ownerUid) {
    126         return mountSdDir(cid, key, ownerUid, true);
    127     }
    128 
    129     public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
    130         try {
    131             int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly);
    132             if (rc != StorageResultCode.OperationSucceeded) {
    133                 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
    134                 return null;
    135             }
    136             return getMountService().getSecureContainerPath(cid);
    137         } catch (RemoteException e) {
    138             Log.e(TAG, "MountService running?");
    139         }
    140         return null;
    141     }
    142 
    143    public static boolean unMountSdDir(String cid) {
    144     try {
    145         int rc = getMountService().unmountSecureContainer(cid, true);
    146         if (rc != StorageResultCode.OperationSucceeded) {
    147             Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
    148             return false;
    149         }
    150         return true;
    151     } catch (RemoteException e) {
    152         Log.e(TAG, "MountService running?");
    153     }
    154         return false;
    155    }
    156 
    157    public static boolean renameSdDir(String oldId, String newId) {
    158        try {
    159            int rc = getMountService().renameSecureContainer(oldId, newId);
    160            if (rc != StorageResultCode.OperationSucceeded) {
    161                Log.e(TAG, "Failed to rename " + oldId + " to " +
    162                        newId + "with rc " + rc);
    163                return false;
    164            }
    165            return true;
    166        } catch (RemoteException e) {
    167            Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
    168                    " with exception : " + e);
    169        }
    170        return false;
    171    }
    172 
    173    public static String getSdDir(String cid) {
    174        try {
    175             return getMountService().getSecureContainerPath(cid);
    176         } catch (RemoteException e) {
    177             Log.e(TAG, "Failed to get container path for " + cid +
    178                 " with exception " + e);
    179         }
    180         return null;
    181    }
    182 
    183    public static String getSdFilesystem(String cid) {
    184        try {
    185             return getMountService().getSecureContainerFilesystemPath(cid);
    186         } catch (RemoteException e) {
    187             Log.e(TAG, "Failed to get container path for " + cid +
    188                 " with exception " + e);
    189         }
    190         return null;
    191    }
    192 
    193     public static boolean finalizeSdDir(String cid) {
    194         try {
    195             int rc = getMountService().finalizeSecureContainer(cid);
    196             if (rc != StorageResultCode.OperationSucceeded) {
    197                 Log.i(TAG, "Failed to finalize container " + cid);
    198                 return false;
    199             }
    200             return true;
    201         } catch (RemoteException e) {
    202             Log.e(TAG, "Failed to finalize container " + cid +
    203                     " with exception " + e);
    204         }
    205         return false;
    206     }
    207 
    208     public static boolean destroySdDir(String cid) {
    209         try {
    210             if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
    211             int rc = getMountService().destroySecureContainer(cid, true);
    212             if (rc != StorageResultCode.OperationSucceeded) {
    213                 Log.i(TAG, "Failed to destroy container " + cid);
    214                 return false;
    215             }
    216             return true;
    217         } catch (RemoteException e) {
    218             Log.e(TAG, "Failed to destroy container " + cid +
    219                     " with exception " + e);
    220         }
    221         return false;
    222     }
    223 
    224     public static String[] getSecureContainerList() {
    225         try {
    226             return getMountService().getSecureContainerList();
    227         } catch (RemoteException e) {
    228             Log.e(TAG, "Failed to get secure container list with exception" +
    229                     e);
    230         }
    231         return null;
    232     }
    233 
    234    public static boolean isContainerMounted(String cid) {
    235        try {
    236            return getMountService().isSecureContainerMounted(cid);
    237        } catch (RemoteException e) {
    238            Log.e(TAG, "Failed to find out if container " + cid + " mounted");
    239        }
    240        return false;
    241    }
    242 
    243     /**
    244      * Extract public files for the single given APK.
    245      */
    246     public static long extractPublicFiles(File apkFile, File publicZipFile)
    247             throws IOException {
    248         final FileOutputStream fstr;
    249         final ZipOutputStream publicZipOutStream;
    250 
    251         if (publicZipFile == null) {
    252             fstr = null;
    253             publicZipOutStream = null;
    254         } else {
    255             fstr = new FileOutputStream(publicZipFile);
    256             publicZipOutStream = new ZipOutputStream(fstr);
    257             Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
    258         }
    259 
    260         long size = 0L;
    261 
    262         try {
    263             final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
    264             try {
    265                 // Copy manifest, resources.arsc and res directory to public zip
    266                 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
    267                     final String zipEntryName = zipEntry.getName();
    268                     if ("AndroidManifest.xml".equals(zipEntryName)
    269                             || "resources.arsc".equals(zipEntryName)
    270                             || zipEntryName.startsWith("res/")) {
    271                         size += zipEntry.getSize();
    272                         if (publicZipFile != null) {
    273                             copyZipEntry(zipEntry, privateZip, publicZipOutStream);
    274                         }
    275                     }
    276                 }
    277             } finally {
    278                 try { privateZip.close(); } catch (IOException e) {}
    279             }
    280 
    281             if (publicZipFile != null) {
    282                 publicZipOutStream.finish();
    283                 publicZipOutStream.flush();
    284                 FileUtils.sync(fstr);
    285                 publicZipOutStream.close();
    286                 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
    287                         | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
    288             }
    289         } finally {
    290             IoUtils.closeQuietly(publicZipOutStream);
    291         }
    292 
    293         return size;
    294     }
    295 
    296     private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
    297             ZipOutputStream outZipStream) throws IOException {
    298         byte[] buffer = new byte[4096];
    299         int num;
    300 
    301         ZipEntry newEntry;
    302         if (zipEntry.getMethod() == ZipEntry.STORED) {
    303             // Preserve the STORED method of the input entry.
    304             newEntry = new ZipEntry(zipEntry);
    305         } else {
    306             // Create a new entry so that the compressed len is recomputed.
    307             newEntry = new ZipEntry(zipEntry.getName());
    308         }
    309         outZipStream.putNextEntry(newEntry);
    310 
    311         final InputStream data = inZipFile.getInputStream(zipEntry);
    312         try {
    313             while ((num = data.read(buffer)) > 0) {
    314                 outZipStream.write(buffer, 0, num);
    315             }
    316             outZipStream.flush();
    317         } finally {
    318             IoUtils.closeQuietly(data);
    319         }
    320     }
    321 
    322     public static boolean fixSdPermissions(String cid, int gid, String filename) {
    323         try {
    324             int rc = getMountService().fixPermissionsSecureContainer(cid, gid, filename);
    325             if (rc != StorageResultCode.OperationSucceeded) {
    326                 Log.i(TAG, "Failed to fixperms container " + cid);
    327                 return false;
    328             }
    329             return true;
    330         } catch (RemoteException e) {
    331             Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
    332         }
    333         return false;
    334     }
    335 
    336     /**
    337      * Given a requested {@link PackageInfo#installLocation} and calculated
    338      * install size, pick the actual location to install the app.
    339      */
    340     public static int resolveInstallLocation(Context context, String packageName,
    341             int installLocation, long sizeBytes, int installFlags) {
    342         ApplicationInfo existingInfo = null;
    343         try {
    344             existingInfo = context.getPackageManager().getApplicationInfo(packageName,
    345                     PackageManager.GET_UNINSTALLED_PACKAGES);
    346         } catch (NameNotFoundException ignored) {
    347         }
    348 
    349         final int prefer;
    350         final boolean checkBoth;
    351         if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
    352             prefer = RECOMMEND_INSTALL_INTERNAL;
    353             checkBoth = false;
    354         } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
    355             prefer = RECOMMEND_INSTALL_EXTERNAL;
    356             checkBoth = false;
    357         } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
    358             prefer = RECOMMEND_INSTALL_INTERNAL;
    359             checkBoth = false;
    360         } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
    361             prefer = RECOMMEND_INSTALL_EXTERNAL;
    362             checkBoth = true;
    363         } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
    364             // When app is already installed, prefer same medium
    365             if (existingInfo != null) {
    366                 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
    367                     prefer = RECOMMEND_INSTALL_EXTERNAL;
    368                 } else {
    369                     prefer = RECOMMEND_INSTALL_INTERNAL;
    370                 }
    371             } else {
    372                 prefer = RECOMMEND_INSTALL_INTERNAL;
    373             }
    374             checkBoth = true;
    375         } else {
    376             prefer = RECOMMEND_INSTALL_INTERNAL;
    377             checkBoth = false;
    378         }
    379 
    380         final boolean emulated = Environment.isExternalStorageEmulated();
    381         final StorageManager storage = StorageManager.from(context);
    382 
    383         boolean fitsOnInternal = false;
    384         if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
    385             final File target = Environment.getDataDirectory();
    386             fitsOnInternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
    387         }
    388 
    389         boolean fitsOnExternal = false;
    390         if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) {
    391             final File target = new UserEnvironment(UserHandle.USER_OWNER)
    392                     .getExternalStorageDirectory();
    393             // External is only an option when size is known
    394             if (sizeBytes > 0) {
    395                 fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
    396             }
    397         }
    398 
    399         if (prefer == RECOMMEND_INSTALL_INTERNAL) {
    400             if (fitsOnInternal) {
    401                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
    402             }
    403         } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) {
    404             if (fitsOnExternal) {
    405                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
    406             }
    407         }
    408 
    409         if (checkBoth) {
    410             if (fitsOnInternal) {
    411                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
    412             } else if (!emulated && fitsOnExternal) {
    413                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
    414             }
    415         }
    416 
    417         /*
    418          * If they requested to be on the external media by default, return that
    419          * the media was unavailable. Otherwise, indicate there was insufficient
    420          * storage space available.
    421          */
    422         if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)
    423                 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
    424             return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
    425         } else {
    426             return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
    427         }
    428     }
    429 
    430     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
    431             String abiOverride) throws IOException {
    432         NativeLibraryHelper.Handle handle = null;
    433         try {
    434             handle = NativeLibraryHelper.Handle.create(pkg);
    435             return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
    436         } finally {
    437             IoUtils.closeQuietly(handle);
    438         }
    439     }
    440 
    441     public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
    442             boolean isForwardLocked, String abiOverride) throws IOException {
    443         long sizeBytes = 0;
    444 
    445         // Include raw APKs, and possibly unpacked resources
    446         for (String codePath : pkg.getAllCodePaths()) {
    447             final File codeFile = new File(codePath);
    448             sizeBytes += codeFile.length();
    449 
    450             if (isForwardLocked) {
    451                 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
    452             }
    453         }
    454 
    455         // Include all relevant native code
    456         sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
    457 
    458         return sizeBytes;
    459     }
    460 
    461     public static String replaceEnd(String str, String before, String after) {
    462         if (!str.endsWith(before)) {
    463             throw new IllegalArgumentException(
    464                     "Expected " + str + " to end with " + before);
    465         }
    466         return str.substring(0, str.length() - before.length()) + after;
    467     }
    468 }
    469