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