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