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