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