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.RemoteException;
     23 import android.os.ServiceManager;
     24 import android.util.Log;
     25 import android.util.Slog;
     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         private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
    103 
    104         @Override
    105         public void onObbResult(String filename, int nonce, int status) throws RemoteException {
    106             final ObbListenerDelegate delegate;
    107             synchronized (mListeners) {
    108                 delegate = mListeners.get(nonce);
    109                 if (delegate != null) {
    110                     mListeners.remove(nonce);
    111                 }
    112             }
    113 
    114             if (delegate != null) {
    115                 delegate.sendObbStateChanged(filename, status);
    116             }
    117         }
    118 
    119         public int addListener(OnObbStateChangeListener listener) {
    120             final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
    121 
    122             synchronized (mListeners) {
    123                 mListeners.put(delegate.nonce, delegate);
    124             }
    125 
    126             return delegate.nonce;
    127         }
    128     }
    129 
    130     private int getNextNonce() {
    131         return mNextNonce.getAndIncrement();
    132     }
    133 
    134     /**
    135      * Private class containing sender and receiver code for StorageEvents.
    136      */
    137     private class ObbListenerDelegate {
    138         private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
    139         private final Handler mHandler;
    140 
    141         private final int nonce;
    142 
    143         ObbListenerDelegate(OnObbStateChangeListener listener) {
    144             nonce = getNextNonce();
    145             mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
    146             mHandler = new Handler(mTgtLooper) {
    147                 @Override
    148                 public void handleMessage(Message msg) {
    149                     final OnObbStateChangeListener listener = getListener();
    150                     if (listener == null) {
    151                         return;
    152                     }
    153 
    154                     StorageEvent e = (StorageEvent) msg.obj;
    155 
    156                     if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) {
    157                         ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e;
    158                         listener.onObbStateChange(ev.path, ev.state);
    159                     } else {
    160                         Log.e(TAG, "Unsupported event " + msg.what);
    161                     }
    162                 }
    163             };
    164         }
    165 
    166         OnObbStateChangeListener getListener() {
    167             if (mObbEventListenerRef == null) {
    168                 return null;
    169             }
    170             return mObbEventListenerRef.get();
    171         }
    172 
    173         void sendObbStateChanged(String path, int state) {
    174             ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state);
    175             mHandler.sendMessage(e.getMessage());
    176         }
    177     }
    178 
    179     /**
    180      * Message sent during an OBB status change event.
    181      */
    182     private class ObbStateChangedStorageEvent extends StorageEvent {
    183         public final String path;
    184 
    185         public final int state;
    186 
    187         public ObbStateChangedStorageEvent(String path, int state) {
    188             super(EVENT_OBB_STATE_CHANGED);
    189             this.path = path;
    190             this.state = state;
    191         }
    192     }
    193 
    194     /**
    195      * Private base class for messages sent between the callback thread
    196      * and the target looper handler.
    197      */
    198     private class StorageEvent {
    199         static final int EVENT_UMS_CONNECTION_CHANGED = 1;
    200         static final int EVENT_STORAGE_STATE_CHANGED = 2;
    201         static final int EVENT_OBB_STATE_CHANGED = 3;
    202 
    203         private Message mMessage;
    204 
    205         public StorageEvent(int what) {
    206             mMessage = Message.obtain();
    207             mMessage.what = what;
    208             mMessage.obj = this;
    209         }
    210 
    211         public Message getMessage() {
    212             return mMessage;
    213         }
    214     }
    215 
    216     /**
    217      * Message sent on a USB mass storage connection change.
    218      */
    219     private class UmsConnectionChangedStorageEvent extends StorageEvent {
    220         public boolean available;
    221 
    222         public UmsConnectionChangedStorageEvent(boolean a) {
    223             super(EVENT_UMS_CONNECTION_CHANGED);
    224             available = a;
    225         }
    226     }
    227 
    228     /**
    229      * Message sent on volume state change.
    230      */
    231     private class StorageStateChangedStorageEvent extends StorageEvent {
    232         public String path;
    233         public String oldState;
    234         public String newState;
    235 
    236         public StorageStateChangedStorageEvent(String p, String oldS, String newS) {
    237             super(EVENT_STORAGE_STATE_CHANGED);
    238             path = p;
    239             oldState = oldS;
    240             newState = newS;
    241         }
    242     }
    243 
    244     /**
    245      * Private class containing sender and receiver code for StorageEvents.
    246      */
    247     private class ListenerDelegate {
    248         final StorageEventListener mStorageEventListener;
    249         private final Handler mHandler;
    250 
    251         ListenerDelegate(StorageEventListener listener) {
    252             mStorageEventListener = listener;
    253             mHandler = new Handler(mTgtLooper) {
    254                 @Override
    255                 public void handleMessage(Message msg) {
    256                     StorageEvent e = (StorageEvent) msg.obj;
    257 
    258                     if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
    259                         UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
    260                         mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
    261                     } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
    262                         StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
    263                         mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
    264                     } else {
    265                         Log.e(TAG, "Unsupported event " + msg.what);
    266                     }
    267                 }
    268             };
    269         }
    270 
    271         StorageEventListener getListener() {
    272             return mStorageEventListener;
    273         }
    274 
    275         void sendShareAvailabilityChanged(boolean available) {
    276             UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available);
    277             mHandler.sendMessage(e.getMessage());
    278         }
    279 
    280         void sendStorageStateChanged(String path, String oldState, String newState) {
    281             StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
    282             mHandler.sendMessage(e.getMessage());
    283         }
    284     }
    285 
    286     /**
    287      * Constructs a StorageManager object through which an application can
    288      * can communicate with the systems mount service.
    289      *
    290      * @param tgtLooper The {@android.os.Looper} which events will be received on.
    291      *
    292      * <p>Applications can get instance of this class by calling
    293      * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
    294      * of {@link android.content.Context#STORAGE_SERVICE}.
    295      *
    296      * @hide
    297      */
    298     public StorageManager(Looper tgtLooper) throws RemoteException {
    299         mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
    300         if (mMountService == null) {
    301             Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
    302             return;
    303         }
    304         mTgtLooper = tgtLooper;
    305         mBinderListener = new MountServiceBinderListener();
    306         mMountService.registerListener(mBinderListener);
    307     }
    308 
    309 
    310     /**
    311      * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
    312      *
    313      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
    314      *
    315      * @hide
    316      */
    317     public void registerListener(StorageEventListener listener) {
    318         if (listener == null) {
    319             return;
    320         }
    321 
    322         synchronized (mListeners) {
    323             mListeners.add(new ListenerDelegate(listener));
    324         }
    325     }
    326 
    327     /**
    328      * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
    329      *
    330      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
    331      *
    332      * @hide
    333      */
    334     public void unregisterListener(StorageEventListener listener) {
    335         if (listener == null) {
    336             return;
    337         }
    338 
    339         synchronized (mListeners) {
    340             final int size = mListeners.size();
    341             for (int i=0 ; i<size ; i++) {
    342                 ListenerDelegate l = mListeners.get(i);
    343                 if (l.getListener() == listener) {
    344                     mListeners.remove(i);
    345                     break;
    346                 }
    347             }
    348         }
    349     }
    350 
    351     /**
    352      * Enables USB Mass Storage (UMS) on the device.
    353      *
    354      * @hide
    355      */
    356     public void enableUsbMassStorage() {
    357         try {
    358             mMountService.setUsbMassStorageEnabled(true);
    359         } catch (Exception ex) {
    360             Log.e(TAG, "Failed to enable UMS", ex);
    361         }
    362     }
    363 
    364     /**
    365      * Disables USB Mass Storage (UMS) on the device.
    366      *
    367      * @hide
    368      */
    369     public void disableUsbMassStorage() {
    370         try {
    371             mMountService.setUsbMassStorageEnabled(false);
    372         } catch (Exception ex) {
    373             Log.e(TAG, "Failed to disable UMS", ex);
    374         }
    375     }
    376 
    377     /**
    378      * Query if a USB Mass Storage (UMS) host is connected.
    379      * @return true if UMS host is connected.
    380      *
    381      * @hide
    382      */
    383     public boolean isUsbMassStorageConnected() {
    384         try {
    385             return mMountService.isUsbMassStorageConnected();
    386         } catch (Exception ex) {
    387             Log.e(TAG, "Failed to get UMS connection state", ex);
    388         }
    389         return false;
    390     }
    391 
    392     /**
    393      * Query if a USB Mass Storage (UMS) is enabled on the device.
    394      * @return true if UMS host is enabled.
    395      *
    396      * @hide
    397      */
    398     public boolean isUsbMassStorageEnabled() {
    399         try {
    400             return mMountService.isUsbMassStorageEnabled();
    401         } catch (RemoteException rex) {
    402             Log.e(TAG, "Failed to get UMS enable state", rex);
    403         }
    404         return false;
    405     }
    406 
    407     /**
    408      * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
    409      * specified, it is supplied to the mounting process to be used in any
    410      * encryption used in the OBB.
    411      * <p>
    412      * The OBB will remain mounted for as long as the StorageManager reference
    413      * is held by the application. As soon as this reference is lost, the OBBs
    414      * in use will be unmounted. The {@link OnObbStateChangeListener} registered
    415      * with this call will receive the success or failure of this operation.
    416      * <p>
    417      * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
    418      * file matches a package ID that is owned by the calling program's UID.
    419      * That is, shared UID applications can attempt to mount any other
    420      * application's OBB that shares its UID.
    421      *
    422      * @param filename the path to the OBB file
    423      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
    424      *            encryption was used on the OBB.
    425      * @param listener will receive the success or failure of the operation
    426      * @return whether the mount call was successfully queued or not
    427      */
    428     public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
    429         if (filename == null) {
    430             throw new IllegalArgumentException("filename cannot be null");
    431         }
    432 
    433         if (listener == null) {
    434             throw new IllegalArgumentException("listener cannot be null");
    435         }
    436 
    437         try {
    438             final int nonce = mObbActionListener.addListener(listener);
    439             mMountService.mountObb(filename, key, mObbActionListener, nonce);
    440             return true;
    441         } catch (RemoteException e) {
    442             Log.e(TAG, "Failed to mount OBB", e);
    443         }
    444 
    445         return false;
    446     }
    447 
    448     /**
    449      * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
    450      * <code>force</code> flag is true, it will kill any application needed to
    451      * unmount the given OBB (even the calling application).
    452      * <p>
    453      * The {@link OnObbStateChangeListener} registered with this call will
    454      * receive the success or failure of this operation.
    455      * <p>
    456      * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
    457      * file matches a package ID that is owned by the calling program's UID.
    458      * That is, shared UID applications can obtain access to any other
    459      * application's OBB that shares its UID.
    460      * <p>
    461      *
    462      * @param filename path to the OBB file
    463      * @param force whether to kill any programs using this in order to unmount
    464      *            it
    465      * @param listener will receive the success or failure of the operation
    466      * @return whether the unmount call was successfully queued or not
    467      */
    468     public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
    469         if (filename == null) {
    470             throw new IllegalArgumentException("filename cannot be null");
    471         }
    472 
    473         if (listener == null) {
    474             throw new IllegalArgumentException("listener cannot be null");
    475         }
    476 
    477         try {
    478             final int nonce = mObbActionListener.addListener(listener);
    479             mMountService.unmountObb(filename, force, mObbActionListener, nonce);
    480             return true;
    481         } catch (RemoteException e) {
    482             Log.e(TAG, "Failed to mount OBB", e);
    483         }
    484 
    485         return false;
    486     }
    487 
    488     /**
    489      * Check whether an Opaque Binary Blob (OBB) is mounted or not.
    490      *
    491      * @param filename path to OBB image
    492      * @return true if OBB is mounted; false if not mounted or on error
    493      */
    494     public boolean isObbMounted(String filename) {
    495         if (filename == null) {
    496             throw new IllegalArgumentException("filename cannot be null");
    497         }
    498 
    499         try {
    500             return mMountService.isObbMounted(filename);
    501         } catch (RemoteException e) {
    502             Log.e(TAG, "Failed to check if OBB is mounted", e);
    503         }
    504 
    505         return false;
    506     }
    507 
    508     /**
    509      * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
    510      * give you the path to where you can obtain access to the internals of the
    511      * OBB.
    512      *
    513      * @param filename path to OBB image
    514      * @return absolute path to mounted OBB image data or <code>null</code> if
    515      *         not mounted or exception encountered trying to read status
    516      */
    517     public String getMountedObbPath(String filename) {
    518         if (filename == null) {
    519             throw new IllegalArgumentException("filename cannot be null");
    520         }
    521 
    522         try {
    523             return mMountService.getMountedObbPath(filename);
    524         } catch (RemoteException e) {
    525             Log.e(TAG, "Failed to find mounted path for OBB", e);
    526         }
    527 
    528         return null;
    529     }
    530 }
    531