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