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