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