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