StorageManager.java revision 7151a9a887051542c6da9f380376f3b306184e5c
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.concurrent.atomic.AtomicInteger;
47
48/**
49 * StorageManager is the interface to the systems storage service. The storage
50 * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
51 * <p>
52 * OBBs contain a filesystem that maybe be encrypted on disk and mounted
53 * on-demand from an application. OBBs are a good way of providing large amounts
54 * of binary assets without packaging them into APKs as they may be multiple
55 * gigabytes in size. However, due to their size, they're most likely stored in
56 * a shared storage pool accessible from all programs. The system does not
57 * guarantee the security of the OBB file itself: if any program modifies the
58 * OBB, there is no guarantee that a read from that OBB will produce the
59 * expected output.
60 * <p>
61 * Get an instance of this class by calling
62 * {@link android.content.Context#getSystemService(java.lang.String)} with an
63 * argument of {@link android.content.Context#STORAGE_SERVICE}.
64 */
65public class StorageManager {
66    private static final String TAG = "StorageManager";
67
68    /** {@hide} */
69    public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
70
71    private final Context mContext;
72    private final ContentResolver mResolver;
73
74    private final IMountService mMountService;
75    private final Looper mLooper;
76    private final AtomicInteger mNextNonce = new AtomicInteger(0);
77
78    private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
79
80    private static class StorageEventListenerDelegate extends IMountServiceListener.Stub implements
81            Handler.Callback {
82        private static final int MSG_STORAGE_STATE_CHANGED = 1;
83        private static final int MSG_VOLUME_STATE_CHANGED = 2;
84
85        final StorageEventListener mCallback;
86        final Handler mHandler;
87
88        public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
89            mCallback = callback;
90            mHandler = new Handler(looper, this);
91        }
92
93        @Override
94        public boolean handleMessage(Message msg) {
95            final SomeArgs args = (SomeArgs) msg.obj;
96            switch (msg.what) {
97                case MSG_STORAGE_STATE_CHANGED:
98                    mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
99                            (String) args.arg3);
100                    args.recycle();
101                    return true;
102                case MSG_VOLUME_STATE_CHANGED:
103                    mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
104                    args.recycle();
105                    return true;
106            }
107            args.recycle();
108            return false;
109        }
110
111        @Override
112        public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
113            // Ignored
114        }
115
116        @Override
117        public void onStorageStateChanged(String path, String oldState, String newState) {
118            final SomeArgs args = SomeArgs.obtain();
119            args.arg1 = path;
120            args.arg2 = oldState;
121            args.arg3 = newState;
122            mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
123        }
124
125        @Override
126        public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
127            final SomeArgs args = SomeArgs.obtain();
128            args.arg1 = vol;
129            args.argi2 = oldState;
130            args.argi3 = newState;
131            mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
132        }
133    }
134
135    /**
136     * Binder listener for OBB action results.
137     */
138    private final ObbActionListener mObbActionListener = new ObbActionListener();
139
140    private class ObbActionListener extends IObbActionListener.Stub {
141        @SuppressWarnings("hiding")
142        private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
143
144        @Override
145        public void onObbResult(String filename, int nonce, int status) {
146            final ObbListenerDelegate delegate;
147            synchronized (mListeners) {
148                delegate = mListeners.get(nonce);
149                if (delegate != null) {
150                    mListeners.remove(nonce);
151                }
152            }
153
154            if (delegate != null) {
155                delegate.sendObbStateChanged(filename, status);
156            }
157        }
158
159        public int addListener(OnObbStateChangeListener listener) {
160            final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
161
162            synchronized (mListeners) {
163                mListeners.put(delegate.nonce, delegate);
164            }
165
166            return delegate.nonce;
167        }
168    }
169
170    private int getNextNonce() {
171        return mNextNonce.getAndIncrement();
172    }
173
174    /**
175     * Private class containing sender and receiver code for StorageEvents.
176     */
177    private class ObbListenerDelegate {
178        private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
179        private final Handler mHandler;
180
181        private final int nonce;
182
183        ObbListenerDelegate(OnObbStateChangeListener listener) {
184            nonce = getNextNonce();
185            mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
186            mHandler = new Handler(mLooper) {
187                @Override
188                public void handleMessage(Message msg) {
189                    final OnObbStateChangeListener changeListener = getListener();
190                    if (changeListener == null) {
191                        return;
192                    }
193
194                    changeListener.onObbStateChange((String) msg.obj, msg.arg1);
195                }
196            };
197        }
198
199        OnObbStateChangeListener getListener() {
200            if (mObbEventListenerRef == null) {
201                return null;
202            }
203            return mObbEventListenerRef.get();
204        }
205
206        void sendObbStateChanged(String path, int state) {
207            mHandler.obtainMessage(0, state, 0, path).sendToTarget();
208        }
209    }
210
211    /** {@hide} */
212    public static StorageManager from(Context context) {
213        return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
214    }
215
216    /**
217     * Constructs a StorageManager object through which an application can
218     * can communicate with the systems mount service.
219     *
220     * @param tgtLooper The {@link android.os.Looper} which events will be received on.
221     *
222     * <p>Applications can get instance of this class by calling
223     * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
224     * of {@link android.content.Context#STORAGE_SERVICE}.
225     *
226     * @hide
227     */
228    public StorageManager(Context context, Looper looper) {
229        mContext = context;
230        mResolver = context.getContentResolver();
231        mLooper = looper;
232        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
233        if (mMountService == null) {
234            Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
235            return;
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 @NonNull List<VolumeInfo> getVolumes() {
446        try {
447            return Arrays.asList(mMountService.getVolumes());
448        } catch (RemoteException e) {
449            throw e.rethrowAsRuntimeException();
450        }
451    }
452
453    /** {@hide} */
454    public void mount(String volId) {
455        try {
456            mMountService.mount(volId);
457        } catch (RemoteException e) {
458            throw e.rethrowAsRuntimeException();
459        }
460    }
461
462    /** {@hide} */
463    public void unmount(String volId) {
464        try {
465            mMountService.unmount(volId);
466        } catch (RemoteException e) {
467            throw e.rethrowAsRuntimeException();
468        }
469    }
470
471    /** {@hide} */
472    public void format(String volId) {
473        try {
474            mMountService.format(volId);
475        } catch (RemoteException e) {
476            throw e.rethrowAsRuntimeException();
477        }
478    }
479
480    /** {@hide} */
481    public void partitionPublic(String diskId) {
482        try {
483            mMountService.partitionPublic(diskId);
484        } catch (RemoteException e) {
485            throw e.rethrowAsRuntimeException();
486        }
487    }
488
489    /** {@hide} */
490    public void partitionPrivate(String diskId) {
491        try {
492            mMountService.partitionPrivate(diskId);
493        } catch (RemoteException e) {
494            throw e.rethrowAsRuntimeException();
495        }
496    }
497
498    /** {@hide} */
499    public void partitionMixed(String diskId, int ratio) {
500        try {
501            mMountService.partitionMixed(diskId, ratio);
502        } catch (RemoteException e) {
503            throw e.rethrowAsRuntimeException();
504        }
505    }
506
507    /** {@hide} */
508    public @Nullable StorageVolume getStorageVolume(File file) {
509        return getStorageVolume(getVolumeList(), file);
510    }
511
512    /** {@hide} */
513    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
514        return getStorageVolume(getVolumeList(userId), file);
515    }
516
517    /** {@hide} */
518    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
519        File canonicalFile = null;
520        try {
521            canonicalFile = file.getCanonicalFile();
522        } catch (IOException ignored) {
523            canonicalFile = null;
524        }
525        for (StorageVolume volume : volumes) {
526            if (volume.getPathFile().equals(file)) {
527                return volume;
528            }
529            if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
530                return volume;
531            }
532        }
533        return null;
534    }
535
536    /**
537     * Gets the state of a volume via its mountpoint.
538     * @hide
539     */
540    @Deprecated
541    public @NonNull String getVolumeState(String mountPoint) {
542        final StorageVolume vol = getStorageVolume(new File(mountPoint));
543        if (vol != null) {
544            return vol.getState();
545        } else {
546            return Environment.MEDIA_UNKNOWN;
547        }
548    }
549
550    /** {@hide} */
551    public @NonNull StorageVolume[] getVolumeList() {
552        return getVolumeList(mContext.getUserId());
553    }
554
555    /** {@hide} */
556    public static @NonNull StorageVolume[] getVolumeList(int userId) {
557        final IMountService mountService = IMountService.Stub.asInterface(
558                ServiceManager.getService("mount"));
559        try {
560            return mountService.getVolumeList(userId);
561        } catch (RemoteException e) {
562            throw e.rethrowAsRuntimeException();
563        }
564    }
565
566    /**
567     * Returns list of paths for all mountable volumes.
568     * @hide
569     */
570    @Deprecated
571    public @NonNull String[] getVolumePaths() {
572        StorageVolume[] volumes = getVolumeList();
573        int count = volumes.length;
574        String[] paths = new String[count];
575        for (int i = 0; i < count; i++) {
576            paths[i] = volumes[i].getPath();
577        }
578        return paths;
579    }
580
581    /** {@hide} */
582    public @NonNull StorageVolume getPrimaryVolume() {
583        return getPrimaryVolume(getVolumeList());
584    }
585
586    /** {@hide} */
587    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
588        for (StorageVolume volume : volumes) {
589            if (volume.isPrimary()) {
590                return volume;
591            }
592        }
593        throw new IllegalStateException("Missing primary storage");
594    }
595
596    /** {@hide} */
597    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
598    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
599    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
600
601    /**
602     * Return the number of available bytes until the given path is considered
603     * running low on storage.
604     *
605     * @hide
606     */
607    public long getStorageBytesUntilLow(File path) {
608        return path.getUsableSpace() - getStorageFullBytes(path);
609    }
610
611    /**
612     * Return the number of available bytes at which the given path is
613     * considered running low on storage.
614     *
615     * @hide
616     */
617    public long getStorageLowBytes(File path) {
618        final long lowPercent = Settings.Global.getInt(mResolver,
619                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
620        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
621
622        final long maxLowBytes = Settings.Global.getLong(mResolver,
623                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
624
625        return Math.min(lowBytes, maxLowBytes);
626    }
627
628    /**
629     * Return the number of available bytes at which the given path is
630     * considered full.
631     *
632     * @hide
633     */
634    public long getStorageFullBytes(File path) {
635        return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
636                DEFAULT_FULL_THRESHOLD_BYTES);
637    }
638
639    /// Consts to match the password types in cryptfs.h
640    /** @hide */
641    public static final int CRYPT_TYPE_PASSWORD = 0;
642    /** @hide */
643    public static final int CRYPT_TYPE_DEFAULT = 1;
644    /** @hide */
645    public static final int CRYPT_TYPE_PATTERN = 2;
646    /** @hide */
647    public static final int CRYPT_TYPE_PIN = 3;
648
649    // Constants for the data available via MountService.getField.
650    /** @hide */
651    public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
652    /** @hide */
653    public static final String OWNER_INFO_KEY = "OwnerInfo";
654    /** @hide */
655    public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
656}
657