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