StorageManager.java revision b2b9ab8354da1485178cd8d8e9d89ac915b3f269
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.os.Environment;
26import android.os.FileUtils;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.provider.Settings;
33import android.util.Log;
34import android.util.SparseArray;
35
36import com.android.internal.os.SomeArgs;
37import com.android.internal.util.Preconditions;
38
39import java.io.File;
40import java.io.IOException;
41import java.lang.ref.WeakReference;
42import java.util.ArrayList;
43import java.util.Arrays;
44import java.util.Iterator;
45import java.util.List;
46import java.util.Objects;
47import java.util.concurrent.atomic.AtomicInteger;
48
49/**
50 * StorageManager is the interface to the systems storage service. The storage
51 * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
52 * <p>
53 * OBBs contain a filesystem that maybe be encrypted on disk and mounted
54 * on-demand from an application. OBBs are a good way of providing large amounts
55 * of binary assets without packaging them into APKs as they may be multiple
56 * gigabytes in size. However, due to their size, they're most likely stored in
57 * a shared storage pool accessible from all programs. The system does not
58 * guarantee the security of the OBB file itself: if any program modifies the
59 * OBB, there is no guarantee that a read from that OBB will produce the
60 * expected output.
61 * <p>
62 * Get an instance of this class by calling
63 * {@link android.content.Context#getSystemService(java.lang.String)} with an
64 * argument of {@link android.content.Context#STORAGE_SERVICE}.
65 */
66public class StorageManager {
67    private static final String TAG = "StorageManager";
68
69    /** {@hide} */
70    public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
71
72    private final Context mContext;
73    private final ContentResolver mResolver;
74
75    private final IMountService mMountService;
76    private final Looper mLooper;
77    private final AtomicInteger mNextNonce = new AtomicInteger(0);
78
79    private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
80
81    private static class StorageEventListenerDelegate extends IMountServiceListener.Stub implements
82            Handler.Callback {
83        private static final int MSG_STORAGE_STATE_CHANGED = 1;
84        private static final int MSG_VOLUME_STATE_CHANGED = 2;
85
86        final StorageEventListener mCallback;
87        final Handler mHandler;
88
89        public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
90            mCallback = callback;
91            mHandler = new Handler(looper, this);
92        }
93
94        @Override
95        public boolean handleMessage(Message msg) {
96            final SomeArgs args = (SomeArgs) msg.obj;
97            switch (msg.what) {
98                case MSG_STORAGE_STATE_CHANGED:
99                    mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
100                            (String) args.arg3);
101                    args.recycle();
102                    return true;
103                case MSG_VOLUME_STATE_CHANGED:
104                    mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
105                    args.recycle();
106                    return true;
107            }
108            args.recycle();
109            return false;
110        }
111
112        @Override
113        public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
114            // Ignored
115        }
116
117        @Override
118        public void onStorageStateChanged(String path, String oldState, String newState) {
119            final SomeArgs args = SomeArgs.obtain();
120            args.arg1 = path;
121            args.arg2 = oldState;
122            args.arg3 = newState;
123            mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
124        }
125
126        @Override
127        public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
128            final SomeArgs args = SomeArgs.obtain();
129            args.arg1 = vol;
130            args.argi2 = oldState;
131            args.argi3 = newState;
132            mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
133        }
134    }
135
136    /**
137     * Binder listener for OBB action results.
138     */
139    private final ObbActionListener mObbActionListener = new ObbActionListener();
140
141    private class ObbActionListener extends IObbActionListener.Stub {
142        @SuppressWarnings("hiding")
143        private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
144
145        @Override
146        public void onObbResult(String filename, int nonce, int status) {
147            final ObbListenerDelegate delegate;
148            synchronized (mListeners) {
149                delegate = mListeners.get(nonce);
150                if (delegate != null) {
151                    mListeners.remove(nonce);
152                }
153            }
154
155            if (delegate != null) {
156                delegate.sendObbStateChanged(filename, status);
157            }
158        }
159
160        public int addListener(OnObbStateChangeListener listener) {
161            final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
162
163            synchronized (mListeners) {
164                mListeners.put(delegate.nonce, delegate);
165            }
166
167            return delegate.nonce;
168        }
169    }
170
171    private int getNextNonce() {
172        return mNextNonce.getAndIncrement();
173    }
174
175    /**
176     * Private class containing sender and receiver code for StorageEvents.
177     */
178    private class ObbListenerDelegate {
179        private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
180        private final Handler mHandler;
181
182        private final int nonce;
183
184        ObbListenerDelegate(OnObbStateChangeListener listener) {
185            nonce = getNextNonce();
186            mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
187            mHandler = new Handler(mLooper) {
188                @Override
189                public void handleMessage(Message msg) {
190                    final OnObbStateChangeListener changeListener = getListener();
191                    if (changeListener == null) {
192                        return;
193                    }
194
195                    changeListener.onObbStateChange((String) msg.obj, msg.arg1);
196                }
197            };
198        }
199
200        OnObbStateChangeListener getListener() {
201            if (mObbEventListenerRef == null) {
202                return null;
203            }
204            return mObbEventListenerRef.get();
205        }
206
207        void sendObbStateChanged(String path, int state) {
208            mHandler.obtainMessage(0, state, 0, path).sendToTarget();
209        }
210    }
211
212    /** {@hide} */
213    public static StorageManager from(Context context) {
214        return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
215    }
216
217    /**
218     * Constructs a StorageManager object through which an application can
219     * can communicate with the systems mount service.
220     *
221     * @param tgtLooper The {@link android.os.Looper} which events will be received on.
222     *
223     * <p>Applications can get instance of this class by calling
224     * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
225     * of {@link android.content.Context#STORAGE_SERVICE}.
226     *
227     * @hide
228     */
229    public StorageManager(Context context, Looper looper) {
230        mContext = context;
231        mResolver = context.getContentResolver();
232        mLooper = looper;
233        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
234        if (mMountService == null) {
235            throw new IllegalStateException("Failed to find running mount service");
236        }
237    }
238
239    /**
240     * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
241     *
242     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
243     *
244     * @hide
245     */
246    public void registerListener(StorageEventListener listener) {
247        synchronized (mDelegates) {
248            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
249                    mLooper);
250            try {
251                mMountService.registerListener(delegate);
252            } catch (RemoteException e) {
253                throw e.rethrowAsRuntimeException();
254            }
255            mDelegates.add(delegate);
256        }
257    }
258
259    /**
260     * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
261     *
262     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
263     *
264     * @hide
265     */
266    public void unregisterListener(StorageEventListener listener) {
267        synchronized (mDelegates) {
268            for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
269                final StorageEventListenerDelegate delegate = i.next();
270                if (delegate.mCallback == listener) {
271                    try {
272                        mMountService.unregisterListener(delegate);
273                    } catch (RemoteException e) {
274                        throw e.rethrowAsRuntimeException();
275                    }
276                    i.remove();
277                }
278            }
279        }
280    }
281
282    /**
283     * Enables USB Mass Storage (UMS) on the device.
284     *
285     * @hide
286     */
287    @Deprecated
288    public void enableUsbMassStorage() {
289    }
290
291    /**
292     * Disables USB Mass Storage (UMS) on the device.
293     *
294     * @hide
295     */
296    @Deprecated
297    public void disableUsbMassStorage() {
298    }
299
300    /**
301     * Query if a USB Mass Storage (UMS) host is connected.
302     * @return true if UMS host is connected.
303     *
304     * @hide
305     */
306    @Deprecated
307    public boolean isUsbMassStorageConnected() {
308        return false;
309    }
310
311    /**
312     * Query if a USB Mass Storage (UMS) is enabled on the device.
313     * @return true if UMS host is enabled.
314     *
315     * @hide
316     */
317    @Deprecated
318    public boolean isUsbMassStorageEnabled() {
319        return false;
320    }
321
322    /**
323     * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
324     * specified, it is supplied to the mounting process to be used in any
325     * encryption used in the OBB.
326     * <p>
327     * The OBB will remain mounted for as long as the StorageManager reference
328     * is held by the application. As soon as this reference is lost, the OBBs
329     * in use will be unmounted. The {@link OnObbStateChangeListener} registered
330     * with this call will receive the success or failure of this operation.
331     * <p>
332     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
333     * file matches a package ID that is owned by the calling program's UID.
334     * That is, shared UID applications can attempt to mount any other
335     * application's OBB that shares its UID.
336     *
337     * @param rawPath the path to the OBB file
338     * @param key secret used to encrypt the OBB; may be <code>null</code> if no
339     *            encryption was used on the OBB.
340     * @param listener will receive the success or failure of the operation
341     * @return whether the mount call was successfully queued or not
342     */
343    public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
344        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
345        Preconditions.checkNotNull(listener, "listener cannot be null");
346
347        try {
348            final String canonicalPath = new File(rawPath).getCanonicalPath();
349            final int nonce = mObbActionListener.addListener(listener);
350            mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
351            return true;
352        } catch (IOException e) {
353            throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
354        } catch (RemoteException e) {
355            Log.e(TAG, "Failed to mount OBB", e);
356        }
357
358        return false;
359    }
360
361    /**
362     * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
363     * <code>force</code> flag is true, it will kill any application needed to
364     * unmount the given OBB (even the calling application).
365     * <p>
366     * The {@link OnObbStateChangeListener} registered with this call will
367     * receive the success or failure of this operation.
368     * <p>
369     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
370     * file matches a package ID that is owned by the calling program's UID.
371     * That is, shared UID applications can obtain access to any other
372     * application's OBB that shares its UID.
373     * <p>
374     *
375     * @param rawPath path to the OBB file
376     * @param force whether to kill any programs using this in order to unmount
377     *            it
378     * @param listener will receive the success or failure of the operation
379     * @return whether the unmount call was successfully queued or not
380     */
381    public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
382        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
383        Preconditions.checkNotNull(listener, "listener cannot be null");
384
385        try {
386            final int nonce = mObbActionListener.addListener(listener);
387            mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
388            return true;
389        } catch (RemoteException e) {
390            Log.e(TAG, "Failed to mount OBB", e);
391        }
392
393        return false;
394    }
395
396    /**
397     * Check whether an Opaque Binary Blob (OBB) is mounted or not.
398     *
399     * @param rawPath path to OBB image
400     * @return true if OBB is mounted; false if not mounted or on error
401     */
402    public boolean isObbMounted(String rawPath) {
403        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
404
405        try {
406            return mMountService.isObbMounted(rawPath);
407        } catch (RemoteException e) {
408            Log.e(TAG, "Failed to check if OBB is mounted", e);
409        }
410
411        return false;
412    }
413
414    /**
415     * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
416     * give you the path to where you can obtain access to the internals of the
417     * OBB.
418     *
419     * @param rawPath path to OBB image
420     * @return absolute path to mounted OBB image data or <code>null</code> if
421     *         not mounted or exception encountered trying to read status
422     */
423    public String getMountedObbPath(String rawPath) {
424        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
425
426        try {
427            return mMountService.getMountedObbPath(rawPath);
428        } catch (RemoteException e) {
429            Log.e(TAG, "Failed to find mounted path for OBB", e);
430        }
431
432        return null;
433    }
434
435    /** {@hide} */
436    public @NonNull List<DiskInfo> getDisks() {
437        try {
438            return Arrays.asList(mMountService.getDisks());
439        } catch (RemoteException e) {
440            throw e.rethrowAsRuntimeException();
441        }
442    }
443
444    /** {@hide} */
445    public @Nullable DiskInfo findDiskById(String id) {
446        Preconditions.checkNotNull(id);
447        // TODO; go directly to service to make this faster
448        for (DiskInfo disk : getDisks()) {
449            if (Objects.equals(disk.id, id)) {
450                return disk;
451            }
452        }
453        return null;
454    }
455
456    /** {@hide} */
457    public @Nullable VolumeInfo findVolumeById(String id) {
458        Preconditions.checkNotNull(id);
459        // TODO; go directly to service to make this faster
460        for (VolumeInfo vol : getVolumes()) {
461            if (Objects.equals(vol.id, id)) {
462                return vol;
463            }
464        }
465        return null;
466    }
467
468    /** {@hide} */
469    public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
470        Preconditions.checkNotNull(fsUuid);
471        // TODO; go directly to service to make this faster
472        for (VolumeInfo vol : getVolumes()) {
473            if (Objects.equals(vol.fsUuid, fsUuid)) {
474                return vol;
475            }
476        }
477        return null;
478    }
479
480    /** {@hide} */
481    public @NonNull List<VolumeInfo> getVolumes() {
482        try {
483            return Arrays.asList(mMountService.getVolumes());
484        } catch (RemoteException e) {
485            throw e.rethrowAsRuntimeException();
486        }
487    }
488
489    /** {@hide} */
490    public void mount(String volId) {
491        try {
492            mMountService.mount(volId);
493        } catch (RemoteException e) {
494            throw e.rethrowAsRuntimeException();
495        }
496    }
497
498    /** {@hide} */
499    public void unmount(String volId) {
500        try {
501            mMountService.unmount(volId);
502        } catch (RemoteException e) {
503            throw e.rethrowAsRuntimeException();
504        }
505    }
506
507    /** {@hide} */
508    public void format(String volId) {
509        try {
510            mMountService.format(volId);
511        } catch (RemoteException e) {
512            throw e.rethrowAsRuntimeException();
513        }
514    }
515
516    /** {@hide} */
517    public void partitionPublic(String diskId) {
518        try {
519            mMountService.partitionPublic(diskId);
520        } catch (RemoteException e) {
521            throw e.rethrowAsRuntimeException();
522        }
523    }
524
525    /** {@hide} */
526    public void partitionPrivate(String diskId) {
527        try {
528            mMountService.partitionPrivate(diskId);
529        } catch (RemoteException e) {
530            throw e.rethrowAsRuntimeException();
531        }
532    }
533
534    /** {@hide} */
535    public void partitionMixed(String diskId, int ratio) {
536        try {
537            mMountService.partitionMixed(diskId, ratio);
538        } catch (RemoteException e) {
539            throw e.rethrowAsRuntimeException();
540        }
541    }
542
543    /** {@hide} */
544    public @Nullable StorageVolume getStorageVolume(File file) {
545        return getStorageVolume(getVolumeList(), file);
546    }
547
548    /** {@hide} */
549    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
550        return getStorageVolume(getVolumeList(userId), file);
551    }
552
553    /** {@hide} */
554    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
555        File canonicalFile = null;
556        try {
557            canonicalFile = file.getCanonicalFile();
558        } catch (IOException ignored) {
559            canonicalFile = null;
560        }
561        for (StorageVolume volume : volumes) {
562            if (volume.getPathFile().equals(file)) {
563                return volume;
564            }
565            if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
566                return volume;
567            }
568        }
569        return null;
570    }
571
572    /**
573     * Gets the state of a volume via its mountpoint.
574     * @hide
575     */
576    @Deprecated
577    public @NonNull String getVolumeState(String mountPoint) {
578        final StorageVolume vol = getStorageVolume(new File(mountPoint));
579        if (vol != null) {
580            return vol.getState();
581        } else {
582            return Environment.MEDIA_UNKNOWN;
583        }
584    }
585
586    /** {@hide} */
587    public @NonNull StorageVolume[] getVolumeList() {
588        return getVolumeList(mContext.getUserId());
589    }
590
591    /** {@hide} */
592    public static @NonNull StorageVolume[] getVolumeList(int userId) {
593        final IMountService mountService = IMountService.Stub.asInterface(
594                ServiceManager.getService("mount"));
595        try {
596            return mountService.getVolumeList(userId);
597        } catch (RemoteException e) {
598            throw e.rethrowAsRuntimeException();
599        }
600    }
601
602    /**
603     * Returns list of paths for all mountable volumes.
604     * @hide
605     */
606    @Deprecated
607    public @NonNull String[] getVolumePaths() {
608        StorageVolume[] volumes = getVolumeList();
609        int count = volumes.length;
610        String[] paths = new String[count];
611        for (int i = 0; i < count; i++) {
612            paths[i] = volumes[i].getPath();
613        }
614        return paths;
615    }
616
617    /** {@hide} */
618    public @NonNull StorageVolume getPrimaryVolume() {
619        return getPrimaryVolume(getVolumeList());
620    }
621
622    /** {@hide} */
623    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
624        for (StorageVolume volume : volumes) {
625            if (volume.isPrimary()) {
626                return volume;
627            }
628        }
629        throw new IllegalStateException("Missing primary storage");
630    }
631
632    /** {@hide} */
633    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
634    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
635    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
636
637    /**
638     * Return the number of available bytes until the given path is considered
639     * running low on storage.
640     *
641     * @hide
642     */
643    public long getStorageBytesUntilLow(File path) {
644        return path.getUsableSpace() - getStorageFullBytes(path);
645    }
646
647    /**
648     * Return the number of available bytes at which the given path is
649     * considered running low on storage.
650     *
651     * @hide
652     */
653    public long getStorageLowBytes(File path) {
654        final long lowPercent = Settings.Global.getInt(mResolver,
655                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
656        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
657
658        final long maxLowBytes = Settings.Global.getLong(mResolver,
659                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
660
661        return Math.min(lowBytes, maxLowBytes);
662    }
663
664    /**
665     * Return the number of available bytes at which the given path is
666     * considered full.
667     *
668     * @hide
669     */
670    public long getStorageFullBytes(File path) {
671        return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
672                DEFAULT_FULL_THRESHOLD_BYTES);
673    }
674
675    /// Consts to match the password types in cryptfs.h
676    /** @hide */
677    public static final int CRYPT_TYPE_PASSWORD = 0;
678    /** @hide */
679    public static final int CRYPT_TYPE_DEFAULT = 1;
680    /** @hide */
681    public static final int CRYPT_TYPE_PATTERN = 2;
682    /** @hide */
683    public static final int CRYPT_TYPE_PIN = 3;
684
685    // Constants for the data available via MountService.getField.
686    /** @hide */
687    public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
688    /** @hide */
689    public static final String OWNER_INFO_KEY = "OwnerInfo";
690    /** @hide */
691    public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
692}
693