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