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