/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os.storage; import static android.net.TrafficStats.MB_IN_BYTES; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.content.ContentResolver; import android.content.Context; import android.content.pm.IPackageMoveObserver; import android.content.pm.PackageManager; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** * StorageManager is the interface to the systems storage service. The storage * manager handles storage-related items such as Opaque Binary Blobs (OBBs). *
* OBBs contain a filesystem that maybe be encrypted on disk and mounted * on-demand from an application. OBBs are a good way of providing large amounts * of binary assets without packaging them into APKs as they may be multiple * gigabytes in size. However, due to their size, they're most likely stored in * a shared storage pool accessible from all programs. The system does not * guarantee the security of the OBB file itself: if any program modifies the * OBB, there is no guarantee that a read from that OBB will produce the * expected output. *
* Get an instance of this class by calling
* {@link android.content.Context#getSystemService(java.lang.String)} with an
* argument of {@link android.content.Context#STORAGE_SERVICE}.
*/
public class StorageManager {
private static final String TAG = "StorageManager";
/** {@hide} */
public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
/** {@hide} */
public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
/** {@hide} */
public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
/** {@hide} */
public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
/** {@hide} */
public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
/** {@hide} */
public static final int DEBUG_FORCE_ADOPTABLE = 1 << 0;
/** {@hide} */
public static final int DEBUG_EMULATE_FBE = 1 << 1;
// NOTE: keep in sync with installd
/** {@hide} */
public static final int FLAG_STORAGE_DE = 1 << 0;
/** {@hide} */
public static final int FLAG_STORAGE_CE = 1 << 1;
/** {@hide} */
public static final int FLAG_FOR_WRITE = 1 << 8;
private final Context mContext;
private final ContentResolver mResolver;
private final IMountService mMountService;
private final Looper mLooper;
private final AtomicInteger mNextNonce = new AtomicInteger(0);
private final ArrayList Applications can get instance of this class by calling
* {@link android.content.Context#getSystemService(java.lang.String)} with an argument
* of {@link android.content.Context#STORAGE_SERVICE}.
*
* @hide
*/
public StorageManager(Context context, Looper looper) {
mContext = context;
mResolver = context.getContentResolver();
mLooper = looper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
throw new IllegalStateException("Failed to find running mount service");
}
}
/**
* Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
* @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
*
* @hide
*/
public void registerListener(StorageEventListener listener) {
synchronized (mDelegates) {
final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
mLooper);
try {
mMountService.registerListener(delegate);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mDelegates.add(delegate);
}
}
/**
* Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
* @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
*
* @hide
*/
public void unregisterListener(StorageEventListener listener) {
synchronized (mDelegates) {
for (Iterator
* The OBB will remain mounted for as long as the StorageManager reference
* is held by the application. As soon as this reference is lost, the OBBs
* in use will be unmounted. The {@link OnObbStateChangeListener} registered
* with this call will receive the success or failure of this operation.
*
* Note: you can only mount OBB files for which the OBB tag on the
* file matches a package ID that is owned by the calling program's UID.
* That is, shared UID applications can attempt to mount any other
* application's OBB that shares its UID.
*
* @param rawPath the path to the OBB file
* @param key secret used to encrypt the OBB; may be
* The {@link OnObbStateChangeListener} registered with this call will
* receive the success or failure of this operation.
*
* Note: you can only mount OBB files for which the OBB tag on the
* file matches a package ID that is owned by the calling program's UID.
* That is, shared UID applications can obtain access to any other
* application's OBB that shares its UID.
*
*
* @param rawPath path to the OBB file
* @param force whether to kill any programs using this in order to unmount
* it
* @param listener will receive the success or failure of the operation
* @return whether the unmount call was successfully queued or not
*/
public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
Preconditions.checkNotNull(listener, "listener cannot be null");
try {
final int nonce = mObbActionListener.addListener(listener);
mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Check whether an Opaque Binary Blob (OBB) is mounted or not.
*
* @param rawPath path to OBB image
* @return true if OBB is mounted; false if not mounted or on error
*/
public boolean isObbMounted(String rawPath) {
Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
try {
return mMountService.isObbMounted(rawPath);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Check the mounted path of an Opaque Binary Blob (OBB) file. This will
* give you the path to where you can obtain access to the internals of the
* OBB.
*
* @param rawPath path to OBB image
* @return absolute path to mounted OBB image data or It always contains the primary storage volume, plus any additional external volume(s)
* available in the device, such as SD cards or attached USB drives.
*/
public @NonNull StorageVolume[] getVolumeList() {
return getVolumeList(mContext.getUserId(), 0);
}
/** {@hide} */
public static @NonNull StorageVolume[] getVolumeList(int userId, int flags) {
final IMountService mountService = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
try {
String packageName = ActivityThread.currentOpPackageName();
if (packageName == null) {
// Package name can be null if the activity thread is running but the app
// hasn't bound yet. In this case we fall back to the first package in the
// current UID. This works for runtime permissions as permission state is
// per UID and permission realted app ops are updated for all UID packages.
String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid(
android.os.Process.myUid());
if (packageNames == null || packageNames.length <= 0) {
return new StorageVolume[0];
}
packageName = packageNames[0];
}
final int uid = ActivityThread.getPackageManager().getPackageUid(packageName,
PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
if (uid <= 0) {
return new StorageVolume[0];
}
return mountService.getVolumeList(uid, packageName, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns list of paths for all mountable volumes.
* @hide
*/
@Deprecated
public @NonNull String[] getVolumePaths() {
StorageVolume[] volumes = getVolumeList();
int count = volumes.length;
String[] paths = new String[count];
for (int i = 0; i < count; i++) {
paths[i] = volumes[i].getPath();
}
return paths;
}
/**
* Gets the primary shared/external storage volume available to the current user.
*/
public @NonNull StorageVolume getPrimaryVolume() {
return getPrimaryVolume(getVolumeList());
}
/** {@hide} */
public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
for (StorageVolume volume : volumes) {
if (volume.isPrimary()) {
return volume;
}
}
throw new IllegalStateException("Missing primary storage");
}
/** {@hide} */
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
/**
* Return the number of available bytes until the given path is considered
* running low on storage.
*
* @hide
*/
public long getStorageBytesUntilLow(File path) {
return path.getUsableSpace() - getStorageFullBytes(path);
}
/**
* Return the number of available bytes at which the given path is
* considered running low on storage.
*
* @hide
*/
public long getStorageLowBytes(File path) {
final long lowPercent = Settings.Global.getInt(mResolver,
Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
final long maxLowBytes = Settings.Global.getLong(mResolver,
Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
return Math.min(lowBytes, maxLowBytes);
}
/**
* Return the number of available bytes at which the given path is
* considered full.
*
* @hide
*/
public long getStorageFullBytes(File path) {
return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
DEFAULT_FULL_THRESHOLD_BYTES);
}
/** {@hide} */
public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
try {
mMountService.createUserKey(userId, serialNumber, ephemeral);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
public void destroyUserKey(int userId) {
try {
mMountService.destroyUserKey(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
try {
mMountService.unlockUserKey(userId, serialNumber, token, secret);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
public void lockUserKey(int userId) {
try {
mMountService.lockUserKey(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
try {
mMountService.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
public boolean isUserKeyUnlocked(int userId) {
try {
return mMountService.isUserKeyUnlocked(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return if data stored at the given path will be encrypted while at rest.
* This can help apps avoid the overhead of double-encrypting data.
*/
public boolean isEncrypted(File file) {
if (FileUtils.contains(Environment.getDataDirectory(), file)) {
return isEncrypted();
} else if (FileUtils.contains(Environment.getExpandDirectory(), file)) {
return true;
}
// TODO: extend to support shared storage
return false;
}
/** {@hide}
* Is this device encryptable or already encrypted?
* @return true for encryptable or encrypted
* false not encrypted and not encryptable
*/
public static boolean isEncryptable() {
final String state = SystemProperties.get("ro.crypto.state", "unsupported");
return !"unsupported".equalsIgnoreCase(state);
}
/** {@hide}
* Is this device already encrypted?
* @return true for encrypted. (Implies isEncryptable() == true)
* false not encrypted
*/
public static boolean isEncrypted() {
final String state = SystemProperties.get("ro.crypto.state", "");
return "encrypted".equalsIgnoreCase(state);
}
/** {@hide}
* Is this device file encrypted?
* @return true for file encrypted. (Implies isEncrypted() == true)
* false not encrypted or block encrypted
*/
public static boolean isFileEncryptedNativeOnly() {
if (!isEncrypted()) {
return false;
}
final String status = SystemProperties.get("ro.crypto.type", "");
return "file".equalsIgnoreCase(status);
}
/** {@hide}
* Is this device block encrypted?
* @return true for block encrypted. (Implies isEncrypted() == true)
* false not encrypted or file encrypted
*/
public static boolean isBlockEncrypted() {
if (!isEncrypted()) {
return false;
}
final String status = SystemProperties.get("ro.crypto.type", "");
return "block".equalsIgnoreCase(status);
}
/** {@hide}
* Is this device block encrypted with credentials?
* @return true for crediential block encrypted.
* (Implies isBlockEncrypted() == true)
* false not encrypted, file encrypted or default block encrypted
*/
public static boolean isNonDefaultBlockEncrypted() {
if (!isBlockEncrypted()) {
return false;
}
try {
IMountService mountService = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
return mountService.getPasswordType() != CRYPT_TYPE_DEFAULT;
} catch (RemoteException e) {
Log.e(TAG, "Error getting encryption type");
return false;
}
}
/** {@hide}
* Is this device in the process of being block encrypted?
* @return true for encrypting.
* false otherwise
* Whether device isEncrypted at this point is undefined
* Note that only system services and CryptKeeper will ever see this return
* true - no app will ever be launched in this state.
* Also note that this state will not change without a teardown of the
* framework, so no service needs to check for changes during their lifespan
*/
public static boolean isBlockEncrypting() {
final String state = SystemProperties.get("vold.encrypt_progress", "");
return !"".equalsIgnoreCase(state);
}
/** {@hide}
* Is this device non default block encrypted and in the process of
* prompting for credentials?
* @return true for prompting for credentials.
* (Implies isNonDefaultBlockEncrypted() == true)
* false otherwise
* Note that only system services and CryptKeeper will ever see this return
* true - no app will ever be launched in this state.
* Also note that this state will not change without a teardown of the
* framework, so no service needs to check for changes during their lifespan
*/
public static boolean inCryptKeeperBounce() {
final String status = SystemProperties.get("vold.decrypt");
return "trigger_restart_min_framework".equals(status);
}
/** {@hide} */
public static boolean isFileEncryptedEmulatedOnly() {
return SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false);
}
/** {@hide}
* Is this device running in a file encrypted mode, either native or emulated?
* @return true for file encrypted, false otherwise
*/
public static boolean isFileEncryptedNativeOrEmulated() {
return isFileEncryptedNativeOnly()
|| isFileEncryptedEmulatedOnly();
}
/** {@hide} */
public static File maybeTranslateEmulatedPathToInternal(File path) {
final IMountService mountService = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
try {
final VolumeInfo[] vols = mountService.getVolumes(0);
for (VolumeInfo vol : vols) {
if ((vol.getType() == VolumeInfo.TYPE_EMULATED
|| vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isMountedReadable()) {
final File internalPath = FileUtils.rewriteAfterRename(vol.getPath(),
vol.getInternalPath(), path);
if (internalPath != null && internalPath.exists()) {
return internalPath;
}
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return path;
}
/** {@hide} */
public ParcelFileDescriptor mountAppFuse(String name) {
try {
return mMountService.mountAppFuse(name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/// Consts to match the password types in cryptfs.h
/** @hide */
public static final int CRYPT_TYPE_PASSWORD = 0;
/** @hide */
public static final int CRYPT_TYPE_DEFAULT = 1;
/** @hide */
public static final int CRYPT_TYPE_PATTERN = 2;
/** @hide */
public static final int CRYPT_TYPE_PIN = 3;
// Constants for the data available via MountService.getField.
/** @hide */
public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
/** @hide */
public static final String OWNER_INFO_KEY = "OwnerInfo";
/** @hide */
public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
/** @hide */
public static final String PASSWORD_VISIBLE_KEY = "PasswordVisible";
}
key
is
* specified, it is supplied to the mounting process to be used in any
* encryption used in the OBB.
* null
if no
* encryption was used on the OBB.
* @param listener will receive the success or failure of the operation
* @return whether the mount call was successfully queued or not
*/
public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
Preconditions.checkNotNull(listener, "listener cannot be null");
try {
final String canonicalPath = new File(rawPath).getCanonicalPath();
final int nonce = mObbActionListener.addListener(listener);
mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
return true;
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
* force
flag is true, it will kill any application needed to
* unmount the given OBB (even the calling application).
* null
if
* not mounted or exception encountered trying to read status
*/
public String getMountedObbPath(String rawPath) {
Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
try {
return mMountService.getMountedObbPath(rawPath);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
public @NonNull List