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