StorageManager.java revision 0a9b54e88b9cbb30748b5f0b331aec3f3ef8d639
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 */ 316 public void registerListener(StorageEventListener listener) { 317 if (listener == null) { 318 return; 319 } 320 321 synchronized (mListeners) { 322 mListeners.add(new ListenerDelegate(listener)); 323 } 324 } 325 326 /** 327 * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}. 328 * 329 * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object. 330 * 331 */ 332 public void unregisterListener(StorageEventListener listener) { 333 if (listener == null) { 334 return; 335 } 336 337 synchronized (mListeners) { 338 final int size = mListeners.size(); 339 for (int i=0 ; i<size ; i++) { 340 ListenerDelegate l = mListeners.get(i); 341 if (l.getListener() == listener) { 342 mListeners.remove(i); 343 break; 344 } 345 } 346 } 347 } 348 349 /** 350 * Enables USB Mass Storage (UMS) on the device. 351 */ 352 public void enableUsbMassStorage() { 353 try { 354 mMountService.setUsbMassStorageEnabled(true); 355 } catch (Exception ex) { 356 Log.e(TAG, "Failed to enable UMS", ex); 357 } 358 } 359 360 /** 361 * Disables USB Mass Storage (UMS) on the device. 362 */ 363 public void disableUsbMassStorage() { 364 try { 365 mMountService.setUsbMassStorageEnabled(false); 366 } catch (Exception ex) { 367 Log.e(TAG, "Failed to disable UMS", ex); 368 } 369 } 370 371 /** 372 * Query if a USB Mass Storage (UMS) host is connected. 373 * @return true if UMS host is connected. 374 */ 375 public boolean isUsbMassStorageConnected() { 376 try { 377 return mMountService.isUsbMassStorageConnected(); 378 } catch (Exception ex) { 379 Log.e(TAG, "Failed to get UMS connection state", ex); 380 } 381 return false; 382 } 383 384 /** 385 * Query if a USB Mass Storage (UMS) is enabled on the device. 386 * @return true if UMS host is enabled. 387 */ 388 public boolean isUsbMassStorageEnabled() { 389 try { 390 return mMountService.isUsbMassStorageEnabled(); 391 } catch (RemoteException rex) { 392 Log.e(TAG, "Failed to get UMS enable state", rex); 393 } 394 return false; 395 } 396 397 /** 398 * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is 399 * specified, it is supplied to the mounting process to be used in any 400 * encryption used in the OBB. 401 * <p> 402 * The OBB will remain mounted for as long as the StorageManager reference 403 * is held by the application. As soon as this reference is lost, the OBBs 404 * in use will be unmounted. The {@link OnObbStateChangeListener} registered 405 * with this call will receive the success or failure of this operation. 406 * <p> 407 * <em>Note:</em> you can only mount OBB files for which the OBB tag on the 408 * file matches a package ID that is owned by the calling program's UID. 409 * That is, shared UID applications can attempt to mount any other 410 * application's OBB that shares its UID. 411 * 412 * @param filename the path to the OBB file 413 * @param key secret used to encrypt the OBB; may be <code>null</code> if no 414 * encryption was used on the OBB. 415 * @param listener will receive the success or failure of the operation 416 * @return whether the mount call was successfully queued or not 417 */ 418 public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) { 419 if (filename == null) { 420 throw new IllegalArgumentException("filename cannot be null"); 421 } 422 423 if (listener == null) { 424 throw new IllegalArgumentException("listener cannot be null"); 425 } 426 427 try { 428 final int nonce = mObbActionListener.addListener(listener); 429 mMountService.mountObb(filename, key, mObbActionListener, nonce); 430 return true; 431 } catch (RemoteException e) { 432 Log.e(TAG, "Failed to mount OBB", e); 433 } 434 435 return false; 436 } 437 438 /** 439 * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the 440 * <code>force</code> flag is true, it will kill any application needed to 441 * unmount the given OBB (even the calling application). 442 * <p> 443 * The {@link OnObbStateChangeListener} registered with this call will 444 * receive the success or failure of this operation. 445 * <p> 446 * <em>Note:</em> you can only mount OBB files for which the OBB tag on the 447 * file matches a package ID that is owned by the calling program's UID. 448 * That is, shared UID applications can obtain access to any other 449 * application's OBB that shares its UID. 450 * <p> 451 * 452 * @param filename path to the OBB file 453 * @param force whether to kill any programs using this in order to unmount 454 * it 455 * @param listener will receive the success or failure of the operation 456 * @return whether the unmount call was successfully queued or not 457 */ 458 public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) { 459 if (filename == null) { 460 throw new IllegalArgumentException("filename cannot be null"); 461 } 462 463 if (listener == null) { 464 throw new IllegalArgumentException("listener cannot be null"); 465 } 466 467 try { 468 final int nonce = mObbActionListener.addListener(listener); 469 mMountService.unmountObb(filename, force, mObbActionListener, nonce); 470 return true; 471 } catch (RemoteException e) { 472 Log.e(TAG, "Failed to mount OBB", e); 473 } 474 475 return false; 476 } 477 478 /** 479 * Check whether an Opaque Binary Blob (OBB) is mounted or not. 480 * 481 * @param filename path to OBB image 482 * @return true if OBB is mounted; false if not mounted or on error 483 */ 484 public boolean isObbMounted(String filename) { 485 if (filename == null) { 486 throw new IllegalArgumentException("filename cannot be null"); 487 } 488 489 try { 490 return mMountService.isObbMounted(filename); 491 } catch (RemoteException e) { 492 Log.e(TAG, "Failed to check if OBB is mounted", e); 493 } 494 495 return false; 496 } 497 498 /** 499 * Check the mounted path of an Opaque Binary Blob (OBB) file. This will 500 * give you the path to where you can obtain access to the internals of the 501 * OBB. 502 * 503 * @param filename path to OBB image 504 * @return absolute path to mounted OBB image data or <code>null</code> if 505 * not mounted or exception encountered trying to read status 506 */ 507 public String getMountedObbPath(String filename) { 508 if (filename == null) { 509 throw new IllegalArgumentException("filename cannot be null"); 510 } 511 512 try { 513 return mMountService.getMountedObbPath(filename); 514 } catch (RemoteException e) { 515 Log.e(TAG, "Failed to find mounted path for OBB", e); 516 } 517 518 return null; 519 } 520} 521