Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62e1b4e9 authored by Kenny Root's avatar Kenny Root
Browse files

Revise free space checks for package installs

Change-Id: Ie72bbab77aa89a50ec096edc1f471eab74829e67
parent 6f3a75e1
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -27,8 +27,9 @@ interface IMediaContainerService {
                String key, String resFileName);
    boolean copyResource(in Uri packageURI,
                in ParcelFileDescriptor outStream);
    PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags);
    boolean checkFreeStorage(boolean external, in Uri fileUri);
    PackageInfoLite getMinimalPackageInfo(in Uri fileUri, in int flags, in long threshold);
    boolean checkInternalFreeStorage(in Uri fileUri, in long threshold);
    boolean checkExternalFreeStorage(in Uri fileUri);
    ObbInfo getObbInfo(in String filename);
    long calculateDirectorySize(in String directory);
}
+3 −8
Original line number Diff line number Diff line
@@ -56,18 +56,13 @@ public class PackageHelper {
        return null;
    }

    public static String createSdDir(long sizeBytes, String cid,
    public static String createSdDir(int sizeMb, String cid,
            String sdEncKey, int uid) {
        // Create mount point via MountService
        IMountService mountService = getMountService();
        int sizeMb = (int) (sizeBytes >> 20);
        if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
            sizeMb++;
        }
        // Add buffer size
        sizeMb++;

        if (localLOGV)
            Log.i(TAG, "Size of container " + sizeMb + " MB " + sizeBytes + " bytes");
            Log.i(TAG, "Size of container " + sizeMb + " MB");

        try {
            int rc = mountService.createSecureContainer(
+163 −146
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
@@ -117,7 +117,7 @@ public class DefaultContainerService extends IntentService {
         * @return Returns PackageInfoLite object containing
         * the package info and recommended app location.
         */
        public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags) {
        public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) {
            PackageInfoLite ret = new PackageInfoLite();
            if (fileUri == null) {
                Log.i(TAG, "Invalid package uri " + fileUri);
@@ -131,15 +131,9 @@ public class DefaultContainerService extends IntentService {
                return ret;
            }
            String archiveFilePath = fileUri.getPath();
            PackageParser packageParser = new PackageParser(archiveFilePath);
            File sourceFile = new File(archiveFilePath);
            DisplayMetrics metrics = new DisplayMetrics();
            metrics.setToDefaults();
            PackageParser.PackageLite pkg = packageParser.parsePackageLite(
                    archiveFilePath, 0);
            // Nuke the parser reference right away and force a gc
            packageParser = null;
            Runtime.getRuntime().gc();
            PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0);
            if (pkg == null) {
                Log.w(TAG, "Failed to parse package");
                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
@@ -147,12 +141,22 @@ public class DefaultContainerService extends IntentService {
            }
            ret.packageName = pkg.packageName;
            ret.installLocation = pkg.installLocation;
            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags);
            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
                    archiveFilePath, flags, threshold);
            return ret;
        }

        public boolean checkFreeStorage(boolean external, Uri fileUri) {
            return checkFreeStorageInner(external, fileUri);
        @Override
        public boolean checkInternalFreeStorage(Uri packageUri, long threshold)
                throws RemoteException {
            final File apkFile = new File(packageUri.getPath());
            return isUnderInternalThreshold(apkFile, threshold);
        }

        @Override
        public boolean checkExternalFreeStorage(Uri packageUri) throws RemoteException {
            final File apkFile = new File(packageUri.getPath());
            return isUnderExternalThreshold(apkFile);
        }

        public ObbInfo getObbInfo(String filename) {
@@ -225,41 +229,15 @@ public class DefaultContainerService extends IntentService {
        String codePath = packageURI.getPath();
        File codeFile = new File(codePath);

        // Calculate size of container needed to hold base APK.
        long sizeBytes = codeFile.length();

        // Check all the native files that need to be copied and add that to the container size.
        ZipFile zipFile;
        List<Pair<ZipEntry, String>> nativeFiles;
        try {
            zipFile = new ZipFile(codeFile);

            nativeFiles = new LinkedList<Pair<ZipEntry, String>>();

            NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles);

            final int N = nativeFiles.size();
            for (int i = 0; i < N; i++) {
                final Pair<ZipEntry, String> entry = nativeFiles.get(i);
        // Native files we need to copy to the container.
        List<Pair<ZipEntry, String>> nativeFiles = new ArrayList<Pair<ZipEntry, String>>();

                /*
                 * Note that PackageHelper.createSdDir adds a 1MB padding on
                 * our claimed size, so we don't have to worry about block
                 * alignment here.
                 */
                sizeBytes += entry.first.getSize();
            }
        } catch (ZipException e) {
            Log.w(TAG, "Failed to extract data from package file", e);
            return null;
        } catch (IOException e) {
            Log.w(TAG, "Failed to cache package shared libs", e);
            return null;
        }
        // Calculate size of container needed to hold base APK.
        final int sizeMb = calculateContainerSize(codeFile, nativeFiles);

        // Create new container
        String newCachePath = null;
        if ((newCachePath = PackageHelper.createSdDir(sizeBytes, newCid, key, Process.myUid())) == null) {
        if ((newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid())) == null) {
            Log.e(TAG, "Failed to create container " + newCid);
            return null;
        }
@@ -274,6 +252,8 @@ public class DefaultContainerService extends IntentService {
        }

        try {
            ZipFile zipFile = new ZipFile(codeFile);

            File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
            sharedLibraryDir.mkdir();

@@ -386,159 +366,196 @@ public class DefaultContainerService extends IntentService {
        return true;
    }

    // Constants related to app heuristics
    // No-installation limit for internal flash: 10% or less space available
    private static final double LOW_NAND_FLASH_TRESHOLD = 0.1;

    // SD-to-internal app size threshold: currently set to 1 MB
    private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024);
    private static final int ERR_LOC = -1;
    private static final int PREFER_INTERNAL = 1;
    private static final int PREFER_EXTERNAL = 2;

    private int recommendAppInstallLocation(int installLocation,
            String archiveFilePath, int flags) {
        boolean checkInt = false;
        boolean checkExt = false;
    private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
            long threshold) {
        int prefer;
        boolean checkBoth = false;

        check_inner : {
            // Check flags.
            /*
             * Explicit install flags should override the manifest settings.
             */
            if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
                // Check for forward locked app
                checkInt = true;
                /*
                 * Forward-locked applications cannot be installed on SD card,
                 * so only allow checking internal storage.
                 */
                prefer = PREFER_INTERNAL;
                break check_inner;
            } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
                // Explicit flag to install internally.
                // Check internal storage and return
                checkInt = true;
                prefer = PREFER_INTERNAL;
                break check_inner;
            } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
                // Explicit flag to install externally.
                // Check external storage and return
                checkExt = true;
                prefer = PREFER_EXTERNAL;
                break check_inner;
            }
            // Check for manifest option

            /* No install flags. Check for manifest option. */
            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
                checkInt = true;
                prefer = PREFER_INTERNAL;
                break check_inner;
            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
                checkExt = true;
                prefer = PREFER_EXTERNAL;
                checkBoth = true;
                break check_inner;
            } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
                checkInt = true;
                // We default to preferring internal storage.
                prefer = PREFER_INTERNAL;
                checkBoth = true;
                break check_inner;
            }

            // Pick user preference
            int installPreference = Settings.System.getInt(getApplicationContext()
                    .getContentResolver(),
                    Settings.Secure.DEFAULT_INSTALL_LOCATION,
                    PackageHelper.APP_INSTALL_AUTO);
            if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
                checkInt = true;
                prefer = PREFER_INTERNAL;
                break check_inner;
            } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
                checkExt = true;
                prefer = PREFER_EXTERNAL;
                break check_inner;
            }
            // Fall back to default policy if nothing else is specified.
            checkInt = true;

            /*
             * Fall back to default policy of internal-only if nothing else is
             * specified.
             */
            prefer = PREFER_INTERNAL;
        }

        final boolean emulated = Environment.isExternalStorageEmulated();

        final File apkFile = new File(archiveFilePath);

        boolean fitsOnInternal = false;
        if (checkBoth || prefer == PREFER_INTERNAL) {
            fitsOnInternal = isUnderInternalThreshold(apkFile, threshold);
        }

        // Package size = code size + cache size + data size
        // If code size > 1 MB, install on SD card.
        // Else install on internal NAND flash, unless space on NAND is less than 10%
        String status = Environment.getExternalStorageState();
        long availSDSize = -1;
        boolean mediaAvailable = false;
        if (!Environment.isExternalStorageEmulated() && status.equals(Environment.MEDIA_MOUNTED)) {
            StatFs sdStats = new StatFs(
                    Environment.getExternalStorageDirectory().getPath());
            availSDSize = (long)sdStats.getAvailableBlocks() *
                    (long)sdStats.getBlockSize();
            mediaAvailable = true;
        }
        StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
        long totalInternalSize = (long)internalStats.getBlockCount() *
                (long)internalStats.getBlockSize();
        long availInternalSize = (long)internalStats.getAvailableBlocks() *
                (long)internalStats.getBlockSize();

        double pctNandFree = (double)availInternalSize / (double)totalInternalSize;

        File apkFile = new File(archiveFilePath);
        long pkgLen = apkFile.length();
        
        // To make final copy
        long reqInstallSize = pkgLen;
        // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
        long reqInternalSize = 0;
        boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
        boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
        boolean fitsOnSd = false;
        if (mediaAvailable && (reqInstallSize < availSDSize)) {
            // If we do not have an internal size requirement
            // don't do a threshold check.
            if (reqInternalSize == 0) {
                fitsOnSd = true;
            } else if ((reqInternalSize < availInternalSize) && intThresholdOk) {
                fitsOnSd = true;
            }
        }
        boolean fitsOnInt = intThresholdOk && intAvailOk;
        if (checkInt) {
            // Check for internal memory availability
            if (fitsOnInt) {
        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
            fitsOnSd = isUnderExternalThreshold(apkFile);
        }

        if (prefer == PREFER_INTERNAL) {
            if (fitsOnInternal) {
                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
            }
        } else if (checkExt) {
        } else if (!emulated && prefer == PREFER_EXTERNAL) {
            if (fitsOnSd) {
                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
            }
        }

        if (checkBoth) {
            // Check for internal first
            if (fitsOnInt) {
            if (fitsOnInternal) {
                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
            }
            // Check for external next
            if (fitsOnSd) {
            } else if (!emulated && fitsOnSd) {
                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
            }
        }
        if ((checkExt || checkBoth) && !mediaAvailable) {

        /*
         * If they requested to be on the external media by default, return that
         * the media was unavailable. Otherwise, indicate there was insufficient
         * storage space available.
         */
        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
                && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
        }
        } else {
            return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
        }
    }

    private boolean checkFreeStorageInner(boolean external, Uri packageURI) {
        File apkFile = new File(packageURI.getPath());
        long size = apkFile.length();
        if (external) {
            String status = Environment.getExternalStorageState();
            long availSDSize = -1;
            if (status.equals(Environment.MEDIA_MOUNTED)) {
                StatFs sdStats = new StatFs(
                        Environment.getExternalStorageDirectory().getPath());
                availSDSize = (long)sdStats.getAvailableBlocks() *
                (long)sdStats.getBlockSize();
            }
            return availSDSize > size;
        }
        StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
        long totalInternalSize = (long)internalStats.getBlockCount() *
        (long)internalStats.getBlockSize();
        long availInternalSize = (long)internalStats.getAvailableBlocks() *
        (long)internalStats.getBlockSize();

        double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
        // To make final copy
        long reqInstallSize = size;
        // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
        long reqInternalSize = 0;
        boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
        boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
        return intThresholdOk && intAvailOk;
    private boolean isUnderInternalThreshold(File apkFile, long threshold) {
        final long size = apkFile.length();

        final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
        final long availInternalSize = (long) internalStats.getAvailableBlocks()
                * (long) internalStats.getBlockSize();

        return (availInternalSize - size) > threshold;
    }


    private boolean isUnderExternalThreshold(File apkFile) {
        if (Environment.isExternalStorageEmulated()) {
            return false;
        }

        final int sizeMb = calculateContainerSize(apkFile, null);

        final int availSdMb;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
            long availSdSize = (long) (sdStats.getAvailableBlocks() * sdStats.getBlockSize());
            availSdMb = (int) (availSdSize >> 20);
        } else {
            availSdMb = -1;
        }

        return availSdMb > sizeMb;
    }

    /**
     * Calculate the container size for an APK. Takes into account the
     * 
     * @param apkFile file from which to calculate size
     * @return size in megabytes (2^20 bytes)
     */
    private int calculateContainerSize(File apkFile, List<Pair<ZipEntry, String>> outFiles) {
        // Calculate size of container needed to hold base APK.
        long sizeBytes = apkFile.length();

        // Check all the native files that need to be copied and add that to the
        // container size.
        ZipFile zipFile;
        final List<Pair<ZipEntry, String>> nativeFiles;
        try {
            zipFile = new ZipFile(apkFile);

            if (outFiles != null) {
                nativeFiles = outFiles;
            } else {
                nativeFiles = new ArrayList<Pair<ZipEntry, String>>();
            }

            NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles);

            final int N = nativeFiles.size();
            for (int i = 0; i < N; i++) {
                final Pair<ZipEntry, String> entry = nativeFiles.get(i);

                /*
                 * Note a 1MB padding is added to the claimed size, so we don't
                 * have to worry about block alignment here.
                 */
                sizeBytes += entry.first.getSize();
            }
        } catch (ZipException e) {
            Log.w(TAG, "Failed to extract data from package file", e);
        } catch (IOException e) {
            Log.w(TAG, "Failed to cache package shared libs", e);
        }

        int sizeMb = (int) (sizeBytes >> 20);
        if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
            sizeMb++;
        }

        /*
         * Add buffer size because we don't have a good way to determine the
         * real FAT size. Your FAT size varies with how many directory entries
         * you need, how big the whole filesystem is, and other such headaches.
         */
        sizeMb++;

        return sizeMb;
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -398,4 +398,24 @@ class DeviceStorageMonitorService extends Binder {
        // force an early check
        postCheckMemoryMsg(true, 0);
    }

    /**
     * Callable from other things in the system service to obtain the low memory
     * threshold.
     * 
     * @return low memory threshold in bytes
     */
    public long getMemoryLowThreshold() {
        return mMemLowThreshold;
    }

    /**
     * Callable from other things in the system process to check whether memory
     * is low.
     * 
     * @return true is memory is low
     */
    public boolean isMemoryLow() {
        return mLowMemFlag;
    }
}
+31 −3
Original line number Diff line number Diff line
@@ -4919,12 +4919,24 @@ class PackageManagerService extends IPackageManager.Stub {
                Slog.w(TAG, "Cannot install fwd locked apps on sdcard");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else {
                final long lowThreshold;

                final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
                        .getService(DeviceStorageMonitorService.SERVICE);
                if (dsm == null) {
                    Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
                    lowThreshold = 0L;
                } else {
                    lowThreshold = dsm.getMemoryLowThreshold();
                }

                // Remote call to find out default install location
                final PackageInfoLite pkgLite;
                try {
                    mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags);
                    pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags,
                            lowThreshold);
                } finally {
                    mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
@@ -5144,10 +5156,26 @@ class PackageManagerService extends IPackageManager.Stub {
        }

        boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
            final long lowThreshold;

            final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
                    .getService(DeviceStorageMonitorService.SERVICE);
            if (dsm == null) {
                Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
                lowThreshold = 0L;
            } else {
                if (dsm.isMemoryLow()) {
                    Log.w(TAG, "Memory is reported as being too low; aborting package install");
                    return false;
                }

                lowThreshold = dsm.getMemoryLowThreshold();
            }

            try {
                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                return imcs.checkFreeStorage(false, packageURI);
                return imcs.checkInternalFreeStorage(packageURI, lowThreshold);
            } finally {
                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
@@ -5373,7 +5401,7 @@ class PackageManagerService extends IPackageManager.Stub {
            try {
                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                return imcs.checkFreeStorage(true, packageURI);
                return imcs.checkExternalFreeStorage(packageURI);
            } finally {
                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }