StorageManager.java revision af9d667ccf3e24058214cf4cc0a8aa8bc5100e3c
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.os.Handler; 20import android.os.Looper; 21import android.os.Message; 22import android.os.RemoteException; 23import android.os.ServiceManager; 24import android.util.Log; 25import android.util.Slog; 26import android.util.SparseArray; 27 28import java.lang.ref.WeakReference; 29import java.util.ArrayList; 30import java.util.List; 31import 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 51public 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 private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>(); 103 104 @Override 105 public void onObbResult(String filename, int nonce, int status) throws RemoteException { 106 final ObbListenerDelegate delegate; 107 synchronized (mListeners) { 108 delegate = mListeners.get(nonce); 109 if (delegate != null) { 110 mListeners.remove(nonce); 111 } 112 } 113 114 if (delegate != null) { 115 delegate.sendObbStateChanged(filename, status); 116 } 117 } 118 119 public int addListener(OnObbStateChangeListener listener) { 120 final ObbListenerDelegate delegate = new ObbListenerDelegate(listener); 121 122 synchronized (mListeners) { 123 mListeners.put(delegate.nonce, delegate); 124 } 125 126 return delegate.nonce; 127 } 128 } 129 130 private int getNextNonce() { 131 return mNextNonce.getAndIncrement(); 132 } 133 134 /** 135 * Private class containing sender and receiver code for StorageEvents. 136 */ 137 private class ObbListenerDelegate { 138 private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef; 139 private final Handler mHandler; 140 141 private final int nonce; 142 143 ObbListenerDelegate(OnObbStateChangeListener listener) { 144 nonce = getNextNonce(); 145 mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener); 146 mHandler = new Handler(mTgtLooper) { 147 @Override 148 public void handleMessage(Message msg) { 149 final OnObbStateChangeListener listener = getListener(); 150 if (listener == null) { 151 return; 152 } 153 154 StorageEvent e = (StorageEvent) msg.obj; 155 156 if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) { 157 ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e; 158 listener.onObbStateChange(ev.path, ev.state); 159 } else { 160 Log.e(TAG, "Unsupported event " + msg.what); 161 } 162 } 163 }; 164 } 165 166 OnObbStateChangeListener getListener() { 167 if (mObbEventListenerRef == null) { 168 return null; 169 } 170 return mObbEventListenerRef.get(); 171 } 172 173 void sendObbStateChanged(String path, int state) { 174 ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state); 175 mHandler.sendMessage(e.getMessage()); 176 } 177 } 178 179 /** 180 * Message sent during an OBB status change event. 181 */ 182 private class ObbStateChangedStorageEvent extends StorageEvent { 183 public final String path; 184 185 public final int state; 186 187 public ObbStateChangedStorageEvent(String path, int state) { 188 super(EVENT_OBB_STATE_CHANGED); 189 this.path = path; 190 this.state = state; 191 } 192 } 193 194 /** 195 * Private base class for messages sent between the callback thread 196 * and the target looper handler. 197 */ 198 private class StorageEvent { 199 static final int EVENT_UMS_CONNECTION_CHANGED = 1; 200 static final int EVENT_STORAGE_STATE_CHANGED = 2; 201 static final int EVENT_OBB_STATE_CHANGED = 3; 202 203 private Message mMessage; 204 205 public StorageEvent(int what) { 206 mMessage = Message.obtain(); 207 mMessage.what = what; 208 mMessage.obj = this; 209 } 210 211 public Message getMessage() { 212 return mMessage; 213 } 214 } 215 216 /** 217 * Message sent on a USB mass storage connection change. 218 */ 219 private class UmsConnectionChangedStorageEvent extends StorageEvent { 220 public boolean available; 221 222 public UmsConnectionChangedStorageEvent(boolean a) { 223 super(EVENT_UMS_CONNECTION_CHANGED); 224 available = a; 225 } 226 } 227 228 /** 229 * Message sent on volume state change. 230 */ 231 private class StorageStateChangedStorageEvent extends StorageEvent { 232 public String path; 233 public String oldState; 234 public String newState; 235 236 public StorageStateChangedStorageEvent(String p, String oldS, String newS) { 237 super(EVENT_STORAGE_STATE_CHANGED); 238 path = p; 239 oldState = oldS; 240 newState = newS; 241 } 242 } 243 244 /** 245 * Private class containing sender and receiver code for StorageEvents. 246 */ 247 private class ListenerDelegate { 248 final StorageEventListener mStorageEventListener; 249 private final Handler mHandler; 250 251 ListenerDelegate(StorageEventListener listener) { 252 mStorageEventListener = listener; 253 mHandler = new Handler(mTgtLooper) { 254 @Override 255 public void handleMessage(Message msg) { 256 StorageEvent e = (StorageEvent) msg.obj; 257 258 if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) { 259 UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e; 260 mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available); 261 } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) { 262 StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e; 263 mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState); 264 } else { 265 Log.e(TAG, "Unsupported event " + msg.what); 266 } 267 } 268 }; 269 } 270 271 StorageEventListener getListener() { 272 return mStorageEventListener; 273 } 274 275 void sendShareAvailabilityChanged(boolean available) { 276 UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available); 277 mHandler.sendMessage(e.getMessage()); 278 } 279 280 void sendStorageStateChanged(String path, String oldState, String newState) { 281 StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState); 282 mHandler.sendMessage(e.getMessage()); 283 } 284 } 285 286 /** 287 * Constructs a StorageManager object through which an application can 288 * can communicate with the systems mount service. 289 * 290 * @param tgtLooper The {@android.os.Looper} which events will be received on. 291 * 292 * <p>Applications can get instance of this class by calling 293 * {@link android.content.Context#getSystemService(java.lang.String)} with an argument 294 * of {@link android.content.Context#STORAGE_SERVICE}. 295 * 296 * @hide 297 */ 298 public StorageManager(Looper tgtLooper) throws RemoteException { 299 mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); 300 if (mMountService == null) { 301 Log.e(TAG, "Unable to connect to mount service! - is it running yet?"); 302 return; 303 } 304 mTgtLooper = tgtLooper; 305 mBinderListener = new MountServiceBinderListener(); 306 mMountService.registerListener(mBinderListener); 307 } 308 309 310 /** 311 * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}. 312 * 313 * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object. 314 * 315 * @hide 316 */ 317 public void registerListener(StorageEventListener listener) { 318 if (listener == null) { 319 return; 320 } 321 322 synchronized (mListeners) { 323 mListeners.add(new ListenerDelegate(listener)); 324 } 325 } 326 327 /** 328 * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}. 329 * 330 * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object. 331 * 332 * @hide 333 */ 334 public void unregisterListener(StorageEventListener listener) { 335 if (listener == null) { 336 return; 337 } 338 339 synchronized (mListeners) { 340 final int size = mListeners.size(); 341 for (int i=0 ; i<size ; i++) { 342 ListenerDelegate l = mListeners.get(i); 343 if (l.getListener() == listener) { 344 mListeners.remove(i); 345 break; 346 } 347 } 348 } 349 } 350 351 /** 352 * Enables USB Mass Storage (UMS) on the device. 353 * 354 * @hide 355 */ 356 public void enableUsbMassStorage() { 357 try { 358 mMountService.setUsbMassStorageEnabled(true); 359 } catch (Exception ex) { 360 Log.e(TAG, "Failed to enable UMS", ex); 361 } 362 } 363 364 /** 365 * Disables USB Mass Storage (UMS) on the device. 366 * 367 * @hide 368 */ 369 public void disableUsbMassStorage() { 370 try { 371 mMountService.setUsbMassStorageEnabled(false); 372 } catch (Exception ex) { 373 Log.e(TAG, "Failed to disable UMS", ex); 374 } 375 } 376 377 /** 378 * Query if a USB Mass Storage (UMS) host is connected. 379 * @return true if UMS host is connected. 380 * 381 * @hide 382 */ 383 public boolean isUsbMassStorageConnected() { 384 try { 385 return mMountService.isUsbMassStorageConnected(); 386 } catch (Exception ex) { 387 Log.e(TAG, "Failed to get UMS connection state", ex); 388 } 389 return false; 390 } 391 392 /** 393 * Query if a USB Mass Storage (UMS) is enabled on the device. 394 * @return true if UMS host is enabled. 395 * 396 * @hide 397 */ 398 public boolean isUsbMassStorageEnabled() { 399 try { 400 return mMountService.isUsbMassStorageEnabled(); 401 } catch (RemoteException rex) { 402 Log.e(TAG, "Failed to get UMS enable state", rex); 403 } 404 return false; 405 } 406 407 /** 408 * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is 409 * specified, it is supplied to the mounting process to be used in any 410 * encryption used in the OBB. 411 * <p> 412 * The OBB will remain mounted for as long as the StorageManager reference 413 * is held by the application. As soon as this reference is lost, the OBBs 414 * in use will be unmounted. The {@link OnObbStateChangeListener} registered 415 * with this call will receive the success or failure of this operation. 416 * <p> 417 * <em>Note:</em> you can only mount OBB files for which the OBB tag on the 418 * file matches a package ID that is owned by the calling program's UID. 419 * That is, shared UID applications can attempt to mount any other 420 * application's OBB that shares its UID. 421 * 422 * @param filename the path to the OBB file 423 * @param key secret used to encrypt the OBB; may be <code>null</code> if no 424 * encryption was used on the OBB. 425 * @param listener will receive the success or failure of the operation 426 * @return whether the mount call was successfully queued or not 427 */ 428 public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) { 429 if (filename == null) { 430 throw new IllegalArgumentException("filename cannot be null"); 431 } 432 433 if (listener == null) { 434 throw new IllegalArgumentException("listener cannot be null"); 435 } 436 437 try { 438 final int nonce = mObbActionListener.addListener(listener); 439 mMountService.mountObb(filename, key, mObbActionListener, nonce); 440 return true; 441 } catch (RemoteException e) { 442 Log.e(TAG, "Failed to mount OBB", e); 443 } 444 445 return false; 446 } 447 448 /** 449 * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the 450 * <code>force</code> flag is true, it will kill any application needed to 451 * unmount the given OBB (even the calling application). 452 * <p> 453 * The {@link OnObbStateChangeListener} registered with this call will 454 * 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 obtain access to any other 459 * application's OBB that shares its UID. 460 * <p> 461 * 462 * @param filename path to the OBB file 463 * @param force whether to kill any programs using this in order to unmount 464 * it 465 * @param listener will receive the success or failure of the operation 466 * @return whether the unmount call was successfully queued or not 467 */ 468 public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) { 469 if (filename == null) { 470 throw new IllegalArgumentException("filename cannot be null"); 471 } 472 473 if (listener == null) { 474 throw new IllegalArgumentException("listener cannot be null"); 475 } 476 477 try { 478 final int nonce = mObbActionListener.addListener(listener); 479 mMountService.unmountObb(filename, force, mObbActionListener, nonce); 480 return true; 481 } catch (RemoteException e) { 482 Log.e(TAG, "Failed to mount OBB", e); 483 } 484 485 return false; 486 } 487 488 /** 489 * Check whether an Opaque Binary Blob (OBB) is mounted or not. 490 * 491 * @param filename path to OBB image 492 * @return true if OBB is mounted; false if not mounted or on error 493 */ 494 public boolean isObbMounted(String filename) { 495 if (filename == null) { 496 throw new IllegalArgumentException("filename cannot be null"); 497 } 498 499 try { 500 return mMountService.isObbMounted(filename); 501 } catch (RemoteException e) { 502 Log.e(TAG, "Failed to check if OBB is mounted", e); 503 } 504 505 return false; 506 } 507 508 /** 509 * Check the mounted path of an Opaque Binary Blob (OBB) file. This will 510 * give you the path to where you can obtain access to the internals of the 511 * OBB. 512 * 513 * @param filename path to OBB image 514 * @return absolute path to mounted OBB image data or <code>null</code> if 515 * not mounted or exception encountered trying to read status 516 */ 517 public String getMountedObbPath(String filename) { 518 if (filename == null) { 519 throw new IllegalArgumentException("filename cannot be null"); 520 } 521 522 try { 523 return mMountService.getMountedObbPath(filename); 524 } catch (RemoteException e) { 525 Log.e(TAG, "Failed to find mounted path for OBB", e); 526 } 527 528 return null; 529 } 530} 531