blob: d1f37436e4ba3483513187c56d454652eabc471c [file] [log] [blame]
/*
* 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.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
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).
* <p>
* 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.
* <p>
* 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 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 FLAG_FOR_WRITE = 1 << 0;
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<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
private static class StorageEventListenerDelegate extends IMountServiceListener.Stub implements
Handler.Callback {
private static final int MSG_STORAGE_STATE_CHANGED = 1;
private static final int MSG_VOLUME_STATE_CHANGED = 2;
private static final int MSG_VOLUME_RECORD_CHANGED = 3;
private static final int MSG_VOLUME_FORGOTTEN = 4;
private static final int MSG_DISK_SCANNED = 5;
private static final int MSG_DISK_DESTROYED = 6;
final StorageEventListener mCallback;
final Handler mHandler;
public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
mCallback = callback;
mHandler = new Handler(looper, this);
}
@Override
public boolean handleMessage(Message msg) {
final SomeArgs args = (SomeArgs) msg.obj;
switch (msg.what) {
case MSG_STORAGE_STATE_CHANGED:
mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
(String) args.arg3);
args.recycle();
return true;
case MSG_VOLUME_STATE_CHANGED:
mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
args.recycle();
return true;
case MSG_VOLUME_RECORD_CHANGED:
mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
args.recycle();
return true;
case MSG_VOLUME_FORGOTTEN:
mCallback.onVolumeForgotten((String) args.arg1);
args.recycle();
return true;
case MSG_DISK_SCANNED:
mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
args.recycle();
return true;
case MSG_DISK_DESTROYED:
mCallback.onDiskDestroyed((DiskInfo) args.arg1);
args.recycle();
return true;
}
args.recycle();
return false;
}
@Override
public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
// Ignored
}
@Override
public void onStorageStateChanged(String path, String oldState, String newState) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = path;
args.arg2 = oldState;
args.arg3 = newState;
mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
}
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = vol;
args.argi2 = oldState;
args.argi3 = newState;
mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
}
@Override
public void onVolumeRecordChanged(VolumeRecord rec) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = rec;
mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
}
@Override
public void onVolumeForgotten(String fsUuid) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = fsUuid;
mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
}
@Override
public void onDiskScanned(DiskInfo disk, int volumeCount) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = disk;
args.argi2 = volumeCount;
mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
}
@Override
public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = disk;
mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
}
}
/**
* Binder listener for OBB action results.
*/
private final ObbActionListener mObbActionListener = new ObbActionListener();
private class ObbActionListener extends IObbActionListener.Stub {
@SuppressWarnings("hiding")
private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
@Override
public void onObbResult(String filename, int nonce, int status) {
final ObbListenerDelegate delegate;
synchronized (mListeners) {
delegate = mListeners.get(nonce);
if (delegate != null) {
mListeners.remove(nonce);
}
}
if (delegate != null) {
delegate.sendObbStateChanged(filename, status);
}
}
public int addListener(OnObbStateChangeListener listener) {
final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
synchronized (mListeners) {
mListeners.put(delegate.nonce, delegate);
}
return delegate.nonce;
}
}
private int getNextNonce() {
return mNextNonce.getAndIncrement();
}
/**
* Private class containing sender and receiver code for StorageEvents.
*/
private class ObbListenerDelegate {
private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
private final Handler mHandler;
private final int nonce;
ObbListenerDelegate(OnObbStateChangeListener listener) {
nonce = getNextNonce();
mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
mHandler = new Handler(mLooper) {
@Override
public void handleMessage(Message msg) {
final OnObbStateChangeListener changeListener = getListener();
if (changeListener == null) {
return;
}
changeListener.onObbStateChange((String) msg.obj, msg.arg1);
}
};
}
OnObbStateChangeListener getListener() {
if (mObbEventListenerRef == null) {
return null;
}
return mObbEventListenerRef.get();
}
void sendObbStateChanged(String path, int state) {
mHandler.obtainMessage(0, state, 0, path).sendToTarget();
}
}
/** {@hide} */
@Deprecated
public static StorageManager from(Context context) {
return context.getSystemService(StorageManager.class);
}
/**
* Constructs a StorageManager object through which an application can
* can communicate with the systems mount service.
*
* @param tgtLooper The {@link android.os.Looper} which events will be received on.
*
* <p>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.rethrowAsRuntimeException();
}
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<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
final StorageEventListenerDelegate delegate = i.next();
if (delegate.mCallback == listener) {
try {
mMountService.unregisterListener(delegate);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
i.remove();
}
}
}
}
/**
* Enables USB Mass Storage (UMS) on the device.
*
* @hide
*/
@Deprecated
public void enableUsbMassStorage() {
}
/**
* Disables USB Mass Storage (UMS) on the device.
*
* @hide
*/
@Deprecated
public void disableUsbMassStorage() {
}
/**
* Query if a USB Mass Storage (UMS) host is connected.
* @return true if UMS host is connected.
*
* @hide
*/
@Deprecated
public boolean isUsbMassStorageConnected() {
return false;
}
/**
* Query if a USB Mass Storage (UMS) is enabled on the device.
* @return true if UMS host is enabled.
*
* @hide
*/
@Deprecated
public boolean isUsbMassStorageEnabled() {
return false;
}
/**
* Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
* specified, it is supplied to the mounting process to be used in any
* encryption used in the OBB.
* <p>
* 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.
* <p>
* <em>Note:</em> 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 <code>null</code> 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) {
Log.e(TAG, "Failed to mount OBB", e);
}
return false;
}
/**
* Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
* <code>force</code> flag is true, it will kill any application needed to
* unmount the given OBB (even the calling application).
* <p>
* The {@link OnObbStateChangeListener} registered with this call will
* receive the success or failure of this operation.
* <p>
* <em>Note:</em> 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.
* <p>
*
* @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) {
Log.e(TAG, "Failed to mount OBB", e);
}
return false;
}
/**
* 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) {
Log.e(TAG, "Failed to check if OBB is mounted", e);
}
return false;
}
/**
* 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 <code>null</code> 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) {
Log.e(TAG, "Failed to find mounted path for OBB", e);
}
return null;
}
/** {@hide} */
public @NonNull List<DiskInfo> getDisks() {
try {
return Arrays.asList(mMountService.getDisks());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public @Nullable DiskInfo findDiskById(String id) {
Preconditions.checkNotNull(id);
// TODO; go directly to service to make this faster
for (DiskInfo disk : getDisks()) {
if (Objects.equals(disk.id, id)) {
return disk;
}
}
return null;
}
/** {@hide} */
public @Nullable VolumeInfo findVolumeById(String id) {
Preconditions.checkNotNull(id);
// TODO; go directly to service to make this faster
for (VolumeInfo vol : getVolumes()) {
if (Objects.equals(vol.id, id)) {
return vol;
}
}
return null;
}
/** {@hide} */
public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
Preconditions.checkNotNull(fsUuid);
// TODO; go directly to service to make this faster
for (VolumeInfo vol : getVolumes()) {
if (Objects.equals(vol.fsUuid, fsUuid)) {
return vol;
}
}
return null;
}
/** {@hide} */
public @Nullable VolumeRecord findRecordByUuid(String fsUuid) {
Preconditions.checkNotNull(fsUuid);
// TODO; go directly to service to make this faster
for (VolumeRecord rec : getVolumeRecords()) {
if (Objects.equals(rec.fsUuid, fsUuid)) {
return rec;
}
}
return null;
}
/** {@hide} */
public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) {
if (emulatedVol != null) {
return findVolumeById(emulatedVol.getId().replace("emulated", "private"));
} else {
return null;
}
}
/** {@hide} */
public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
if (privateVol != null) {
return findVolumeById(privateVol.getId().replace("private", "emulated"));
} else {
return null;
}
}
/** {@hide} */
public @Nullable VolumeInfo findVolumeByQualifiedUuid(String volumeUuid) {
if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
return findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
} else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
return getPrimaryPhysicalVolume();
} else {
return findVolumeByUuid(volumeUuid);
}
}
/** {@hide} */
public @NonNull List<VolumeInfo> getVolumes() {
try {
return Arrays.asList(mMountService.getVolumes(0));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public @NonNull List<VolumeInfo> getWritablePrivateVolumes() {
try {
final ArrayList<VolumeInfo> res = new ArrayList<>();
for (VolumeInfo vol : mMountService.getVolumes(0)) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
res.add(vol);
}
}
return res;
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public @NonNull List<VolumeRecord> getVolumeRecords() {
try {
return Arrays.asList(mMountService.getVolumeRecords(0));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
if (vol == null) return null;
// Nickname always takes precedence when defined
if (!TextUtils.isEmpty(vol.fsUuid)) {
final VolumeRecord rec = findRecordByUuid(vol.fsUuid);
if (rec != null && !TextUtils.isEmpty(rec.nickname)) {
return rec.nickname;
}
}
if (!TextUtils.isEmpty(vol.getDescription())) {
return vol.getDescription();
}
if (vol.disk != null) {
return vol.disk.getDescription();
}
return null;
}
/** {@hide} */
public @Nullable VolumeInfo getPrimaryPhysicalVolume() {
final List<VolumeInfo> vols = getVolumes();
for (VolumeInfo vol : vols) {
if (vol.isPrimaryPhysical()) {
return vol;
}
}
return null;
}
/** {@hide} */
public void mount(String volId) {
try {
mMountService.mount(volId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void unmount(String volId) {
try {
mMountService.unmount(volId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void format(String volId) {
try {
mMountService.format(volId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public long benchmark(String volId) {
try {
return mMountService.benchmark(volId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void partitionPublic(String diskId) {
try {
mMountService.partitionPublic(diskId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void partitionPrivate(String diskId) {
try {
mMountService.partitionPrivate(diskId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void partitionMixed(String diskId, int ratio) {
try {
mMountService.partitionMixed(diskId, ratio);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void wipeAdoptableDisks() {
// We only wipe devices in "adoptable" locations, which are in a
// long-term stable slot/location on the device, where apps have a
// reasonable chance of storing sensitive data. (Apps need to go through
// SAF to write to transient volumes.)
final List<DiskInfo> disks = getDisks();
for (DiskInfo disk : disks) {
final String diskId = disk.getId();
if (disk.isAdoptable()) {
Slog.d(TAG, "Found adoptable " + diskId + "; wiping");
try {
// TODO: switch to explicit wipe command when we have it,
// for now rely on the fact that vfat format does a wipe
mMountService.partitionPublic(diskId);
} catch (Exception e) {
Slog.w(TAG, "Failed to wipe " + diskId + ", but soldiering onward", e);
}
} else {
Slog.d(TAG, "Ignorning non-adoptable disk " + disk.getId());
}
}
}
/** {@hide} */
public void setVolumeNickname(String fsUuid, String nickname) {
try {
mMountService.setVolumeNickname(fsUuid, nickname);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void setVolumeInited(String fsUuid, boolean inited) {
try {
mMountService.setVolumeUserFlags(fsUuid, inited ? VolumeRecord.USER_FLAG_INITED : 0,
VolumeRecord.USER_FLAG_INITED);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void setVolumeSnoozed(String fsUuid, boolean snoozed) {
try {
mMountService.setVolumeUserFlags(fsUuid, snoozed ? VolumeRecord.USER_FLAG_SNOOZED : 0,
VolumeRecord.USER_FLAG_SNOOZED);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public void forgetVolume(String fsUuid) {
try {
mMountService.forgetVolume(fsUuid);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/**
* This is not the API you're looking for.
*
* @see PackageManager#getPrimaryStorageCurrentVolume()
* @hide
*/
public String getPrimaryStorageUuid() {
try {
return mMountService.getPrimaryStorageUuid();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/**
* This is not the API you're looking for.
*
* @see PackageManager#movePrimaryStorage(VolumeInfo)
* @hide
*/
public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
try {
mMountService.setPrimaryStorageUuid(volumeUuid, callback);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/** {@hide} */
public @Nullable StorageVolume getStorageVolume(File file) {
return getStorageVolume(getVolumeList(), file);
}
/** {@hide} */
public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
return getStorageVolume(getVolumeList(userId, 0), file);
}
/** {@hide} */
private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
try {
file = file.getCanonicalFile();
} catch (IOException ignored) {
return null;
}
for (StorageVolume volume : volumes) {
File volumeFile = volume.getPathFile();
try {
volumeFile = volumeFile.getCanonicalFile();
} catch (IOException ignored) {
continue;
}
if (FileUtils.contains(volumeFile, file)) {
return volume;
}
}
return null;
}
/**
* Gets the state of a volume via its mountpoint.
* @hide
*/
@Deprecated
public @NonNull String getVolumeState(String mountPoint) {
final StorageVolume vol = getStorageVolume(new File(mountPoint));
if (vol != null) {
return vol.getState();
} else {
return Environment.MEDIA_UNKNOWN;
}
}
/** {@hide} */
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, userId);
if (uid <= 0) {
return new StorageVolume[0];
}
return mountService.getVolumeList(uid, packageName, flags);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
/**
* 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;
}
/** {@hide} */
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 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 ignored) {
}
return path;
}
/// 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";
}