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