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
This commit is contained in:
Jeff Sharkey
2014-01-10 16:27:19 -08:00
parent ac6b332764
commit 4ca728c064
5 changed files with 150 additions and 84 deletions

View File

@@ -18453,10 +18453,13 @@ package android.os {
method public static java.io.File getExternalStorageDirectory();
method public static java.io.File getExternalStoragePublicDirectory(java.lang.String);
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.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(java.io.File);
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_DCIM;
field public static java.lang.String DIRECTORY_DOCUMENTS;

View File

@@ -728,7 +728,7 @@ public abstract class Context {
* Returned paths may be {@code null} if a storage device is unavailable.
*
* @see #getExternalFilesDir(String)
* @see Environment#getStorageState(File)
* @see Environment#getExternalStorageState(File)
*/
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.
*
* @see #getObbDir()
* @see Environment#getStorageState(File)
* @see Environment#getExternalStorageState(File)
*/
public abstract File[] getObbDirs();
@@ -895,7 +895,7 @@ public abstract class Context {
* Returned paths may be {@code null} if a storage device is unavailable.
*
* @see #getExternalCacheDir()
* @see Environment#getStorageState(File)
* @see Environment#getExternalStorageState(File)
*/
public abstract File[] getExternalCacheDirs();

View File

@@ -16,14 +16,13 @@
package android.os;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.google.android.collect.Lists;
import java.io.File;
@@ -66,33 +65,6 @@ public class Environment {
private static UserEnvironment sCurrentUser;
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 {
initForCurrentUser();
}
@@ -101,10 +73,6 @@ public class Environment {
public static void initForCurrentUser() {
final int userId = UserHandle.myUserId();
sCurrentUser = new UserEnvironment(userId);
synchronized (sLock) {
sPrimaryVolume = null;
}
}
/** {@hide} */
@@ -603,28 +571,28 @@ public class Environment {
* Unknown storage state, such as when a path isn't backed by known storage
* media.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
public static final String MEDIA_UNKNOWN = "unknown";
/**
* Storage state if the media is not present.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
public static final String MEDIA_REMOVED = "removed";
/**
* Storage state if the media is present but not mounted.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
public static final String MEDIA_UNMOUNTED = "unmounted";
/**
* Storage state if the media is present and being disk-checked.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
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
* unsupported filesystem.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
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
* read/write access.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
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
* read-only access.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
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
* mass storage.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
public static final String MEDIA_SHARED = "shared";
/**
* 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";
@@ -671,7 +639,7 @@ public class Environment {
* Storage state if the media is present but cannot be mounted. Typically
* this happens if the file system on the media is corrupted.
*
* @see #getStorageState(File)
* @see #getExternalStorageState(File)
*/
public static final String MEDIA_UNMOUNTABLE = "unmountable";
@@ -687,7 +655,15 @@ public class Environment {
*/
public static String getExternalStorageState() {
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_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
public static String getStorageState(File path) {
final String rawPath;
try {
rawPath = path.getCanonicalPath();
} catch (IOException e) {
Log.w(TAG, "Failed to resolve target path: " + e);
return Environment.MEDIA_UNKNOWN;
}
try {
public static String getExternalStorageState(File path) {
final StorageVolume volume = getStorageVolume(path);
if (volume != null) {
final IMountService mountService = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
final StorageVolume[] volumes = mountService.getVolumeList();
for (StorageVolume volume : volumes) {
if (rawPath.startsWith(volume.getPath())) {
return mountService.getVolumeState(volume.getPath());
}
try {
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;
}
/**
* 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() {
final StorageVolume primary = getPrimaryVolume();
return (primary != null && primary.isRemovable());
if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
return isExternalStorageRemovable(externalDir);
}
/**
* Returns whether the device has an external storage device which is
* emulated. If true, the device does not have real external storage, and the directory
* returned by {@link #getExternalStorageDirectory()} will be allocated using a portion of
* the internal storage system.
* Returns whether the storage device that provides the given path is
* removable.
*
* <p>Certain system services, such as the package manager, use this
* to determine where to install an application.
* @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.
* @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
* {@link android.app.admin.DevicePolicyManager#setStorageEncryption(
* android.content.ComponentName, boolean)} for additional details.
* @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName,
* boolean)
*/
public static boolean isExternalStorageEmulated() {
final StorageVolume primary = getPrimaryVolume();
return (primary != null && primary.isEmulated());
if (isStorageDisabled()) return false;
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) {
@@ -815,6 +813,32 @@ public class Environment {
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
* translated backing path hosted on internal storage. This bypasses any

View File

@@ -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);
}
}

View File

@@ -26,6 +26,8 @@ import android.test.suitebuilder.annotation.MediumTest;
import com.google.android.collect.Sets;
import libcore.io.IoUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -33,8 +35,6 @@ import java.io.FileWriter;
import java.util.Arrays;
import java.util.HashSet;
import libcore.io.IoUtils;
@MediumTest
public class FileUtilsTest extends AndroidTestCase {
private static final String TEST_DATA =
@@ -112,6 +112,23 @@ public class FileUtilsTest extends AndroidTestCase {
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 {
FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
assertDirContents();