Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.os.storage;
     18 
     19 import static android.net.TrafficStats.MB_IN_BYTES;
     20 
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.os.Environment;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.Parcelable;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.provider.Settings;
     31 import android.util.Log;
     32 import android.util.SparseArray;
     33 
     34 import com.android.internal.util.Preconditions;
     35 
     36 import java.io.File;
     37 import java.io.IOException;
     38 import java.lang.ref.WeakReference;
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 import java.util.concurrent.atomic.AtomicInteger;
     42 
     43 /**
     44  * StorageManager is the interface to the systems storage service. The storage
     45  * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
     46  * <p>
     47  * OBBs contain a filesystem that maybe be encrypted on disk and mounted
     48  * on-demand from an application. OBBs are a good way of providing large amounts
     49  * of binary assets without packaging them into APKs as they may be multiple
     50  * gigabytes in size. However, due to their size, they're most likely stored in
     51  * a shared storage pool accessible from all programs. The system does not
     52  * guarantee the security of the OBB file itself: if any program modifies the
     53  * OBB, there is no guarantee that a read from that OBB will produce the
     54  * expected output.
     55  * <p>
     56  * Get an instance of this class by calling
     57  * {@link android.content.Context#getSystemService(java.lang.String)} with an
     58  * argument of {@link android.content.Context#STORAGE_SERVICE}.
     59  */
     60 public class StorageManager {
     61     private static final String TAG = "StorageManager";
     62 
     63     private final ContentResolver mResolver;
     64 
     65     /*
     66      * Our internal MountService binder reference
     67      */
     68     private final IMountService mMountService;
     69 
     70     /*
     71      * The looper target for callbacks
     72      */
     73     private final Looper mTgtLooper;
     74 
     75     /*
     76      * Target listener for binder callbacks
     77      */
     78     private MountServiceBinderListener mBinderListener;
     79 
     80     /*
     81      * List of our listeners
     82      */
     83     private List<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>();
     84 
     85     /*
     86      * Next available nonce
     87      */
     88     final private AtomicInteger mNextNonce = new AtomicInteger(0);
     89 
     90     private class MountServiceBinderListener extends IMountServiceListener.Stub {
     91         public void onUsbMassStorageConnectionChanged(boolean available) {
     92             final int size = mListeners.size();
     93             for (int i = 0; i < size; i++) {
     94                 mListeners.get(i).sendShareAvailabilityChanged(available);
     95             }
     96         }
     97 
     98         public void onStorageStateChanged(String path, String oldState, String newState) {
     99             final int size = mListeners.size();
    100             for (int i = 0; i < size; i++) {
    101                 mListeners.get(i).sendStorageStateChanged(path, oldState, newState);
    102             }
    103         }
    104     }
    105 
    106     /**
    107      * Binder listener for OBB action results.
    108      */
    109     private final ObbActionListener mObbActionListener = new ObbActionListener();
    110 
    111     private class ObbActionListener extends IObbActionListener.Stub {
    112         @SuppressWarnings("hiding")
    113         private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
    114 
    115         @Override
    116         public void onObbResult(String filename, int nonce, int status) {
    117             final ObbListenerDelegate delegate;
    118             synchronized (mListeners) {
    119                 delegate = mListeners.get(nonce);
    120                 if (delegate != null) {
    121                     mListeners.remove(nonce);
    122                 }
    123             }
    124 
    125             if (delegate != null) {
    126                 delegate.sendObbStateChanged(filename, status);
    127             }
    128         }
    129 
    130         public int addListener(OnObbStateChangeListener listener) {
    131             final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
    132 
    133             synchronized (mListeners) {
    134                 mListeners.put(delegate.nonce, delegate);
    135             }
    136 
    137             return delegate.nonce;
    138         }
    139     }
    140 
    141     private int getNextNonce() {
    142         return mNextNonce.getAndIncrement();
    143     }
    144 
    145     /**
    146      * Private class containing sender and receiver code for StorageEvents.
    147      */
    148     private class ObbListenerDelegate {
    149         private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
    150         private final Handler mHandler;
    151 
    152         private final int nonce;
    153 
    154         ObbListenerDelegate(OnObbStateChangeListener listener) {
    155             nonce = getNextNonce();
    156             mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
    157             mHandler = new Handler(mTgtLooper) {
    158                 @Override
    159                 public void handleMessage(Message msg) {
    160                     final OnObbStateChangeListener changeListener = getListener();
    161                     if (changeListener == null) {
    162                         return;
    163                     }
    164 
    165                     StorageEvent e = (StorageEvent) msg.obj;
    166 
    167                     if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) {
    168                         ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e;
    169                         changeListener.onObbStateChange(ev.path, ev.state);
    170                     } else {
    171                         Log.e(TAG, "Unsupported event " + msg.what);
    172                     }
    173                 }
    174             };
    175         }
    176 
    177         OnObbStateChangeListener getListener() {
    178             if (mObbEventListenerRef == null) {
    179                 return null;
    180             }
    181             return mObbEventListenerRef.get();
    182         }
    183 
    184         void sendObbStateChanged(String path, int state) {
    185             ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state);
    186             mHandler.sendMessage(e.getMessage());
    187         }
    188     }
    189 
    190     /**
    191      * Message sent during an OBB status change event.
    192      */
    193     private class ObbStateChangedStorageEvent extends StorageEvent {
    194         public final String path;
    195 
    196         public final int state;
    197 
    198         public ObbStateChangedStorageEvent(String path, int state) {
    199             super(EVENT_OBB_STATE_CHANGED);
    200             this.path = path;
    201             this.state = state;
    202         }
    203     }
    204 
    205     /**
    206      * Private base class for messages sent between the callback thread
    207      * and the target looper handler.
    208      */
    209     private class StorageEvent {
    210         static final int EVENT_UMS_CONNECTION_CHANGED = 1;
    211         static final int EVENT_STORAGE_STATE_CHANGED = 2;
    212         static final int EVENT_OBB_STATE_CHANGED = 3;
    213 
    214         private Message mMessage;
    215 
    216         public StorageEvent(int what) {
    217             mMessage = Message.obtain();
    218             mMessage.what = what;
    219             mMessage.obj = this;
    220         }
    221 
    222         public Message getMessage() {
    223             return mMessage;
    224         }
    225     }
    226 
    227     /**
    228      * Message sent on a USB mass storage connection change.
    229      */
    230     private class UmsConnectionChangedStorageEvent extends StorageEvent {
    231         public boolean available;
    232 
    233         public UmsConnectionChangedStorageEvent(boolean a) {
    234             super(EVENT_UMS_CONNECTION_CHANGED);
    235             available = a;
    236         }
    237     }
    238 
    239     /**
    240      * Message sent on volume state change.
    241      */
    242     private class StorageStateChangedStorageEvent extends StorageEvent {
    243         public String path;
    244         public String oldState;
    245         public String newState;
    246 
    247         public StorageStateChangedStorageEvent(String p, String oldS, String newS) {
    248             super(EVENT_STORAGE_STATE_CHANGED);
    249             path = p;
    250             oldState = oldS;
    251             newState = newS;
    252         }
    253     }
    254 
    255     /**
    256      * Private class containing sender and receiver code for StorageEvents.
    257      */
    258     private class ListenerDelegate {
    259         final StorageEventListener mStorageEventListener;
    260         private final Handler mHandler;
    261 
    262         ListenerDelegate(StorageEventListener listener) {
    263             mStorageEventListener = listener;
    264             mHandler = new Handler(mTgtLooper) {
    265                 @Override
    266                 public void handleMessage(Message msg) {
    267                     StorageEvent e = (StorageEvent) msg.obj;
    268 
    269                     if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
    270                         UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
    271                         mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
    272                     } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
    273                         StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
    274                         mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
    275                     } else {
    276                         Log.e(TAG, "Unsupported event " + msg.what);
    277                     }
    278                 }
    279             };
    280         }
    281 
    282         StorageEventListener getListener() {
    283             return mStorageEventListener;
    284         }
    285 
    286         void sendShareAvailabilityChanged(boolean available) {
    287             UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available);
    288             mHandler.sendMessage(e.getMessage());
    289         }
    290 
    291         void sendStorageStateChanged(String path, String oldState, String newState) {
    292             StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
    293             mHandler.sendMessage(e.getMessage());
    294         }
    295     }
    296 
    297     /** {@hide} */
    298     public static StorageManager from(Context context) {
    299         return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
    300     }
    301 
    302     /**
    303      * Constructs a StorageManager object through which an application can
    304      * can communicate with the systems mount service.
    305      *
    306      * @param tgtLooper The {@link android.os.Looper} which events will be received on.
    307      *
    308      * <p>Applications can get instance of this class by calling
    309      * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
    310      * of {@link android.content.Context#STORAGE_SERVICE}.
    311      *
    312      * @hide
    313      */
    314     public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
    315         mResolver = resolver;
    316         mTgtLooper = tgtLooper;
    317         mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
    318         if (mMountService == null) {
    319             Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
    320             return;
    321         }
    322     }
    323 
    324     /**
    325      * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
    326      *
    327      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
    328      *
    329      * @hide
    330      */
    331     public void registerListener(StorageEventListener listener) {
    332         if (listener == null) {
    333             return;
    334         }
    335 
    336         synchronized (mListeners) {
    337             if (mBinderListener == null ) {
    338                 try {
    339                     mBinderListener = new MountServiceBinderListener();
    340                     mMountService.registerListener(mBinderListener);
    341                 } catch (RemoteException rex) {
    342                     Log.e(TAG, "Register mBinderListener failed");
    343                     return;
    344                 }
    345             }
    346             mListeners.add(new ListenerDelegate(listener));
    347         }
    348     }
    349 
    350     /**
    351      * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
    352      *
    353      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
    354      *
    355      * @hide
    356      */
    357     public void unregisterListener(StorageEventListener listener) {
    358         if (listener == null) {
    359             return;
    360         }
    361 
    362         synchronized (mListeners) {
    363             final int size = mListeners.size();
    364             for (int i=0 ; i<size ; i++) {
    365                 ListenerDelegate l = mListeners.get(i);
    366                 if (l.getListener() == listener) {
    367                     mListeners.remove(i);
    368                     break;
    369                 }
    370             }
    371             if (mListeners.size() == 0 && mBinderListener != null) {
    372                 try {
    373                     mMountService.unregisterListener(mBinderListener);
    374                 } catch (RemoteException rex) {
    375                     Log.e(TAG, "Unregister mBinderListener failed");
    376                     return;
    377                 }
    378             }
    379        }
    380     }
    381 
    382     /**
    383      * Enables USB Mass Storage (UMS) on the device.
    384      *
    385      * @hide
    386      */
    387     public void enableUsbMassStorage() {
    388         try {
    389             mMountService.setUsbMassStorageEnabled(true);
    390         } catch (Exception ex) {
    391             Log.e(TAG, "Failed to enable UMS", ex);
    392         }
    393     }
    394 
    395     /**
    396      * Disables USB Mass Storage (UMS) on the device.
    397      *
    398      * @hide
    399      */
    400     public void disableUsbMassStorage() {
    401         try {
    402             mMountService.setUsbMassStorageEnabled(false);
    403         } catch (Exception ex) {
    404             Log.e(TAG, "Failed to disable UMS", ex);
    405         }
    406     }
    407 
    408     /**
    409      * Query if a USB Mass Storage (UMS) host is connected.
    410      * @return true if UMS host is connected.
    411      *
    412      * @hide
    413      */
    414     public boolean isUsbMassStorageConnected() {
    415         try {
    416             return mMountService.isUsbMassStorageConnected();
    417         } catch (Exception ex) {
    418             Log.e(TAG, "Failed to get UMS connection state", ex);
    419         }
    420         return false;
    421     }
    422 
    423     /**
    424      * Query if a USB Mass Storage (UMS) is enabled on the device.
    425      * @return true if UMS host is enabled.
    426      *
    427      * @hide
    428      */
    429     public boolean isUsbMassStorageEnabled() {
    430         try {
    431             return mMountService.isUsbMassStorageEnabled();
    432         } catch (RemoteException rex) {
    433             Log.e(TAG, "Failed to get UMS enable state", rex);
    434         }
    435         return false;
    436     }
    437 
    438     /**
    439      * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
    440      * specified, it is supplied to the mounting process to be used in any
    441      * encryption used in the OBB.
    442      * <p>
    443      * The OBB will remain mounted for as long as the StorageManager reference
    444      * is held by the application. As soon as this reference is lost, the OBBs
    445      * in use will be unmounted. The {@link OnObbStateChangeListener} registered
    446      * with this call will receive the success or failure of this operation.
    447      * <p>
    448      * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
    449      * file matches a package ID that is owned by the calling program's UID.
    450      * That is, shared UID applications can attempt to mount any other
    451      * application's OBB that shares its UID.
    452      *
    453      * @param rawPath the path to the OBB file
    454      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
    455      *            encryption was used on the OBB.
    456      * @param listener will receive the success or failure of the operation
    457      * @return whether the mount call was successfully queued or not
    458      */
    459     public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
    460         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
    461         Preconditions.checkNotNull(listener, "listener cannot be null");
    462 
    463         try {
    464             final String canonicalPath = new File(rawPath).getCanonicalPath();
    465             final int nonce = mObbActionListener.addListener(listener);
    466             mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
    467             return true;
    468         } catch (IOException e) {
    469             throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
    470         } catch (RemoteException e) {
    471             Log.e(TAG, "Failed to mount OBB", e);
    472         }
    473 
    474         return false;
    475     }
    476 
    477     /**
    478      * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
    479      * <code>force</code> flag is true, it will kill any application needed to
    480      * unmount the given OBB (even the calling application).
    481      * <p>
    482      * The {@link OnObbStateChangeListener} registered with this call will
    483      * receive the success or failure of this operation.
    484      * <p>
    485      * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
    486      * file matches a package ID that is owned by the calling program's UID.
    487      * That is, shared UID applications can obtain access to any other
    488      * application's OBB that shares its UID.
    489      * <p>
    490      *
    491      * @param rawPath path to the OBB file
    492      * @param force whether to kill any programs using this in order to unmount
    493      *            it
    494      * @param listener will receive the success or failure of the operation
    495      * @return whether the unmount call was successfully queued or not
    496      */
    497     public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
    498         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
    499         Preconditions.checkNotNull(listener, "listener cannot be null");
    500 
    501         try {
    502             final int nonce = mObbActionListener.addListener(listener);
    503             mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
    504             return true;
    505         } catch (RemoteException e) {
    506             Log.e(TAG, "Failed to mount OBB", e);
    507         }
    508 
    509         return false;
    510     }
    511 
    512     /**
    513      * Check whether an Opaque Binary Blob (OBB) is mounted or not.
    514      *
    515      * @param rawPath path to OBB image
    516      * @return true if OBB is mounted; false if not mounted or on error
    517      */
    518     public boolean isObbMounted(String rawPath) {
    519         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
    520 
    521         try {
    522             return mMountService.isObbMounted(rawPath);
    523         } catch (RemoteException e) {
    524             Log.e(TAG, "Failed to check if OBB is mounted", e);
    525         }
    526 
    527         return false;
    528     }
    529 
    530     /**
    531      * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
    532      * give you the path to where you can obtain access to the internals of the
    533      * OBB.
    534      *
    535      * @param rawPath path to OBB image
    536      * @return absolute path to mounted OBB image data or <code>null</code> if
    537      *         not mounted or exception encountered trying to read status
    538      */
    539     public String getMountedObbPath(String rawPath) {
    540         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
    541 
    542         try {
    543             return mMountService.getMountedObbPath(rawPath);
    544         } catch (RemoteException e) {
    545             Log.e(TAG, "Failed to find mounted path for OBB", e);
    546         }
    547 
    548         return null;
    549     }
    550 
    551     /**
    552      * Gets the state of a volume via its mountpoint.
    553      * @hide
    554      */
    555     public String getVolumeState(String mountPoint) {
    556          if (mMountService == null) return Environment.MEDIA_REMOVED;
    557         try {
    558             return mMountService.getVolumeState(mountPoint);
    559         } catch (RemoteException e) {
    560             Log.e(TAG, "Failed to get volume state", e);
    561             return null;
    562         }
    563     }
    564 
    565     /**
    566      * Returns list of all mountable volumes.
    567      * @hide
    568      */
    569     public StorageVolume[] getVolumeList() {
    570         if (mMountService == null) return new StorageVolume[0];
    571         try {
    572             Parcelable[] list = mMountService.getVolumeList();
    573             if (list == null) return new StorageVolume[0];
    574             int length = list.length;
    575             StorageVolume[] result = new StorageVolume[length];
    576             for (int i = 0; i < length; i++) {
    577                 result[i] = (StorageVolume)list[i];
    578             }
    579             return result;
    580         } catch (RemoteException e) {
    581             Log.e(TAG, "Failed to get volume list", e);
    582             return null;
    583         }
    584     }
    585 
    586     /**
    587      * Returns list of paths for all mountable volumes.
    588      * @hide
    589      */
    590     public String[] getVolumePaths() {
    591         StorageVolume[] volumes = getVolumeList();
    592         if (volumes == null) return null;
    593         int count = volumes.length;
    594         String[] paths = new String[count];
    595         for (int i = 0; i < count; i++) {
    596             paths[i] = volumes[i].getPath();
    597         }
    598         return paths;
    599     }
    600 
    601     /** {@hide} */
    602     public StorageVolume getPrimaryVolume() {
    603         return getPrimaryVolume(getVolumeList());
    604     }
    605 
    606     /** {@hide} */
    607     public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
    608         for (StorageVolume volume : volumes) {
    609             if (volume.isPrimary()) {
    610                 return volume;
    611             }
    612         }
    613         Log.w(TAG, "No primary storage defined");
    614         return null;
    615     }
    616 
    617     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
    618     private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
    619     private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
    620 
    621     /**
    622      * Return the number of available bytes until the given path is considered
    623      * running low on storage.
    624      *
    625      * @hide
    626      */
    627     public long getStorageBytesUntilLow(File path) {
    628         return path.getUsableSpace() - getStorageFullBytes(path);
    629     }
    630 
    631     /**
    632      * Return the number of available bytes at which the given path is
    633      * considered running low on storage.
    634      *
    635      * @hide
    636      */
    637     public long getStorageLowBytes(File path) {
    638         final long lowPercent = Settings.Global.getInt(mResolver,
    639                 Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
    640         final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
    641 
    642         final long maxLowBytes = Settings.Global.getLong(mResolver,
    643                 Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
    644 
    645         return Math.min(lowBytes, maxLowBytes);
    646     }
    647 
    648     /**
    649      * Return the number of available bytes at which the given path is
    650      * considered full.
    651      *
    652      * @hide
    653      */
    654     public long getStorageFullBytes(File path) {
    655         return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
    656                 DEFAULT_FULL_THRESHOLD_BYTES);
    657     }
    658 
    659     /// Consts to match the password types in cryptfs.h
    660     /** @hide */
    661     public static final int CRYPT_TYPE_PASSWORD = 0;
    662     /** @hide */
    663     public static final int CRYPT_TYPE_DEFAULT = 1;
    664     /** @hide */
    665     public static final int CRYPT_TYPE_PATTERN = 2;
    666     /** @hide */
    667     public static final int CRYPT_TYPE_PIN = 3;
    668 
    669     // Constants for the data available via MountService.getField.
    670     /** @hide */
    671     public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
    672     /** @hide */
    673     public static final String OWNER_INFO_KEY = "OwnerInfo";
    674     /** @hide */
    675     public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
    676 }
    677