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