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