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