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