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

Commit 683bcd30 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Use allocatable space when measuring for install.

The system is often willing to clear cached data to make room for
incoming installs, so use StorageManager.getAllocatableBytes() when
making "does it fit?" style decisions.

Add new INSTALL_ALLOCATE_AGGRESSIVE flag, which will flow through
to use StorageManager.FLAG_ALLOCATE_AGGRESSIVE when making allocation
related requests.  (This can be used by installers to indicate
packages that are critical to system health or security.

Test: runtest -x frameworks/base/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
Bug: 36131437
Change-Id: If8118762fd1ca1f497d2cdd1787bdb3c9759dcc0
parent cf2c279a
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -11086,6 +11086,7 @@ package android.content.pm {
  public static class PackageInstaller.SessionParams implements android.os.Parcelable {
  public static class PackageInstaller.SessionParams implements android.os.Parcelable {
    ctor public PackageInstaller.SessionParams(int);
    ctor public PackageInstaller.SessionParams(int);
    method public int describeContents();
    method public int describeContents();
    method public void setAllocateAggressive(boolean);
    method public void setAllowDowngrade(boolean);
    method public void setAllowDowngrade(boolean);
    method public void setAppIcon(android.graphics.Bitmap);
    method public void setAppIcon(android.graphics.Bitmap);
    method public void setAppLabel(java.lang.CharSequence);
    method public void setAppLabel(java.lang.CharSequence);
+10 −0
Original line number Original line Diff line number Diff line
@@ -1152,6 +1152,16 @@ public class PackageInstaller {
            this.installReason = installReason;
            this.installReason = installReason;
        }
        }


        /** {@hide} */
        @SystemApi
        public void setAllocateAggressive(boolean allocateAggressive) {
            if (allocateAggressive) {
                installFlags |= PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
            } else {
                installFlags &= ~PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
            }
        }

        /** {@hide} */
        /** {@hide} */
        public void dump(IndentingPrintWriter pw) {
        public void dump(IndentingPrintWriter pw) {
            pw.printPair("mode", mode);
            pw.printPair("mode", mode);
+22 −9
Original line number Original line Diff line number Diff line
@@ -51,6 +51,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeInfo;
import android.util.AndroidException;
import android.util.AndroidException;
import android.util.Log;
import android.util.Log;
@@ -610,6 +611,9 @@ public abstract class PackageManager {
            INSTALL_FORCE_PERMISSION_PROMPT,
            INSTALL_FORCE_PERMISSION_PROMPT,
            INSTALL_INSTANT_APP,
            INSTALL_INSTANT_APP,
            INSTALL_DONT_KILL_APP,
            INSTALL_DONT_KILL_APP,
            INSTALL_FORCE_SDK,
            INSTALL_FULL_APP,
            INSTALL_ALLOCATE_AGGRESSIVE,
    })
    })
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface InstallFlags {}
    public @interface InstallFlags {}
@@ -716,15 +720,6 @@ public abstract class PackageManager {
     */
     */
    public static final int INSTALL_INSTANT_APP = 0x00000800;
    public static final int INSTALL_INSTANT_APP = 0x00000800;


    /**
     * Flag parameter for {@link #installPackage} to indicate that this package is
     * to be installed as a heavy weight app. This is fundamentally the opposite of
     * {@link #INSTALL_INSTANT_APP}.
     *
     * @hide
     */
    public static final int INSTALL_FULL_APP = 0x00004000;

    /**
    /**
     * Flag parameter for {@link #installPackage} to indicate that this package contains
     * Flag parameter for {@link #installPackage} to indicate that this package contains
     * a feature split to an existing application and the existing application should not
     * a feature split to an existing application and the existing application should not
@@ -742,6 +737,24 @@ public abstract class PackageManager {
     */
     */
    public static final int INSTALL_FORCE_SDK = 0x00002000;
    public static final int INSTALL_FORCE_SDK = 0x00002000;


    /**
     * Flag parameter for {@link #installPackage} to indicate that this package is
     * to be installed as a heavy weight app. This is fundamentally the opposite of
     * {@link #INSTALL_INSTANT_APP}.
     *
     * @hide
     */
    public static final int INSTALL_FULL_APP = 0x00004000;

    /**
     * Flag parameter for {@link #installPackage} to indicate that this package
     * is critical to system health or security, meaning the system should use
     * {@link StorageManager#FLAG_ALLOCATE_AGGRESSIVE} internally.
     *
     * @hide
     */
    public static final int INSTALL_ALLOCATE_AGGRESSIVE = 0x00008000;

    /**
    /**
     * Flag parameter for
     * Flag parameter for
     * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
     * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
+75 −34
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageLite;
import android.os.Environment;
import android.os.Environment;
@@ -354,10 +355,12 @@ public class PackageHelper {
        abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
        abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
        abstract public File getDataDirectory();
        abstract public File getDataDirectory();


        public boolean fitsOnInternalStorage(Context context, long sizeBytes) {
        public boolean fitsOnInternalStorage(Context context, SessionParams params)
                throws IOException {
            StorageManager storage = getStorageManager(context);
            StorageManager storage = getStorageManager(context);
            File target = getDataDirectory();
            File target = getDataDirectory();
            return (sizeBytes <= storage.getStorageBytesUntilLow(target));
            return (params.sizeBytes <= storage.getAllocatableBytes(target,
                    translateAllocateFlags(params.installFlags)));
        }
        }
    }
    }


@@ -401,6 +404,18 @@ public class PackageHelper {
        return sDefaultTestableInterface;
        return sDefaultTestableInterface;
    }
    }


    @VisibleForTesting
    @Deprecated
    public static String resolveInstallVolume(Context context, String packageName,
            int installLocation, long sizeBytes, TestableInterface testInterface)
            throws IOException {
        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
        params.appPackageName = packageName;
        params.installLocation = installLocation;
        params.sizeBytes = sizeBytes;
        return resolveInstallVolume(context, params, testInterface);
    }

    /**
    /**
     * Given a requested {@link PackageInfo#installLocation} and calculated
     * Given a requested {@link PackageInfo#installLocation} and calculated
     * install size, pick the actual volume to install the app. Only considers
     * install size, pick the actual volume to install the app. Only considers
@@ -410,25 +425,25 @@ public class PackageHelper {
     * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
     * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
     *         for internal storage.
     *         for internal storage.
     */
     */
    public static String resolveInstallVolume(Context context, String packageName,
    public static String resolveInstallVolume(Context context, SessionParams params)
            int installLocation, long sizeBytes) throws IOException {
            throws IOException {
        TestableInterface testableInterface = getDefaultTestableInterface();
        TestableInterface testableInterface = getDefaultTestableInterface();
        return resolveInstallVolume(context, packageName,
        return resolveInstallVolume(context, params.appPackageName, params.installLocation,
                installLocation, sizeBytes, testableInterface);
                params.sizeBytes, testableInterface);
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    public static String resolveInstallVolume(Context context, String packageName,
    public static String resolveInstallVolume(Context context, SessionParams params,
            int installLocation, long sizeBytes, TestableInterface testInterface)
            TestableInterface testInterface) throws IOException {
            throws IOException {
        final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
        final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
        final boolean allow3rdPartyOnInternal =
        final boolean allow3rdPartyOnInternal =
                testInterface.getAllow3rdPartyOnInternalConfig(context);
                testInterface.getAllow3rdPartyOnInternalConfig(context);
        // TODO: handle existing apps installed in ASEC; currently assumes
        // TODO: handle existing apps installed in ASEC; currently assumes
        // they'll end up back on internal storage
        // they'll end up back on internal storage
        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, packageName);
        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
                params.appPackageName);


        final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, sizeBytes);
        final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, params);
        final StorageManager storageManager =
        final StorageManager storageManager =
                testInterface.getStorageManager(context);
                testInterface.getStorageManager(context);


@@ -438,7 +453,8 @@ public class PackageHelper {
                return StorageManager.UUID_PRIVATE_INTERNAL;
                return StorageManager.UUID_PRIVATE_INTERNAL;
            } else {
            } else {
                throw new IOException("Not enough space on existing volume "
                throw new IOException("Not enough space on existing volume "
                        + existingInfo.volumeUuid + " for system app " + packageName + " upgrade");
                        + existingInfo.volumeUuid + " for system app " + params.appPackageName
                        + " upgrade");
            }
            }
        }
        }


@@ -450,8 +466,9 @@ public class PackageHelper {
            boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
            boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()
            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()
                    && (!isInternalStorage || allow3rdPartyOnInternal)) {
                    && (!isInternalStorage || allow3rdPartyOnInternal)) {
                final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
                final long availBytes = storageManager.getAllocatableBytes(new File(vol.path),
                if (availBytes >= sizeBytes) {
                        translateAllocateFlags(params.installFlags));
                if (availBytes >= params.sizeBytes) {
                    allCandidates.add(vol.fsUuid);
                    allCandidates.add(vol.fsUuid);
                }
                }
                if (availBytes >= bestCandidateAvailBytes) {
                if (availBytes >= bestCandidateAvailBytes) {
@@ -463,11 +480,11 @@ public class PackageHelper {


        // If app expresses strong desire for internal storage, honor it
        // If app expresses strong desire for internal storage, honor it
        if (!forceAllowOnExternal
        if (!forceAllowOnExternal
                && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
                && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
            if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
            if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
                    StorageManager.UUID_PRIVATE_INTERNAL)) {
                    StorageManager.UUID_PRIVATE_INTERNAL)) {
                throw new IOException("Cannot automatically move " + packageName + " from "
                throw new IOException("Cannot automatically move " + params.appPackageName
                        + existingInfo.volumeUuid + " to internal storage");
                        + " from " + existingInfo.volumeUuid + " to internal storage");
            }
            }


            if (!allow3rdPartyOnInternal) {
            if (!allow3rdPartyOnInternal) {
@@ -490,7 +507,7 @@ public class PackageHelper {
                return existingInfo.volumeUuid;
                return existingInfo.volumeUuid;
            } else {
            } else {
                throw new IOException("Not enough space on existing volume "
                throw new IOException("Not enough space on existing volume "
                        + existingInfo.volumeUuid + " for " + packageName + " upgrade");
                        + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
            }
            }
        }
        }


@@ -504,29 +521,45 @@ public class PackageHelper {
        }
        }
    }
    }


    public static boolean fitsOnInternal(Context context, long sizeBytes) {
    public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
        final StorageManager storage = context.getSystemService(StorageManager.class);
        final StorageManager storage = context.getSystemService(StorageManager.class);
        final File target = Environment.getDataDirectory();
        final File target = Environment.getDataDirectory();
        return (sizeBytes <= storage.getStorageBytesUntilLow(target));
        return (params.sizeBytes <= storage.getAllocatableBytes(target,
                translateAllocateFlags(params.installFlags)));
    }
    }


    public static boolean fitsOnExternal(Context context, long sizeBytes) {
    public static boolean fitsOnExternal(Context context, SessionParams params) {
        final StorageManager storage = context.getSystemService(StorageManager.class);
        final StorageManager storage = context.getSystemService(StorageManager.class);
        final StorageVolume primary = storage.getPrimaryVolume();
        final StorageVolume primary = storage.getPrimaryVolume();
        return (sizeBytes > 0) && !primary.isEmulated()
        return (params.sizeBytes > 0) && !primary.isEmulated()
                && Environment.MEDIA_MOUNTED.equals(primary.getState())
                && Environment.MEDIA_MOUNTED.equals(primary.getState())
                && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
                && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
    }

    @Deprecated
    public static int resolveInstallLocation(Context context, String packageName,
            int installLocation, long sizeBytes, int installFlags) {
        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
        params.appPackageName = packageName;
        params.installLocation = installLocation;
        params.sizeBytes = sizeBytes;
        params.installFlags = installFlags;
        try {
            return resolveInstallLocation(context, params);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }
    }


    /**
    /**
     * Given a requested {@link PackageInfo#installLocation} and calculated
     * Given a requested {@link PackageInfo#installLocation} and calculated
     * install size, pick the actual location to install the app.
     * install size, pick the actual location to install the app.
     */
     */
    public static int resolveInstallLocation(Context context, String packageName,
    public static int resolveInstallLocation(Context context, SessionParams params)
            int installLocation, long sizeBytes, int installFlags) {
            throws IOException {
        ApplicationInfo existingInfo = null;
        ApplicationInfo existingInfo = null;
        try {
        try {
            existingInfo = context.getPackageManager().getApplicationInfo(packageName,
            existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
                    PackageManager.MATCH_ANY_USER);
                    PackageManager.MATCH_ANY_USER);
        } catch (NameNotFoundException ignored) {
        } catch (NameNotFoundException ignored) {
        }
        }
@@ -534,23 +567,23 @@ public class PackageHelper {
        final int prefer;
        final int prefer;
        final boolean checkBoth;
        final boolean checkBoth;
        boolean ephemeral = false;
        boolean ephemeral = false;
        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
        if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
            prefer = RECOMMEND_INSTALL_INTERNAL;
            prefer = RECOMMEND_INSTALL_INTERNAL;
            ephemeral = true;
            ephemeral = true;
            checkBoth = false;
            checkBoth = false;
        } else if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
        } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
            prefer = RECOMMEND_INSTALL_INTERNAL;
            prefer = RECOMMEND_INSTALL_INTERNAL;
            checkBoth = false;
            checkBoth = false;
        } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
        } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
            prefer = RECOMMEND_INSTALL_EXTERNAL;
            prefer = RECOMMEND_INSTALL_EXTERNAL;
            checkBoth = false;
            checkBoth = false;
        } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
            prefer = RECOMMEND_INSTALL_INTERNAL;
            prefer = RECOMMEND_INSTALL_INTERNAL;
            checkBoth = false;
            checkBoth = false;
        } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
            prefer = RECOMMEND_INSTALL_EXTERNAL;
            prefer = RECOMMEND_INSTALL_EXTERNAL;
            checkBoth = true;
            checkBoth = true;
        } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
            // When app is already installed, prefer same medium
            // When app is already installed, prefer same medium
            if (existingInfo != null) {
            if (existingInfo != null) {
                // TODO: distinguish if this is external ASEC
                // TODO: distinguish if this is external ASEC
@@ -570,12 +603,12 @@ public class PackageHelper {


        boolean fitsOnInternal = false;
        boolean fitsOnInternal = false;
        if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
        if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
            fitsOnInternal = fitsOnInternal(context, sizeBytes);
            fitsOnInternal = fitsOnInternal(context, params);
        }
        }


        boolean fitsOnExternal = false;
        boolean fitsOnExternal = false;
        if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
        if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
            fitsOnExternal = fitsOnExternal(context, sizeBytes);
            fitsOnExternal = fitsOnExternal(context, params);
        }
        }


        if (prefer == RECOMMEND_INSTALL_INTERNAL) {
        if (prefer == RECOMMEND_INSTALL_INTERNAL) {
@@ -641,4 +674,12 @@ public class PackageHelper {
        }
        }
        return str.substring(0, str.length() - before.length()) + after;
        return str.substring(0, str.length() - before.length()) + after;
    }
    }

    public static int translateAllocateFlags(int installFlags) {
        if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {
            return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;
        } else {
            return 0;
        }
    }
}
}
+24 −29
Original line number Original line Diff line number Diff line
@@ -58,9 +58,9 @@ public class PackageHelperTests extends AndroidTestCase {
    private static final long sAdoptedSize = 10000;
    private static final long sAdoptedSize = 10000;
    private static final long sPublicSize = 1000000;
    private static final long sPublicSize = 1000000;


    private static final StorageManager sStorageManager = createStorageManagerMock();
    private static StorageManager sStorageManager;


    private static StorageManager createStorageManagerMock() {
    private static StorageManager createStorageManagerMock() throws Exception {
        VolumeInfo internalVol = new VolumeInfo("private",
        VolumeInfo internalVol = new VolumeInfo("private",
                VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
                VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
        internalVol.path = sInternalVolPath;
        internalVol.path = sInternalVolPath;
@@ -93,6 +93,12 @@ public class PackageHelperTests extends AndroidTestCase {
        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
        Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
        Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
        Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
        Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
        Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalFile), Mockito.anyInt()))
                .thenReturn(sInternalSize);
        Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(adoptedFile), Mockito.anyInt()))
                .thenReturn(sAdoptedSize);
        Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(publicFile), Mockito.anyInt()))
                .thenReturn(sPublicSize);
        return storageManager;
        return storageManager;
    }
    }


@@ -156,18 +162,10 @@ public class PackageHelperTests extends AndroidTestCase {
        }
        }
    }
    }


    void failStr(String errMsg) {
        Log.w(TAG, "errMsg=" + errMsg);
        fail(errMsg);
    }

    void failStr(Exception e) {
        failStr(e.getMessage());
    }

    @Override
    @Override
    protected void setUp() throws Exception {
    protected void setUp() throws Exception {
        super.setUp();
        super.setUp();
        sStorageManager = createStorageManagerMock();
        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
        cleanupContainers();
        cleanupContainers();
    }
    }
@@ -175,17 +173,17 @@ public class PackageHelperTests extends AndroidTestCase {
    @Override
    @Override
    protected void tearDown() throws Exception {
    protected void tearDown() throws Exception {
        super.tearDown();
        super.tearDown();
        sStorageManager = null;
        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
        cleanupContainers();
        cleanupContainers();
    }
    }


    public void testMountAndPullSdCard() {
    public void testMountAndPullSdCard() throws Exception {
        try {
        fullId = PREFIX;
        fullId = PREFIX;
        fullId2 = PackageHelper.createSdDir(1024 * MB_IN_BYTES, fullId, "none",
        fullId2 = PackageHelper.createSdDir(1024 * MB_IN_BYTES, fullId, "none",
                android.os.Process.myUid(), true);
                android.os.Process.myUid(), true);


            Log.d(TAG,PackageHelper.getSdDir(fullId));
        Log.d(TAG, "getSdDir=" + PackageHelper.getSdDir(fullId));
        PackageHelper.unMountSdDir(fullId);
        PackageHelper.unMountSdDir(fullId);


        Runnable r1 = getMountRunnable();
        Runnable r1 = getMountRunnable();
@@ -194,9 +192,6 @@ public class PackageHelperTests extends AndroidTestCase {
        Thread thread2 = new Thread(r2);
        Thread thread2 = new Thread(r2);
        thread2.start();
        thread2.start();
        thread.start();
        thread.start();
        } catch (Exception e) {
            failStr(e);
        }
    }
    }


    public Runnable getMountRunnable() {
    public Runnable getMountRunnable() {
Loading