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

Commit 4ca728c0 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Detect removable and emulated secondary storage.

Also rename existing secondary storage API to match naming
convention in rest of class.

Bug: 11536709
Change-Id: I2684c817de4982b414893d2d9927a21e3f171d53
parent ac6b3327
Loading
Loading
Loading
Loading
+4 −1
Original line number Original line Diff line number Diff line
@@ -18453,10 +18453,13 @@ package android.os {
    method public static java.io.File getExternalStorageDirectory();
    method public static java.io.File getExternalStorageDirectory();
    method public static java.io.File getExternalStoragePublicDirectory(java.lang.String);
    method public static java.io.File getExternalStoragePublicDirectory(java.lang.String);
    method public static java.lang.String getExternalStorageState();
    method public static java.lang.String getExternalStorageState();
    method public static java.lang.String getExternalStorageState(java.io.File);
    method public static java.io.File getRootDirectory();
    method public static java.io.File getRootDirectory();
    method public static java.lang.String getStorageState(java.io.File);
    method public static deprecated java.lang.String getStorageState(java.io.File);
    method public static boolean isExternalStorageEmulated();
    method public static boolean isExternalStorageEmulated();
    method public static boolean isExternalStorageEmulated(java.io.File);
    method public static boolean isExternalStorageRemovable();
    method public static boolean isExternalStorageRemovable();
    method public static boolean isExternalStorageRemovable(java.io.File);
    field public static java.lang.String DIRECTORY_ALARMS;
    field public static java.lang.String DIRECTORY_ALARMS;
    field public static java.lang.String DIRECTORY_DCIM;
    field public static java.lang.String DIRECTORY_DCIM;
    field public static java.lang.String DIRECTORY_DOCUMENTS;
    field public static java.lang.String DIRECTORY_DOCUMENTS;
+3 −3
Original line number Original line Diff line number Diff line
@@ -728,7 +728,7 @@ public abstract class Context {
     * Returned paths may be {@code null} if a storage device is unavailable.
     * Returned paths may be {@code null} if a storage device is unavailable.
     *
     *
     * @see #getExternalFilesDir(String)
     * @see #getExternalFilesDir(String)
     * @see Environment#getStorageState(File)
     * @see Environment#getExternalStorageState(File)
     */
     */
    public abstract File[] getExternalFilesDirs(String type);
    public abstract File[] getExternalFilesDirs(String type);


@@ -792,7 +792,7 @@ public abstract class Context {
     * Returned paths may be {@code null} if a storage device is unavailable.
     * Returned paths may be {@code null} if a storage device is unavailable.
     *
     *
     * @see #getObbDir()
     * @see #getObbDir()
     * @see Environment#getStorageState(File)
     * @see Environment#getExternalStorageState(File)
     */
     */
    public abstract File[] getObbDirs();
    public abstract File[] getObbDirs();


@@ -895,7 +895,7 @@ public abstract class Context {
     * Returned paths may be {@code null} if a storage device is unavailable.
     * Returned paths may be {@code null} if a storage device is unavailable.
     *
     *
     * @see #getExternalCacheDir()
     * @see #getExternalCacheDir()
     * @see Environment#getStorageState(File)
     * @see Environment#getExternalStorageState(File)
     */
     */
    public abstract File[] getExternalCacheDirs();
    public abstract File[] getExternalCacheDirs();


+102 −78
Original line number Original line Diff line number Diff line
@@ -16,14 +16,13 @@


package android.os;
package android.os;


import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Context;
import android.os.storage.IMountService;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;


import com.android.internal.annotations.GuardedBy;
import com.google.android.collect.Lists;
import com.google.android.collect.Lists;


import java.io.File;
import java.io.File;
@@ -66,33 +65,6 @@ public class Environment {
    private static UserEnvironment sCurrentUser;
    private static UserEnvironment sCurrentUser;
    private static boolean sUserRequired;
    private static boolean sUserRequired;


    private static final Object sLock = new Object();

    @GuardedBy("sLock")
    private static volatile StorageVolume sPrimaryVolume;

    private static StorageVolume getPrimaryVolume() {
        if (SystemProperties.getBoolean("config.disable_storage", false)) {
            return null;
        }

        if (sPrimaryVolume == null) {
            synchronized (sLock) {
                if (sPrimaryVolume == null) {
                    try {
                        IMountService mountService = IMountService.Stub.asInterface(ServiceManager
                                .getService("mount"));
                        final StorageVolume[] volumes = mountService.getVolumeList();
                        sPrimaryVolume = StorageManager.getPrimaryVolume(volumes);
                    } catch (Exception e) {
                        Log.e(TAG, "couldn't talk to MountService", e);
                    }
                }
            }
        }
        return sPrimaryVolume;
    }

    static {
    static {
        initForCurrentUser();
        initForCurrentUser();
    }
    }
@@ -101,10 +73,6 @@ public class Environment {
    public static void initForCurrentUser() {
    public static void initForCurrentUser() {
        final int userId = UserHandle.myUserId();
        final int userId = UserHandle.myUserId();
        sCurrentUser = new UserEnvironment(userId);
        sCurrentUser = new UserEnvironment(userId);

        synchronized (sLock) {
            sPrimaryVolume = null;
        }
    }
    }


    /** {@hide} */
    /** {@hide} */
@@ -603,28 +571,28 @@ public class Environment {
     * Unknown storage state, such as when a path isn't backed by known storage
     * Unknown storage state, such as when a path isn't backed by known storage
     * media.
     * media.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_UNKNOWN = "unknown";
    public static final String MEDIA_UNKNOWN = "unknown";


    /**
    /**
     * Storage state if the media is not present.
     * Storage state if the media is not present.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_REMOVED = "removed";
    public static final String MEDIA_REMOVED = "removed";


    /**
    /**
     * Storage state if the media is present but not mounted.
     * Storage state if the media is present but not mounted.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_UNMOUNTED = "unmounted";
    public static final String MEDIA_UNMOUNTED = "unmounted";


    /**
    /**
     * Storage state if the media is present and being disk-checked.
     * Storage state if the media is present and being disk-checked.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_CHECKING = "checking";
    public static final String MEDIA_CHECKING = "checking";


@@ -632,7 +600,7 @@ public class Environment {
     * Storage state if the media is present but is blank or is using an
     * Storage state if the media is present but is blank or is using an
     * unsupported filesystem.
     * unsupported filesystem.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_NOFS = "nofs";
    public static final String MEDIA_NOFS = "nofs";


@@ -640,7 +608,7 @@ public class Environment {
     * Storage state if the media is present and mounted at its mount point with
     * Storage state if the media is present and mounted at its mount point with
     * read/write access.
     * read/write access.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_MOUNTED = "mounted";
    public static final String MEDIA_MOUNTED = "mounted";


@@ -648,7 +616,7 @@ public class Environment {
     * Storage state if the media is present and mounted at its mount point with
     * Storage state if the media is present and mounted at its mount point with
     * read-only access.
     * read-only access.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
    public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";


@@ -656,14 +624,14 @@ public class Environment {
     * Storage state if the media is present not mounted, and shared via USB
     * Storage state if the media is present not mounted, and shared via USB
     * mass storage.
     * mass storage.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_SHARED = "shared";
    public static final String MEDIA_SHARED = "shared";


    /**
    /**
     * Storage state if the media was removed before it was unmounted.
     * Storage state if the media was removed before it was unmounted.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_BAD_REMOVAL = "bad_removal";
    public static final String MEDIA_BAD_REMOVAL = "bad_removal";


@@ -671,7 +639,7 @@ public class Environment {
     * Storage state if the media is present but cannot be mounted. Typically
     * Storage state if the media is present but cannot be mounted. Typically
     * this happens if the file system on the media is corrupted.
     * this happens if the file system on the media is corrupted.
     *
     *
     * @see #getStorageState(File)
     * @see #getExternalStorageState(File)
     */
     */
    public static final String MEDIA_UNMOUNTABLE = "unmountable";
    public static final String MEDIA_UNMOUNTABLE = "unmountable";


@@ -687,7 +655,15 @@ public class Environment {
     */
     */
    public static String getExternalStorageState() {
    public static String getExternalStorageState() {
        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
        return getStorageState(externalDir);
        return getExternalStorageState(externalDir);
    }

    /**
     * @deprecated use {@link #getExternalStorageState(File)}
     */
    @Deprecated
    public static String getStorageState(File path) {
        return getExternalStorageState(path);
    }
    }


    /**
    /**
@@ -700,59 +676,81 @@ public class Environment {
     *         {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
     *         {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
     *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
     *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
     */
     */
    public static String getStorageState(File path) {
    public static String getExternalStorageState(File path) {
        final String rawPath;
        final StorageVolume volume = getStorageVolume(path);
        try {
        if (volume != null) {
            rawPath = path.getCanonicalPath();
        } catch (IOException e) {
            Log.w(TAG, "Failed to resolve target path: " + e);
            return Environment.MEDIA_UNKNOWN;
        }

        try {
            final IMountService mountService = IMountService.Stub.asInterface(
            final IMountService mountService = IMountService.Stub.asInterface(
                    ServiceManager.getService("mount"));
                    ServiceManager.getService("mount"));
            final StorageVolume[] volumes = mountService.getVolumeList();
            try {
            for (StorageVolume volume : volumes) {
                if (rawPath.startsWith(volume.getPath())) {
                return mountService.getVolumeState(volume.getPath());
                return mountService.getVolumeState(volume.getPath());
                }
            }
            } catch (RemoteException e) {
            } catch (RemoteException e) {
            Log.w(TAG, "Failed to find external storage state: " + e);
            }
            }
        }

        return Environment.MEDIA_UNKNOWN;
        return Environment.MEDIA_UNKNOWN;
    }
    }


    /**
    /**
     * Returns whether the primary "external" storage device is removable.
     * Returns whether the primary "external" storage device is removable.
     * If true is returned, this device is for example an SD card that the
     * user can remove.  If false is returned, the storage is built into
     * the device and can not be physically removed.
     *
     *
     * <p>See {@link #getExternalStorageDirectory()} for more information.
     * @return true if the storage device can be removed (such as an SD card),
     *         or false if the storage device is built in and cannot be
     *         physically removed.
     */
     */
    public static boolean isExternalStorageRemovable() {
    public static boolean isExternalStorageRemovable() {
        final StorageVolume primary = getPrimaryVolume();
        if (isStorageDisabled()) return false;
        return (primary != null && primary.isRemovable());
        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
        return isExternalStorageRemovable(externalDir);
    }
    }


    /**
    /**
     * Returns whether the device has an external storage device which is
     * Returns whether the storage device that provides the given path is
     * emulated. If true, the device does not have real external storage, and the directory
     * removable.
     * returned by {@link #getExternalStorageDirectory()} will be allocated using a portion of
     * the internal storage system.
     *
     *
     * <p>Certain system services, such as the package manager, use this
     * @return true if the storage device can be removed (such as an SD card),
     * to determine where to install an application.
     *         or false if the storage device is built in and cannot be
     *         physically removed.
     * @throws IllegalArgumentException if the path is not a valid storage
     *             device.
     */
    public static boolean isExternalStorageRemovable(File path) {
        final StorageVolume volume = getStorageVolume(path);
        if (volume != null) {
            return volume.isRemovable();
        } else {
            throw new IllegalArgumentException("Failed to find storage device at " + path);
        }
    }

    /**
     * Returns whether the primary "external" storage device is emulated. If
     * true, data stored on this device will be stored on a portion of the
     * internal storage system.
     *
     *
     * <p>Emulated external storage may also be encrypted - see
     * @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName,
     * {@link android.app.admin.DevicePolicyManager#setStorageEncryption(
     *      boolean)
     * android.content.ComponentName, boolean)} for additional details.
     */
     */
    public static boolean isExternalStorageEmulated() {
    public static boolean isExternalStorageEmulated() {
        final StorageVolume primary = getPrimaryVolume();
        if (isStorageDisabled()) return false;
        return (primary != null && primary.isEmulated());
        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
        return isExternalStorageEmulated(externalDir);
    }

    /**
     * Returns whether the storage device that provides the given path is
     * emulated. If true, data stored on this device will be stored on a portion
     * of the internal storage system.
     *
     * @throws IllegalArgumentException if the path is not a valid storage
     *             device.
     */
    public static boolean isExternalStorageEmulated(File path) {
        final StorageVolume volume = getStorageVolume(path);
        if (volume != null) {
            return volume.isEmulated();
        } else {
            throw new IllegalArgumentException("Failed to find storage device at " + path);
        }
    }
    }


    static File getDirectory(String variableName, String defaultPath) {
    static File getDirectory(String variableName, String defaultPath) {
@@ -815,6 +813,32 @@ public class Environment {
        return cur;
        return cur;
    }
    }


    private static boolean isStorageDisabled() {
        return SystemProperties.getBoolean("config.disable_storage", false);
    }

    private static StorageVolume getStorageVolume(File path) {
        try {
            path = path.getCanonicalFile();
        } catch (IOException e) {
            return null;
        }

        try {
            final IMountService mountService = IMountService.Stub.asInterface(
                    ServiceManager.getService("mount"));
            final StorageVolume[] volumes = mountService.getVolumeList();
            for (StorageVolume volume : volumes) {
                if (FileUtils.contains(volume.getPathFile(), path)) {
                    return volume;
                }
            }
        } catch (RemoteException e) {
        }

        return null;
    }

    /**
    /**
     * If the given path exists on emulated external storage, return the
     * If the given path exists on emulated external storage, return the
     * translated backing path hosted on internal storage. This bypasses any
     * translated backing path hosted on internal storage. This bypasses any
+22 −0
Original line number Original line Diff line number Diff line
@@ -355,4 +355,26 @@ public class FileUtils {
            }
            }
        }
        }
    }
    }

    /**
     * Test if a file lives under the given directory, either as a direct child
     * or a distant grandchild.
     * <p>
     * Both files <em>must</em> have been resolved using
     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
     * attacks.
     */
    public static boolean contains(File dir, File file) {
        String dirPath = dir.getPath();
        String filePath = file.getPath();

        if (dirPath.equals(filePath)) {
            return true;
        }

        if (!dirPath.endsWith("/")) {
            dirPath += "/";
        }
        return filePath.startsWith(dirPath);
    }
}
}
+19 −2
Original line number Original line Diff line number Diff line
@@ -26,6 +26,8 @@ import android.test.suitebuilder.annotation.MediumTest;


import com.google.android.collect.Sets;
import com.google.android.collect.Sets;


import libcore.io.IoUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
@@ -33,8 +35,6 @@ import java.io.FileWriter;
import java.util.Arrays;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HashSet;


import libcore.io.IoUtils;

@MediumTest
@MediumTest
public class FileUtilsTest extends AndroidTestCase {
public class FileUtilsTest extends AndroidTestCase {
    private static final String TEST_DATA =
    private static final String TEST_DATA =
@@ -112,6 +112,23 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
        assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
    }
    }


    public void testContains() throws Exception {
        assertTrue(FileUtils.contains(new File("/"), new File("/moo.txt")));
        assertTrue(FileUtils.contains(new File("/"), new File("/")));

        assertTrue(FileUtils.contains(new File("/sdcard"), new File("/sdcard")));
        assertTrue(FileUtils.contains(new File("/sdcard/"), new File("/sdcard/")));

        assertTrue(FileUtils.contains(new File("/sdcard"), new File("/sdcard/moo.txt")));
        assertTrue(FileUtils.contains(new File("/sdcard/"), new File("/sdcard/moo.txt")));

        assertFalse(FileUtils.contains(new File("/sdcard"), new File("/moo.txt")));
        assertFalse(FileUtils.contains(new File("/sdcard/"), new File("/moo.txt")));

        assertFalse(FileUtils.contains(new File("/sdcard"), new File("/sdcard.txt")));
        assertFalse(FileUtils.contains(new File("/sdcard/"), new File("/sdcard.txt")));
    }

    public void testDeleteOlderEmptyDir() throws Exception {
    public void testDeleteOlderEmptyDir() throws Exception {
        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
        assertDirContents();
        assertDirContents();