StorageManager.java revision d967f4664f40f9a4c5262a44b19df9bbdf457d8a
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.RemoteException;
23import android.os.ServiceManager;
24import android.util.Log;
25import android.util.Slog;
26import android.util.SparseArray;
27
28import java.lang.ref.WeakReference;
29import java.util.ArrayList;
30import java.util.List;
31import 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
51public 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    /**
532     * Gets the state of a volume via its mountpoint.
533     * @hide
534     */
535    public String getVolumeState(String mountPoint) {
536        try {
537            return mMountService.getVolumeState(mountPoint);
538        } catch (RemoteException e) {
539            Log.e(TAG, "Failed to get volume state", e);
540            return null;
541        }
542    }
543
544    /**
545     * Returns list of all mountable volumes.
546     * @hide
547     */
548    public String[] getVolumeList() {
549        try {
550            return mMountService.getVolumeList();
551        } catch (RemoteException e) {
552            Log.e(TAG, "Failed to get volume list", e);
553            return null;
554        }
555    }
556}
557