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