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