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