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