/* * 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.content.ContentResolver; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; 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.List; 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";
private final ContentResolver mResolver;
/*
* Our internal MountService binder reference
*/
private final IMountService mMountService;
/*
* The looper target for callbacks
*/
private final Looper mTgtLooper;
/*
* Target listener for binder callbacks
*/
private MountServiceBinderListener mBinderListener;
/*
* List of our listeners
*/
private List 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(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
mResolver = resolver;
mTgtLooper = tgtLooper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
return;
}
}
/**
* Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
* @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
*
* @hide
*/
public void registerListener(StorageEventListener listener) {
if (listener == null) {
return;
}
synchronized (mListeners) {
if (mBinderListener == null ) {
try {
mBinderListener = new MountServiceBinderListener();
mMountService.registerListener(mBinderListener);
} catch (RemoteException rex) {
Log.e(TAG, "Register mBinderListener failed");
return;
}
}
mListeners.add(new ListenerDelegate(listener));
}
}
/**
* Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
* @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
*
* @hide
*/
public void unregisterListener(StorageEventListener listener) {
if (listener == null) {
return;
}
synchronized (mListeners) {
final int size = mListeners.size();
for (int i=0 ; i
* 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) {
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 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) {
Log.e(TAG, "Failed to mount OBB", e);
}
return false;
}
/**
* 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) {
Log.e(TAG, "Failed to find mounted path for OBB", e);
}
return null;
}
/**
* Gets the state of a volume via its mountpoint.
* @hide
*/
public String getVolumeState(String mountPoint) {
if (mMountService == null) return Environment.MEDIA_REMOVED;
try {
return mMountService.getVolumeState(mountPoint);
} catch (RemoteException e) {
Log.e(TAG, "Failed to get volume state", e);
return null;
}
}
/**
* Returns list of all mountable volumes.
* @hide
*/
public StorageVolume[] getVolumeList() {
if (mMountService == null) return new StorageVolume[0];
try {
Parcelable[] list = mMountService.getVolumeList();
if (list == null) return new StorageVolume[0];
int length = list.length;
StorageVolume[] result = new StorageVolume[length];
for (int i = 0; i < length; i++) {
result[i] = (StorageVolume)list[i];
}
return result;
} catch (RemoteException e) {
Log.e(TAG, "Failed to get volume list", e);
return null;
}
}
/**
* Returns list of paths for all mountable volumes.
* @hide
*/
public String[] getVolumePaths() {
StorageVolume[] volumes = getVolumeList();
if (volumes == null) return null;
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 StorageVolume getPrimaryVolume() {
return getPrimaryVolume(getVolumeList());
}
/** {@hide} */
public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
for (StorageVolume volume : volumes) {
if (volume.isPrimary()) {
return volume;
}
}
Log.w(TAG, "No primary storage defined");
return null;
}
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);
}
/// 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";
}