StorageManager.java revision 6614bb657929c70dad988fb14b4b91f3b9d4f7fc
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
17package android.os.storage;
18
19import android.os.Environment;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.Parcelable;
24import android.os.RemoteException;
25import android.os.ServiceManager;
26import android.util.Log;
27import android.util.SparseArray;
28
29import java.lang.ref.WeakReference;
30import java.util.ArrayList;
31import java.util.List;
32import 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
52public class StorageManager
53{
54    private static final String TAG = "StorageManager";
55
56    /*
57     * Our internal MountService binder reference
58     */
59    final 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 {@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    }
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            if (mBinderListener == null ) {
324                try {
325                    mBinderListener = new MountServiceBinderListener();
326                    mMountService.registerListener(mBinderListener);
327                } catch (RemoteException rex) {
328                    Log.e(TAG, "Register mBinderListener failed");
329                    return;
330                }
331            }
332            mListeners.add(new ListenerDelegate(listener));
333        }
334    }
335
336    /**
337     * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
338     *
339     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
340     *
341     * @hide
342     */
343    public void unregisterListener(StorageEventListener listener) {
344        if (listener == null) {
345            return;
346        }
347
348        synchronized (mListeners) {
349            final int size = mListeners.size();
350            for (int i=0 ; i<size ; i++) {
351                ListenerDelegate l = mListeners.get(i);
352                if (l.getListener() == listener) {
353                    mListeners.remove(i);
354                    break;
355                }
356            }
357            if (mListeners.size() == 0 && mBinderListener != null) {
358                try {
359                    mMountService.unregisterListener(mBinderListener);
360                } catch (RemoteException rex) {
361                    Log.e(TAG, "Unregister mBinderListener failed");
362                    return;
363                }
364            }
365       }
366    }
367
368    /**
369     * Enables USB Mass Storage (UMS) on the device.
370     *
371     * @hide
372     */
373    public void enableUsbMassStorage() {
374        try {
375            mMountService.setUsbMassStorageEnabled(true);
376        } catch (Exception ex) {
377            Log.e(TAG, "Failed to enable UMS", ex);
378        }
379    }
380
381    /**
382     * Disables USB Mass Storage (UMS) on the device.
383     *
384     * @hide
385     */
386    public void disableUsbMassStorage() {
387        try {
388            mMountService.setUsbMassStorageEnabled(false);
389        } catch (Exception ex) {
390            Log.e(TAG, "Failed to disable UMS", ex);
391        }
392    }
393
394    /**
395     * Query if a USB Mass Storage (UMS) host is connected.
396     * @return true if UMS host is connected.
397     *
398     * @hide
399     */
400    public boolean isUsbMassStorageConnected() {
401        try {
402            return mMountService.isUsbMassStorageConnected();
403        } catch (Exception ex) {
404            Log.e(TAG, "Failed to get UMS connection state", ex);
405        }
406        return false;
407    }
408
409    /**
410     * Query if a USB Mass Storage (UMS) is enabled on the device.
411     * @return true if UMS host is enabled.
412     *
413     * @hide
414     */
415    public boolean isUsbMassStorageEnabled() {
416        try {
417            return mMountService.isUsbMassStorageEnabled();
418        } catch (RemoteException rex) {
419            Log.e(TAG, "Failed to get UMS enable state", rex);
420        }
421        return false;
422    }
423
424    /**
425     * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
426     * specified, it is supplied to the mounting process to be used in any
427     * encryption used in the OBB.
428     * <p>
429     * The OBB will remain mounted for as long as the StorageManager reference
430     * is held by the application. As soon as this reference is lost, the OBBs
431     * in use will be unmounted. The {@link OnObbStateChangeListener} registered
432     * with this call will receive the success or failure of this operation.
433     * <p>
434     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
435     * file matches a package ID that is owned by the calling program's UID.
436     * That is, shared UID applications can attempt to mount any other
437     * application's OBB that shares its UID.
438     *
439     * @param filename the path to the OBB file
440     * @param key secret used to encrypt the OBB; may be <code>null</code> if no
441     *            encryption was used on the OBB.
442     * @param listener will receive the success or failure of the operation
443     * @return whether the mount call was successfully queued or not
444     */
445    public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
446        if (filename == null) {
447            throw new IllegalArgumentException("filename cannot be null");
448        }
449
450        if (listener == null) {
451            throw new IllegalArgumentException("listener cannot be null");
452        }
453
454        try {
455            final int nonce = mObbActionListener.addListener(listener);
456            mMountService.mountObb(filename, key, mObbActionListener, nonce);
457            return true;
458        } catch (RemoteException e) {
459            Log.e(TAG, "Failed to mount OBB", e);
460        }
461
462        return false;
463    }
464
465    /**
466     * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
467     * <code>force</code> flag is true, it will kill any application needed to
468     * unmount the given OBB (even the calling application).
469     * <p>
470     * The {@link OnObbStateChangeListener} registered with this call will
471     * receive the success or failure of this operation.
472     * <p>
473     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
474     * file matches a package ID that is owned by the calling program's UID.
475     * That is, shared UID applications can obtain access to any other
476     * application's OBB that shares its UID.
477     * <p>
478     *
479     * @param filename path to the OBB file
480     * @param force whether to kill any programs using this in order to unmount
481     *            it
482     * @param listener will receive the success or failure of the operation
483     * @return whether the unmount call was successfully queued or not
484     */
485    public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
486        if (filename == null) {
487            throw new IllegalArgumentException("filename cannot be null");
488        }
489
490        if (listener == null) {
491            throw new IllegalArgumentException("listener cannot be null");
492        }
493
494        try {
495            final int nonce = mObbActionListener.addListener(listener);
496            mMountService.unmountObb(filename, force, mObbActionListener, nonce);
497            return true;
498        } catch (RemoteException e) {
499            Log.e(TAG, "Failed to mount OBB", e);
500        }
501
502        return false;
503    }
504
505    /**
506     * Check whether an Opaque Binary Blob (OBB) is mounted or not.
507     *
508     * @param filename path to OBB image
509     * @return true if OBB is mounted; false if not mounted or on error
510     */
511    public boolean isObbMounted(String filename) {
512        if (filename == null) {
513            throw new IllegalArgumentException("filename cannot be null");
514        }
515
516        try {
517            return mMountService.isObbMounted(filename);
518        } catch (RemoteException e) {
519            Log.e(TAG, "Failed to check if OBB is mounted", e);
520        }
521
522        return false;
523    }
524
525    /**
526     * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
527     * give you the path to where you can obtain access to the internals of the
528     * OBB.
529     *
530     * @param filename path to OBB image
531     * @return absolute path to mounted OBB image data or <code>null</code> if
532     *         not mounted or exception encountered trying to read status
533     */
534    public String getMountedObbPath(String filename) {
535        if (filename == null) {
536            throw new IllegalArgumentException("filename cannot be null");
537        }
538
539        try {
540            return mMountService.getMountedObbPath(filename);
541        } catch (RemoteException e) {
542            Log.e(TAG, "Failed to find mounted path for OBB", e);
543        }
544
545        return null;
546    }
547
548    /**
549     * Gets the state of a volume via its mountpoint.
550     * @hide
551     */
552    public String getVolumeState(String mountPoint) {
553         if (mMountService == null) return Environment.MEDIA_REMOVED;
554        try {
555            return mMountService.getVolumeState(mountPoint);
556        } catch (RemoteException e) {
557            Log.e(TAG, "Failed to get volume state", e);
558            return null;
559        }
560    }
561
562    /**
563     * Returns list of all mountable volumes.
564     * @hide
565     */
566    public StorageVolume[] getVolumeList() {
567        if (mMountService == null) return new StorageVolume[0];
568        try {
569            Parcelable[] list = mMountService.getVolumeList();
570            if (list == null) return new StorageVolume[0];
571            int length = list.length;
572            StorageVolume[] result = new StorageVolume[length];
573            for (int i = 0; i < length; i++) {
574                result[i] = (StorageVolume)list[i];
575            }
576            return result;
577        } catch (RemoteException e) {
578            Log.e(TAG, "Failed to get volume list", e);
579            return null;
580        }
581    }
582
583    /**
584     * Returns list of paths for all mountable volumes.
585     * @hide
586     */
587    public String[] getVolumePaths() {
588        StorageVolume[] volumes = getVolumeList();
589        if (volumes == null) return null;
590        int count = volumes.length;
591        String[] paths = new String[count];
592        for (int i = 0; i < count; i++) {
593            paths[i] = volumes[i].getPath();
594        }
595        return paths;
596    }
597}
598