StorageManager.java revision 2f6a3885533a52758c2cd4f81f6123a712be8ae6
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.Handler;
20import android.os.Looper;
21import android.os.Message;
22import android.os.Parcelable;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25import android.util.Log;
26import android.util.Slog;
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    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        private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
104
105        @Override
106        public void onObbResult(String filename, int nonce, int status) throws RemoteException {
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 listener = getListener();
151                    if (listener == 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                        listener.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