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