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