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 { 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 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 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 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 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 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 177 OnObbStateChangeListener getListener() { 178 if (mObbEventListenerRef == null) { 179 return null; 180 } 181 return mObbEventListenerRef.get(); 182 } 183 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 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 216 public StorageEvent(int what) { 217 mMessage = Message.obtain(); 218 mMessage.what = what; 219 mMessage.obj = this; 220 } 221 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 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 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 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 282 StorageEventListener getListener() { 283 return mStorageEventListener; 284 } 285 286 void sendShareAvailabilityChanged(boolean available) { 287 UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available); 288 mHandler.sendMessage(e.getMessage()); 289 } 290 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} */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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} */ 602 public StorageVolume getPrimaryVolume() { 603 return getPrimaryVolume(getVolumeList()); 604 } 605 606 /** {@hide} */ 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 */ 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 */ 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 */ 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