StorageManager.java revision 6dce4964b4d1a13d276d95730b8fb09d6a5a8d04
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<VolumeInfo> getWritablePrivateVolumes() {
590        try {
591            final ArrayList<VolumeInfo> res = new ArrayList<>();
592            for (VolumeInfo vol : mMountService.getVolumes(0)) {
593                if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
594                    res.add(vol);
595                }
596            }
597            return res;
598        } catch (RemoteException e) {
599            throw e.rethrowAsRuntimeException();
600        }
601    }
602
603    /** {@hide} */
604    public @NonNull List<VolumeRecord> getVolumeRecords() {
605        try {
606            return Arrays.asList(mMountService.getVolumeRecords(0));
607        } catch (RemoteException e) {
608            throw e.rethrowAsRuntimeException();
609        }
610    }
611
612    /** {@hide} */
613    public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
614        if (vol == null) return null;
615
616        // Nickname always takes precedence when defined
617        if (!TextUtils.isEmpty(vol.fsUuid)) {
618            final VolumeRecord rec = findRecordByUuid(vol.fsUuid);
619            if (rec != null && !TextUtils.isEmpty(rec.nickname)) {
620                return rec.nickname;
621            }
622        }
623
624        if (!TextUtils.isEmpty(vol.getDescription())) {
625            return vol.getDescription();
626        }
627
628        if (vol.disk != null) {
629            return vol.disk.getDescription();
630        }
631
632        return null;
633    }
634
635    /** {@hide} */
636    public @Nullable VolumeInfo getPrimaryPhysicalVolume() {
637        final List<VolumeInfo> vols = getVolumes();
638        for (VolumeInfo vol : vols) {
639            if (vol.isPrimaryPhysical()) {
640                return vol;
641            }
642        }
643        return null;
644    }
645
646    /** {@hide} */
647    public void mount(String volId) {
648        try {
649            mMountService.mount(volId);
650        } catch (RemoteException e) {
651            throw e.rethrowAsRuntimeException();
652        }
653    }
654
655    /** {@hide} */
656    public void unmount(String volId) {
657        try {
658            mMountService.unmount(volId);
659        } catch (RemoteException e) {
660            throw e.rethrowAsRuntimeException();
661        }
662    }
663
664    /** {@hide} */
665    public void format(String volId) {
666        try {
667            mMountService.format(volId);
668        } catch (RemoteException e) {
669            throw e.rethrowAsRuntimeException();
670        }
671    }
672
673    /** {@hide} */
674    public long benchmark(String volId) {
675        try {
676            return mMountService.benchmark(volId);
677        } catch (RemoteException e) {
678            throw e.rethrowAsRuntimeException();
679        }
680    }
681
682    /** {@hide} */
683    public void partitionPublic(String diskId) {
684        try {
685            mMountService.partitionPublic(diskId);
686        } catch (RemoteException e) {
687            throw e.rethrowAsRuntimeException();
688        }
689    }
690
691    /** {@hide} */
692    public void partitionPrivate(String diskId) {
693        try {
694            mMountService.partitionPrivate(diskId);
695        } catch (RemoteException e) {
696            throw e.rethrowAsRuntimeException();
697        }
698    }
699
700    /** {@hide} */
701    public void partitionMixed(String diskId, int ratio) {
702        try {
703            mMountService.partitionMixed(diskId, ratio);
704        } catch (RemoteException e) {
705            throw e.rethrowAsRuntimeException();
706        }
707    }
708
709    /** {@hide} */
710    public void wipeAdoptableDisks() {
711        // We only wipe devices in "adoptable" locations, which are in a
712        // long-term stable slot/location on the device, where apps have a
713        // reasonable chance of storing sensitive data. (Apps need to go through
714        // SAF to write to transient volumes.)
715        final List<DiskInfo> disks = getDisks();
716        for (DiskInfo disk : disks) {
717            final String diskId = disk.getId();
718            if (disk.isAdoptable()) {
719                Slog.d(TAG, "Found adoptable " + diskId + "; wiping");
720                try {
721                    // TODO: switch to explicit wipe command when we have it,
722                    // for now rely on the fact that vfat format does a wipe
723                    mMountService.partitionPublic(diskId);
724                } catch (Exception e) {
725                    Slog.w(TAG, "Failed to wipe " + diskId + ", but soldiering onward", e);
726                }
727            } else {
728                Slog.d(TAG, "Ignorning non-adoptable disk " + disk.getId());
729            }
730        }
731    }
732
733    /** {@hide} */
734    public void setVolumeNickname(String fsUuid, String nickname) {
735        try {
736            mMountService.setVolumeNickname(fsUuid, nickname);
737        } catch (RemoteException e) {
738            throw e.rethrowAsRuntimeException();
739        }
740    }
741
742    /** {@hide} */
743    public void setVolumeInited(String fsUuid, boolean inited) {
744        try {
745            mMountService.setVolumeUserFlags(fsUuid, inited ? VolumeRecord.USER_FLAG_INITED : 0,
746                    VolumeRecord.USER_FLAG_INITED);
747        } catch (RemoteException e) {
748            throw e.rethrowAsRuntimeException();
749        }
750    }
751
752    /** {@hide} */
753    public void setVolumeSnoozed(String fsUuid, boolean snoozed) {
754        try {
755            mMountService.setVolumeUserFlags(fsUuid, snoozed ? VolumeRecord.USER_FLAG_SNOOZED : 0,
756                    VolumeRecord.USER_FLAG_SNOOZED);
757        } catch (RemoteException e) {
758            throw e.rethrowAsRuntimeException();
759        }
760    }
761
762    /** {@hide} */
763    public void forgetVolume(String fsUuid) {
764        try {
765            mMountService.forgetVolume(fsUuid);
766        } catch (RemoteException e) {
767            throw e.rethrowAsRuntimeException();
768        }
769    }
770
771    /**
772     * This is not the API you're looking for.
773     *
774     * @see PackageManager#getPrimaryStorageCurrentVolume()
775     * @hide
776     */
777    public String getPrimaryStorageUuid() {
778        try {
779            return mMountService.getPrimaryStorageUuid();
780        } catch (RemoteException e) {
781            throw e.rethrowAsRuntimeException();
782        }
783    }
784
785    /**
786     * This is not the API you're looking for.
787     *
788     * @see PackageManager#movePrimaryStorage(VolumeInfo)
789     * @hide
790     */
791    public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
792        try {
793            mMountService.setPrimaryStorageUuid(volumeUuid, callback);
794        } catch (RemoteException e) {
795            throw e.rethrowAsRuntimeException();
796        }
797    }
798
799    /** {@hide} */
800    public @Nullable StorageVolume getStorageVolume(File file) {
801        return getStorageVolume(getVolumeList(), file);
802    }
803
804    /** {@hide} */
805    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
806        return getStorageVolume(getVolumeList(userId), file);
807    }
808
809    /** {@hide} */
810    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
811        File canonicalFile = null;
812        try {
813            canonicalFile = file.getCanonicalFile();
814        } catch (IOException ignored) {
815            canonicalFile = null;
816        }
817        for (StorageVolume volume : volumes) {
818            if (volume.getPathFile().equals(file)) {
819                return volume;
820            }
821            if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
822                return volume;
823            }
824        }
825        return null;
826    }
827
828    /**
829     * Gets the state of a volume via its mountpoint.
830     * @hide
831     */
832    @Deprecated
833    public @NonNull String getVolumeState(String mountPoint) {
834        final StorageVolume vol = getStorageVolume(new File(mountPoint));
835        if (vol != null) {
836            return vol.getState();
837        } else {
838            return Environment.MEDIA_UNKNOWN;
839        }
840    }
841
842    /** {@hide} */
843    public @NonNull StorageVolume[] getVolumeList() {
844        return getVolumeList(mContext.getUserId());
845    }
846
847    /** {@hide} */
848    public static @NonNull StorageVolume[] getVolumeList(int userId) {
849        final IMountService mountService = IMountService.Stub.asInterface(
850                ServiceManager.getService("mount"));
851        try {
852            return mountService.getVolumeList(userId);
853        } catch (RemoteException e) {
854            throw e.rethrowAsRuntimeException();
855        }
856    }
857
858    /**
859     * Returns list of paths for all mountable volumes.
860     * @hide
861     */
862    @Deprecated
863    public @NonNull String[] getVolumePaths() {
864        StorageVolume[] volumes = getVolumeList();
865        int count = volumes.length;
866        String[] paths = new String[count];
867        for (int i = 0; i < count; i++) {
868            paths[i] = volumes[i].getPath();
869        }
870        return paths;
871    }
872
873    /** {@hide} */
874    public @NonNull StorageVolume getPrimaryVolume() {
875        return getPrimaryVolume(getVolumeList());
876    }
877
878    /** {@hide} */
879    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
880        for (StorageVolume volume : volumes) {
881            if (volume.isPrimary()) {
882                return volume;
883            }
884        }
885        throw new IllegalStateException("Missing primary storage");
886    }
887
888    /** {@hide} */
889    public void remountUid(int uid) {
890        try {
891            mMountService.remountUid(uid);
892        } catch (RemoteException e) {
893            throw e.rethrowAsRuntimeException();
894        }
895    }
896
897    /** {@hide} */
898    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
899    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
900    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
901
902    /**
903     * Return the number of available bytes until the given path is considered
904     * running low on storage.
905     *
906     * @hide
907     */
908    public long getStorageBytesUntilLow(File path) {
909        return path.getUsableSpace() - getStorageFullBytes(path);
910    }
911
912    /**
913     * Return the number of available bytes at which the given path is
914     * considered running low on storage.
915     *
916     * @hide
917     */
918    public long getStorageLowBytes(File path) {
919        final long lowPercent = Settings.Global.getInt(mResolver,
920                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
921        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
922
923        final long maxLowBytes = Settings.Global.getLong(mResolver,
924                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
925
926        return Math.min(lowBytes, maxLowBytes);
927    }
928
929    /**
930     * Return the number of available bytes at which the given path is
931     * considered full.
932     *
933     * @hide
934     */
935    public long getStorageFullBytes(File path) {
936        return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
937                DEFAULT_FULL_THRESHOLD_BYTES);
938    }
939
940    /** {@hide} */
941    public static File maybeTranslateEmulatedPathToInternal(File path) {
942        final IMountService mountService = IMountService.Stub.asInterface(
943                ServiceManager.getService("mount"));
944        try {
945            final VolumeInfo[] vols = mountService.getVolumes(0);
946            for (VolumeInfo vol : vols) {
947                if ((vol.getType() == VolumeInfo.TYPE_EMULATED
948                        || vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isMountedReadable()) {
949                    final File internalPath = FileUtils.rewriteAfterRename(vol.getPath(),
950                            vol.getInternalPath(), path);
951                    if (internalPath != null) {
952                        return internalPath;
953                    }
954                }
955            }
956        } catch (RemoteException ignored) {
957        }
958        return path;
959    }
960
961    /// Consts to match the password types in cryptfs.h
962    /** @hide */
963    public static final int CRYPT_TYPE_PASSWORD = 0;
964    /** @hide */
965    public static final int CRYPT_TYPE_DEFAULT = 1;
966    /** @hide */
967    public static final int CRYPT_TYPE_PATTERN = 2;
968    /** @hide */
969    public static final int CRYPT_TYPE_PIN = 3;
970
971    // Constants for the data available via MountService.getField.
972    /** @hide */
973    public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
974    /** @hide */
975    public static final String OWNER_INFO_KEY = "OwnerInfo";
976    /** @hide */
977    public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
978    /** @hide */
979    public static final String PASSWORD_VISIBLE_KEY = "PasswordVisible";
980}
981