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