MountService.java revision a5250c93928e256738125b265e10c96c3575597e
1/* 2 * Copyright (C) 2007 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 com.android.server; 18 19import com.android.internal.app.IMediaContainerService; 20import com.android.internal.util.XmlUtils; 21import com.android.server.am.ActivityManagerService; 22 23import android.Manifest; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.ServiceConnection; 30import android.content.pm.PackageManager; 31import android.content.res.ObbInfo; 32import android.content.res.Resources; 33import android.content.res.TypedArray; 34import android.content.res.XmlResourceParser; 35import android.net.Uri; 36import android.os.Binder; 37import android.os.Environment; 38import android.os.Handler; 39import android.os.HandlerThread; 40import android.os.IBinder; 41import android.os.Looper; 42import android.os.Message; 43import android.os.Parcelable; 44import android.os.RemoteException; 45import android.os.ServiceManager; 46import android.os.SystemClock; 47import android.os.SystemProperties; 48import android.os.storage.IMountService; 49import android.os.storage.IMountServiceListener; 50import android.os.storage.IMountShutdownObserver; 51import android.os.storage.IObbActionListener; 52import android.os.storage.OnObbStateChangeListener; 53import android.os.storage.StorageResultCode; 54import android.os.storage.StorageVolume; 55import android.text.TextUtils; 56import android.util.AttributeSet; 57import android.util.Slog; 58import android.util.Xml; 59 60import org.xmlpull.v1.XmlPullParser; 61import org.xmlpull.v1.XmlPullParserException; 62 63import java.io.FileDescriptor; 64import java.io.IOException; 65import java.io.PrintWriter; 66import java.math.BigInteger; 67import java.security.NoSuchAlgorithmException; 68import java.security.spec.InvalidKeySpecException; 69import java.security.spec.KeySpec; 70import java.util.ArrayList; 71import java.util.HashMap; 72import java.util.HashSet; 73import java.util.Iterator; 74import java.util.LinkedList; 75import java.util.List; 76import java.util.Map; 77import java.util.Map.Entry; 78import java.util.Set; 79 80import javax.crypto.SecretKey; 81import javax.crypto.SecretKeyFactory; 82import javax.crypto.spec.PBEKeySpec; 83 84/** 85 * MountService implements back-end services for platform storage 86 * management. 87 * @hide - Applications should use android.os.storage.StorageManager 88 * to access the MountService. 89 */ 90class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks { 91 92 private static final boolean LOCAL_LOGD = false; 93 private static final boolean DEBUG_UNMOUNT = false; 94 private static final boolean DEBUG_EVENTS = false; 95 private static final boolean DEBUG_OBB = false; 96 97 private static final String TAG = "MountService"; 98 99 private static final String VOLD_TAG = "VoldConnector"; 100 101 /* 102 * Internal vold volume state constants 103 */ 104 class VolumeState { 105 public static final int Init = -1; 106 public static final int NoMedia = 0; 107 public static final int Idle = 1; 108 public static final int Pending = 2; 109 public static final int Checking = 3; 110 public static final int Mounted = 4; 111 public static final int Unmounting = 5; 112 public static final int Formatting = 6; 113 public static final int Shared = 7; 114 public static final int SharedMnt = 8; 115 } 116 117 /* 118 * Internal vold response code constants 119 */ 120 class VoldResponseCode { 121 /* 122 * 100 series - Requestion action was initiated; expect another reply 123 * before proceeding with a new command. 124 */ 125 public static final int VolumeListResult = 110; 126 public static final int AsecListResult = 111; 127 public static final int StorageUsersListResult = 112; 128 129 /* 130 * 200 series - Requestion action has been successfully completed. 131 */ 132 public static final int ShareStatusResult = 210; 133 public static final int AsecPathResult = 211; 134 public static final int ShareEnabledResult = 212; 135 136 /* 137 * 400 series - Command was accepted, but the requested action 138 * did not take place. 139 */ 140 public static final int OpFailedNoMedia = 401; 141 public static final int OpFailedMediaBlank = 402; 142 public static final int OpFailedMediaCorrupt = 403; 143 public static final int OpFailedVolNotMounted = 404; 144 public static final int OpFailedStorageBusy = 405; 145 public static final int OpFailedStorageNotFound = 406; 146 147 /* 148 * 600 series - Unsolicited broadcasts. 149 */ 150 public static final int VolumeStateChange = 605; 151 public static final int ShareAvailabilityChange = 620; 152 public static final int VolumeDiskInserted = 630; 153 public static final int VolumeDiskRemoved = 631; 154 public static final int VolumeBadRemoval = 632; 155 } 156 157 private Context mContext; 158 private NativeDaemonConnector mConnector; 159 private final ArrayList<StorageVolume> mVolumes = new ArrayList<StorageVolume>(); 160 private StorageVolume mPrimaryVolume; 161 private final HashMap<String, String> mVolumeStates = new HashMap<String, String>(); 162 private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>(); 163 private String mExternalStoragePath; 164 private PackageManagerService mPms; 165 private boolean mUmsEnabling; 166 // Used as a lock for methods that register/unregister listeners. 167 final private ArrayList<MountServiceBinderListener> mListeners = 168 new ArrayList<MountServiceBinderListener>(); 169 private boolean mBooted = false; 170 private boolean mReady = false; 171 private boolean mSendUmsConnectedOnBoot = false; 172 // true if we should fake MEDIA_MOUNTED state for external storage 173 private boolean mEmulateExternalStorage = false; 174 175 /** 176 * Private hash of currently mounted secure containers. 177 * Used as a lock in methods to manipulate secure containers. 178 */ 179 final private HashSet<String> mAsecMountSet = new HashSet<String>(); 180 181 /** 182 * The size of the crypto algorithm key in bits for OBB files. Currently 183 * Twofish is used which takes 128-bit keys. 184 */ 185 private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128; 186 187 /** 188 * The number of times to run SHA1 in the PBKDF2 function for OBB files. 189 * 1024 is reasonably secure and not too slow. 190 */ 191 private static final int PBKDF2_HASH_ROUNDS = 1024; 192 193 /** 194 * Mounted OBB tracking information. Used to track the current state of all 195 * OBBs. 196 */ 197 final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>(); 198 final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); 199 200 class ObbState implements IBinder.DeathRecipient { 201 public ObbState(String filename, int callerUid, IObbActionListener token, int nonce) 202 throws RemoteException { 203 this.filename = filename; 204 this.callerUid = callerUid; 205 this.token = token; 206 this.nonce = nonce; 207 } 208 209 // OBB source filename 210 String filename; 211 212 // Binder.callingUid() 213 final public int callerUid; 214 215 // Token of remote Binder caller 216 final IObbActionListener token; 217 218 // Identifier to pass back to the token 219 final int nonce; 220 221 public IBinder getBinder() { 222 return token.asBinder(); 223 } 224 225 @Override 226 public void binderDied() { 227 ObbAction action = new UnmountObbAction(this, true); 228 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 229 } 230 231 public void link() throws RemoteException { 232 getBinder().linkToDeath(this, 0); 233 } 234 235 public void unlink() { 236 getBinder().unlinkToDeath(this, 0); 237 } 238 239 @Override 240 public String toString() { 241 StringBuilder sb = new StringBuilder("ObbState{"); 242 sb.append("filename="); 243 sb.append(filename); 244 sb.append(",token="); 245 sb.append(token.toString()); 246 sb.append(",callerUid="); 247 sb.append(callerUid); 248 sb.append('}'); 249 return sb.toString(); 250 } 251 } 252 253 // OBB Action Handler 254 final private ObbActionHandler mObbActionHandler; 255 256 // OBB action handler messages 257 private static final int OBB_RUN_ACTION = 1; 258 private static final int OBB_MCS_BOUND = 2; 259 private static final int OBB_MCS_UNBIND = 3; 260 private static final int OBB_MCS_RECONNECT = 4; 261 private static final int OBB_FLUSH_MOUNT_STATE = 5; 262 263 /* 264 * Default Container Service information 265 */ 266 static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( 267 "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); 268 269 final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); 270 271 class DefaultContainerConnection implements ServiceConnection { 272 public void onServiceConnected(ComponentName name, IBinder service) { 273 if (DEBUG_OBB) 274 Slog.i(TAG, "onServiceConnected"); 275 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); 276 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); 277 } 278 279 public void onServiceDisconnected(ComponentName name) { 280 if (DEBUG_OBB) 281 Slog.i(TAG, "onServiceDisconnected"); 282 } 283 }; 284 285 // Used in the ObbActionHandler 286 private IMediaContainerService mContainerService = null; 287 288 // Handler messages 289 private static final int H_UNMOUNT_PM_UPDATE = 1; 290 private static final int H_UNMOUNT_PM_DONE = 2; 291 private static final int H_UNMOUNT_MS = 3; 292 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms 293 private static final int MAX_UNMOUNT_RETRIES = 4; 294 295 class UnmountCallBack { 296 final String path; 297 final boolean force; 298 int retries; 299 300 UnmountCallBack(String path, boolean force) { 301 retries = 0; 302 this.path = path; 303 this.force = force; 304 } 305 306 void handleFinished() { 307 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); 308 doUnmountVolume(path, true); 309 } 310 } 311 312 class UmsEnableCallBack extends UnmountCallBack { 313 final String method; 314 315 UmsEnableCallBack(String path, String method, boolean force) { 316 super(path, force); 317 this.method = method; 318 } 319 320 @Override 321 void handleFinished() { 322 super.handleFinished(); 323 doShareUnshareVolume(path, method, true); 324 } 325 } 326 327 class ShutdownCallBack extends UnmountCallBack { 328 IMountShutdownObserver observer; 329 ShutdownCallBack(String path, IMountShutdownObserver observer) { 330 super(path, true); 331 this.observer = observer; 332 } 333 334 @Override 335 void handleFinished() { 336 int ret = doUnmountVolume(path, true); 337 if (observer != null) { 338 try { 339 observer.onShutDownComplete(ret); 340 } catch (RemoteException e) { 341 Slog.w(TAG, "RemoteException when shutting down"); 342 } 343 } 344 } 345 } 346 347 class MountServiceHandler extends Handler { 348 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); 349 boolean mUpdatingStatus = false; 350 351 MountServiceHandler(Looper l) { 352 super(l); 353 } 354 355 @Override 356 public void handleMessage(Message msg) { 357 switch (msg.what) { 358 case H_UNMOUNT_PM_UPDATE: { 359 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); 360 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 361 mForceUnmounts.add(ucb); 362 if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); 363 // Register only if needed. 364 if (!mUpdatingStatus) { 365 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); 366 mUpdatingStatus = true; 367 mPms.updateExternalMediaStatus(false, true); 368 } 369 break; 370 } 371 case H_UNMOUNT_PM_DONE: { 372 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); 373 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); 374 mUpdatingStatus = false; 375 int size = mForceUnmounts.size(); 376 int sizeArr[] = new int[size]; 377 int sizeArrN = 0; 378 // Kill processes holding references first 379 ActivityManagerService ams = (ActivityManagerService) 380 ServiceManager.getService("activity"); 381 for (int i = 0; i < size; i++) { 382 UnmountCallBack ucb = mForceUnmounts.get(i); 383 String path = ucb.path; 384 boolean done = false; 385 if (!ucb.force) { 386 done = true; 387 } else { 388 int pids[] = getStorageUsers(path); 389 if (pids == null || pids.length == 0) { 390 done = true; 391 } else { 392 // Eliminate system process here? 393 ams.killPids(pids, "unmount media", true); 394 // Confirm if file references have been freed. 395 pids = getStorageUsers(path); 396 if (pids == null || pids.length == 0) { 397 done = true; 398 } 399 } 400 } 401 if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { 402 // Retry again 403 Slog.i(TAG, "Retrying to kill storage users again"); 404 mHandler.sendMessageDelayed( 405 mHandler.obtainMessage(H_UNMOUNT_PM_DONE, 406 ucb.retries++), 407 RETRY_UNMOUNT_DELAY); 408 } else { 409 if (ucb.retries >= MAX_UNMOUNT_RETRIES) { 410 Slog.i(TAG, "Failed to unmount media inspite of " + 411 MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); 412 } 413 sizeArr[sizeArrN++] = i; 414 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, 415 ucb)); 416 } 417 } 418 // Remove already processed elements from list. 419 for (int i = (sizeArrN-1); i >= 0; i--) { 420 mForceUnmounts.remove(sizeArr[i]); 421 } 422 break; 423 } 424 case H_UNMOUNT_MS : { 425 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); 426 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 427 ucb.handleFinished(); 428 break; 429 } 430 } 431 } 432 }; 433 final private HandlerThread mHandlerThread; 434 final private Handler mHandler; 435 436 private void waitForReady() { 437 while (mReady == false) { 438 for (int retries = 5; retries > 0; retries--) { 439 if (mReady) { 440 return; 441 } 442 SystemClock.sleep(1000); 443 } 444 Slog.w(TAG, "Waiting too long for mReady!"); 445 } 446 } 447 448 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 449 @Override 450 public void onReceive(Context context, Intent intent) { 451 String action = intent.getAction(); 452 453 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 454 mBooted = true; 455 456 /* 457 * In the simulator, we need to broadcast a volume mounted event 458 * to make the media scanner run. 459 */ 460 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 461 notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, 462 VolumeState.Mounted); 463 return; 464 } 465 new Thread() { 466 @Override 467 public void run() { 468 try { 469 // it is not safe to call vold with mVolumeStates locked 470 // so we make a copy of the paths and states and process them 471 // outside the lock 472 String[] paths, states; 473 int count; 474 synchronized (mVolumeStates) { 475 Set<String> keys = mVolumeStates.keySet(); 476 count = keys.size(); 477 paths = (String[])keys.toArray(new String[count]); 478 states = new String[count]; 479 for (int i = 0; i < count; i++) { 480 states[i] = mVolumeStates.get(paths[i]); 481 } 482 } 483 484 for (int i = 0; i < count; i++) { 485 String path = paths[i]; 486 String state = states[i]; 487 488 if (state.equals(Environment.MEDIA_UNMOUNTED)) { 489 int rc = doMountVolume(path); 490 if (rc != StorageResultCode.OperationSucceeded) { 491 Slog.e(TAG, String.format("Boot-time mount failed (%d)", 492 rc)); 493 } 494 } else if (state.equals(Environment.MEDIA_SHARED)) { 495 /* 496 * Bootstrap UMS enabled state since vold indicates 497 * the volume is shared (runtime restart while ums enabled) 498 */ 499 notifyVolumeStateChange(null, path, VolumeState.NoMedia, 500 VolumeState.Shared); 501 } 502 } 503 504 /* notify external storage has mounted to trigger media scanner */ 505 if (mEmulateExternalStorage) { 506 notifyVolumeStateChange(null, 507 Environment.getExternalStorageDirectory().getPath(), 508 VolumeState.NoMedia, VolumeState.Mounted); 509 } 510 511 /* 512 * If UMS was connected on boot, send the connected event 513 * now that we're up. 514 */ 515 if (mSendUmsConnectedOnBoot) { 516 sendUmsIntent(true); 517 mSendUmsConnectedOnBoot = false; 518 } 519 } catch (Exception ex) { 520 Slog.e(TAG, "Boot-time mount exception", ex); 521 } 522 } 523 }.start(); 524 } 525 } 526 }; 527 528 private final class MountServiceBinderListener implements IBinder.DeathRecipient { 529 final IMountServiceListener mListener; 530 531 MountServiceBinderListener(IMountServiceListener listener) { 532 mListener = listener; 533 534 } 535 536 public void binderDied() { 537 if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); 538 synchronized (mListeners) { 539 mListeners.remove(this); 540 mListener.asBinder().unlinkToDeath(this, 0); 541 } 542 } 543 } 544 545 private void doShareUnshareVolume(String path, String method, boolean enable) { 546 // TODO: Add support for multiple share methods 547 if (!method.equals("ums")) { 548 throw new IllegalArgumentException(String.format("Method %s not supported", method)); 549 } 550 551 try { 552 mConnector.doCommand(String.format( 553 "volume %sshare %s %s", (enable ? "" : "un"), path, method)); 554 } catch (NativeDaemonConnectorException e) { 555 Slog.e(TAG, "Failed to share/unshare", e); 556 } 557 } 558 559 private void updatePublicVolumeState(String path, String state) { 560 String oldState; 561 synchronized(mVolumeStates) { 562 oldState = mVolumeStates.put(path, state); 563 } 564 if (state.equals(oldState)) { 565 Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", 566 state, state, path)); 567 return; 568 } 569 570 Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); 571 572 if (path.equals(mExternalStoragePath)) { 573 // Update state on PackageManager, but only of real events 574 if (!mEmulateExternalStorage) { 575 if (Environment.MEDIA_UNMOUNTED.equals(state)) { 576 mPms.updateExternalMediaStatus(false, false); 577 578 /* 579 * Some OBBs might have been unmounted when this volume was 580 * unmounted, so send a message to the handler to let it know to 581 * remove those from the list of mounted OBBS. 582 */ 583 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( 584 OBB_FLUSH_MOUNT_STATE, path)); 585 } else if (Environment.MEDIA_MOUNTED.equals(state)) { 586 mPms.updateExternalMediaStatus(true, false); 587 } 588 } 589 } 590 synchronized (mListeners) { 591 for (int i = mListeners.size() -1; i >= 0; i--) { 592 MountServiceBinderListener bl = mListeners.get(i); 593 try { 594 bl.mListener.onStorageStateChanged(path, oldState, state); 595 } catch (RemoteException rex) { 596 Slog.e(TAG, "Listener dead"); 597 mListeners.remove(i); 598 } catch (Exception ex) { 599 Slog.e(TAG, "Listener failed", ex); 600 } 601 } 602 } 603 } 604 605 /** 606 * 607 * Callback from NativeDaemonConnector 608 */ 609 public void onDaemonConnected() { 610 /* 611 * Since we'll be calling back into the NativeDaemonConnector, 612 * we need to do our work in a new thread. 613 */ 614 new Thread() { 615 @Override 616 public void run() { 617 /** 618 * Determine media state and UMS detection status 619 */ 620 try { 621 String[] vols = mConnector.doListCommand( 622 "volume list", VoldResponseCode.VolumeListResult); 623 for (String volstr : vols) { 624 String[] tok = volstr.split(" "); 625 // FMT: <label> <mountpoint> <state> 626 String path = tok[1]; 627 String state = Environment.MEDIA_REMOVED; 628 629 int st = Integer.parseInt(tok[2]); 630 if (st == VolumeState.NoMedia) { 631 state = Environment.MEDIA_REMOVED; 632 } else if (st == VolumeState.Idle) { 633 state = Environment.MEDIA_UNMOUNTED; 634 } else if (st == VolumeState.Mounted) { 635 state = Environment.MEDIA_MOUNTED; 636 Slog.i(TAG, "Media already mounted on daemon connection"); 637 } else if (st == VolumeState.Shared) { 638 state = Environment.MEDIA_SHARED; 639 Slog.i(TAG, "Media shared on daemon connection"); 640 } else { 641 throw new Exception(String.format("Unexpected state %d", st)); 642 } 643 644 if (state != null) { 645 if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); 646 updatePublicVolumeState(path, state); 647 } 648 } 649 } catch (Exception e) { 650 Slog.e(TAG, "Error processing initial volume state", e); 651 updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED); 652 } 653 654 try { 655 boolean avail = doGetShareMethodAvailable("ums"); 656 notifyShareAvailabilityChange("ums", avail); 657 } catch (Exception ex) { 658 Slog.w(TAG, "Failed to get share availability"); 659 } 660 /* 661 * Now that we've done our initialization, release 662 * the hounds! 663 */ 664 mReady = true; 665 } 666 }.start(); 667 } 668 669 /** 670 * Callback from NativeDaemonConnector 671 */ 672 public boolean onEvent(int code, String raw, String[] cooked) { 673 if (DEBUG_EVENTS) { 674 StringBuilder builder = new StringBuilder(); 675 builder.append("onEvent::"); 676 builder.append(" raw= " + raw); 677 if (cooked != null) { 678 builder.append(" cooked = " ); 679 for (String str : cooked) { 680 builder.append(" " + str); 681 } 682 } 683 Slog.i(TAG, builder.toString()); 684 } 685 if (code == VoldResponseCode.VolumeStateChange) { 686 /* 687 * One of the volumes we're managing has changed state. 688 * Format: "NNN Volume <label> <path> state changed 689 * from <old_#> (<old_str>) to <new_#> (<new_str>)" 690 */ 691 notifyVolumeStateChange( 692 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 693 Integer.parseInt(cooked[10])); 694 } else if (code == VoldResponseCode.ShareAvailabilityChange) { 695 // FMT: NNN Share method <method> now <available|unavailable> 696 boolean avail = false; 697 if (cooked[5].equals("available")) { 698 avail = true; 699 } 700 notifyShareAvailabilityChange(cooked[3], avail); 701 } else if ((code == VoldResponseCode.VolumeDiskInserted) || 702 (code == VoldResponseCode.VolumeDiskRemoved) || 703 (code == VoldResponseCode.VolumeBadRemoval)) { 704 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 705 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 706 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 707 String action = null; 708 final String label = cooked[2]; 709 final String path = cooked[3]; 710 int major = -1; 711 int minor = -1; 712 713 try { 714 String devComp = cooked[6].substring(1, cooked[6].length() -1); 715 String[] devTok = devComp.split(":"); 716 major = Integer.parseInt(devTok[0]); 717 minor = Integer.parseInt(devTok[1]); 718 } catch (Exception ex) { 719 Slog.e(TAG, "Failed to parse major/minor", ex); 720 } 721 722 if (code == VoldResponseCode.VolumeDiskInserted) { 723 new Thread() { 724 @Override 725 public void run() { 726 try { 727 int rc; 728 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 729 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc)); 730 } 731 } catch (Exception ex) { 732 Slog.w(TAG, "Failed to mount media on insertion", ex); 733 } 734 } 735 }.start(); 736 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 737 /* 738 * This event gets trumped if we're already in BAD_REMOVAL state 739 */ 740 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 741 return true; 742 } 743 /* Send the media unmounted event first */ 744 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 745 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 746 sendStorageIntent(Environment.MEDIA_UNMOUNTED, path); 747 748 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); 749 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 750 action = Intent.ACTION_MEDIA_REMOVED; 751 } else if (code == VoldResponseCode.VolumeBadRemoval) { 752 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 753 /* Send the media unmounted event first */ 754 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 755 action = Intent.ACTION_MEDIA_UNMOUNTED; 756 757 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); 758 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); 759 action = Intent.ACTION_MEDIA_BAD_REMOVAL; 760 } else { 761 Slog.e(TAG, String.format("Unknown code {%d}", code)); 762 } 763 764 if (action != null) { 765 sendStorageIntent(action, path); 766 } 767 } else { 768 return false; 769 } 770 771 return true; 772 } 773 774 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { 775 String vs = getVolumeState(path); 776 if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs); 777 778 String action = null; 779 780 if (oldState == VolumeState.Shared && newState != oldState) { 781 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); 782 sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, path); 783 } 784 785 if (newState == VolumeState.Init) { 786 } else if (newState == VolumeState.NoMedia) { 787 // NoMedia is handled via Disk Remove events 788 } else if (newState == VolumeState.Idle) { 789 /* 790 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 791 * if we're in the process of enabling UMS 792 */ 793 if (!vs.equals( 794 Environment.MEDIA_BAD_REMOVAL) && !vs.equals( 795 Environment.MEDIA_NOFS) && !vs.equals( 796 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { 797 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); 798 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 799 action = Intent.ACTION_MEDIA_UNMOUNTED; 800 } 801 } else if (newState == VolumeState.Pending) { 802 } else if (newState == VolumeState.Checking) { 803 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); 804 updatePublicVolumeState(path, Environment.MEDIA_CHECKING); 805 action = Intent.ACTION_MEDIA_CHECKING; 806 } else if (newState == VolumeState.Mounted) { 807 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); 808 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); 809 action = Intent.ACTION_MEDIA_MOUNTED; 810 } else if (newState == VolumeState.Unmounting) { 811 action = Intent.ACTION_MEDIA_EJECT; 812 } else if (newState == VolumeState.Formatting) { 813 } else if (newState == VolumeState.Shared) { 814 if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); 815 /* Send the media unmounted event first */ 816 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 817 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, path); 818 819 if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); 820 updatePublicVolumeState(path, Environment.MEDIA_SHARED); 821 action = Intent.ACTION_MEDIA_SHARED; 822 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); 823 } else if (newState == VolumeState.SharedMnt) { 824 Slog.e(TAG, "Live shared mounts not supported yet!"); 825 return; 826 } else { 827 Slog.e(TAG, "Unhandled VolumeState {" + newState + "}"); 828 } 829 830 if (action != null) { 831 sendStorageIntent(action, path); 832 } 833 } 834 835 private boolean doGetShareMethodAvailable(String method) { 836 ArrayList<String> rsp; 837 try { 838 rsp = mConnector.doCommand("share status " + method); 839 } catch (NativeDaemonConnectorException ex) { 840 Slog.e(TAG, "Failed to determine whether share method " + method + " is available."); 841 return false; 842 } 843 844 for (String line : rsp) { 845 String[] tok = line.split(" "); 846 if (tok.length < 3) { 847 Slog.e(TAG, "Malformed response to share status " + method); 848 return false; 849 } 850 851 int code; 852 try { 853 code = Integer.parseInt(tok[0]); 854 } catch (NumberFormatException nfe) { 855 Slog.e(TAG, String.format("Error parsing code %s", tok[0])); 856 return false; 857 } 858 if (code == VoldResponseCode.ShareStatusResult) { 859 if (tok[2].equals("available")) 860 return true; 861 return false; 862 } else { 863 Slog.e(TAG, String.format("Unexpected response code %d", code)); 864 return false; 865 } 866 } 867 Slog.e(TAG, "Got an empty response"); 868 return false; 869 } 870 871 private int doMountVolume(String path) { 872 int rc = StorageResultCode.OperationSucceeded; 873 874 if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); 875 try { 876 mConnector.doCommand(String.format("volume mount %s", path)); 877 } catch (NativeDaemonConnectorException e) { 878 /* 879 * Mount failed for some reason 880 */ 881 String action = null; 882 int code = e.getCode(); 883 if (code == VoldResponseCode.OpFailedNoMedia) { 884 /* 885 * Attempt to mount but no media inserted 886 */ 887 rc = StorageResultCode.OperationFailedNoMedia; 888 } else if (code == VoldResponseCode.OpFailedMediaBlank) { 889 if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); 890 /* 891 * Media is blank or does not contain a supported filesystem 892 */ 893 updatePublicVolumeState(path, Environment.MEDIA_NOFS); 894 action = Intent.ACTION_MEDIA_NOFS; 895 rc = StorageResultCode.OperationFailedMediaBlank; 896 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 897 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); 898 /* 899 * Volume consistency check failed 900 */ 901 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); 902 action = Intent.ACTION_MEDIA_UNMOUNTABLE; 903 rc = StorageResultCode.OperationFailedMediaCorrupt; 904 } else { 905 rc = StorageResultCode.OperationFailedInternalError; 906 } 907 908 /* 909 * Send broadcast intent (if required for the failure) 910 */ 911 if (action != null) { 912 sendStorageIntent(action, path); 913 } 914 } 915 916 return rc; 917 } 918 919 /* 920 * If force is not set, we do not unmount if there are 921 * processes holding references to the volume about to be unmounted. 922 * If force is set, all the processes holding references need to be 923 * killed via the ActivityManager before actually unmounting the volume. 924 * This might even take a while and might be retried after timed delays 925 * to make sure we dont end up in an instable state and kill some core 926 * processes. 927 */ 928 private int doUnmountVolume(String path, boolean force) { 929 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { 930 return VoldResponseCode.OpFailedVolNotMounted; 931 } 932 933 /* 934 * Force a GC to make sure AssetManagers in other threads of the 935 * system_server are cleaned up. We have to do this since AssetManager 936 * instances are kept as a WeakReference and it's possible we have files 937 * open on the external storage. 938 */ 939 Runtime.getRuntime().gc(); 940 941 // Redundant probably. But no harm in updating state again. 942 mPms.updateExternalMediaStatus(false, false); 943 try { 944 mConnector.doCommand(String.format( 945 "volume unmount %s%s", path, (force ? " force" : ""))); 946 // We unmounted the volume. None of the asec containers are available now. 947 synchronized (mAsecMountSet) { 948 mAsecMountSet.clear(); 949 } 950 return StorageResultCode.OperationSucceeded; 951 } catch (NativeDaemonConnectorException e) { 952 // Don't worry about mismatch in PackageManager since the 953 // call back will handle the status changes any way. 954 int code = e.getCode(); 955 if (code == VoldResponseCode.OpFailedVolNotMounted) { 956 return StorageResultCode.OperationFailedStorageNotMounted; 957 } else if (code == VoldResponseCode.OpFailedStorageBusy) { 958 return StorageResultCode.OperationFailedStorageBusy; 959 } else { 960 return StorageResultCode.OperationFailedInternalError; 961 } 962 } 963 } 964 965 private int doFormatVolume(String path) { 966 try { 967 String cmd = String.format("volume format %s", path); 968 mConnector.doCommand(cmd); 969 return StorageResultCode.OperationSucceeded; 970 } catch (NativeDaemonConnectorException e) { 971 int code = e.getCode(); 972 if (code == VoldResponseCode.OpFailedNoMedia) { 973 return StorageResultCode.OperationFailedNoMedia; 974 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 975 return StorageResultCode.OperationFailedMediaCorrupt; 976 } else { 977 return StorageResultCode.OperationFailedInternalError; 978 } 979 } 980 } 981 982 private boolean doGetVolumeShared(String path, String method) { 983 String cmd = String.format("volume shared %s %s", path, method); 984 ArrayList<String> rsp; 985 986 try { 987 rsp = mConnector.doCommand(cmd); 988 } catch (NativeDaemonConnectorException ex) { 989 Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); 990 return false; 991 } 992 993 for (String line : rsp) { 994 String[] tok = line.split(" "); 995 if (tok.length < 3) { 996 Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); 997 return false; 998 } 999 1000 int code; 1001 try { 1002 code = Integer.parseInt(tok[0]); 1003 } catch (NumberFormatException nfe) { 1004 Slog.e(TAG, String.format("Error parsing code %s", tok[0])); 1005 return false; 1006 } 1007 if (code == VoldResponseCode.ShareEnabledResult) { 1008 return "enabled".equals(tok[2]); 1009 } else { 1010 Slog.e(TAG, String.format("Unexpected response code %d", code)); 1011 return false; 1012 } 1013 } 1014 Slog.e(TAG, "Got an empty response"); 1015 return false; 1016 } 1017 1018 private void notifyShareAvailabilityChange(String method, final boolean avail) { 1019 if (!method.equals("ums")) { 1020 Slog.w(TAG, "Ignoring unsupported share method {" + method + "}"); 1021 return; 1022 } 1023 1024 synchronized (mListeners) { 1025 for (int i = mListeners.size() -1; i >= 0; i--) { 1026 MountServiceBinderListener bl = mListeners.get(i); 1027 try { 1028 bl.mListener.onUsbMassStorageConnectionChanged(avail); 1029 } catch (RemoteException rex) { 1030 Slog.e(TAG, "Listener dead"); 1031 mListeners.remove(i); 1032 } catch (Exception ex) { 1033 Slog.e(TAG, "Listener failed", ex); 1034 } 1035 } 1036 } 1037 1038 if (mBooted == true) { 1039 sendUmsIntent(avail); 1040 } else { 1041 mSendUmsConnectedOnBoot = avail; 1042 } 1043 1044 final String path = Environment.getExternalStorageDirectory().getPath(); 1045 if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) { 1046 /* 1047 * USB mass storage disconnected while enabled 1048 */ 1049 new Thread() { 1050 @Override 1051 public void run() { 1052 try { 1053 int rc; 1054 Slog.w(TAG, "Disabling UMS after cable disconnect"); 1055 doShareUnshareVolume(path, "ums", false); 1056 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 1057 Slog.e(TAG, String.format( 1058 "Failed to remount {%s} on UMS enabled-disconnect (%d)", 1059 path, rc)); 1060 } 1061 } catch (Exception ex) { 1062 Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex); 1063 } 1064 } 1065 }.start(); 1066 } 1067 } 1068 1069 private void sendStorageIntent(String action, String path) { 1070 Intent intent = new Intent(action, Uri.parse("file://" + path)); 1071 // add StorageVolume extra 1072 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path)); 1073 Slog.d(TAG, "sendStorageIntent " + intent); 1074 mContext.sendBroadcast(intent); 1075 } 1076 1077 private void sendUmsIntent(boolean c) { 1078 mContext.sendBroadcast( 1079 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED))); 1080 } 1081 1082 private void validatePermission(String perm) { 1083 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 1084 throw new SecurityException(String.format("Requires %s permission", perm)); 1085 } 1086 } 1087 1088 // Storage list XML tags 1089 private static final String TAG_STORAGE_LIST = "StorageList"; 1090 private static final String TAG_STORAGE = "storage"; 1091 1092 private void readStorageList(Resources resources) { 1093 int id = com.android.internal.R.xml.storage_list; 1094 XmlResourceParser parser = resources.getXml(id); 1095 AttributeSet attrs = Xml.asAttributeSet(parser); 1096 1097 try { 1098 XmlUtils.beginDocument(parser, TAG_STORAGE_LIST); 1099 while (true) { 1100 XmlUtils.nextElement(parser); 1101 1102 String element = parser.getName(); 1103 if (element == null) break; 1104 1105 if (TAG_STORAGE.equals(element)) { 1106 TypedArray a = resources.obtainAttributes(attrs, 1107 com.android.internal.R.styleable.Storage); 1108 1109 CharSequence path = a.getText( 1110 com.android.internal.R.styleable.Storage_mountPoint); 1111 CharSequence description = a.getText( 1112 com.android.internal.R.styleable.Storage_storageDescription); 1113 boolean primary = a.getBoolean( 1114 com.android.internal.R.styleable.Storage_primary, false); 1115 boolean removable = a.getBoolean( 1116 com.android.internal.R.styleable.Storage_removable, false); 1117 boolean emulated = a.getBoolean( 1118 com.android.internal.R.styleable.Storage_emulated, false); 1119 int mtpReserve = a.getInt( 1120 com.android.internal.R.styleable.Storage_mtpReserve, 0); 1121 1122 Slog.d(TAG, "got storage path: " + path + " description: " + description + 1123 " primary: " + primary + " removable: " + removable + 1124 " emulated: " + emulated + " mtpReserve: " + mtpReserve); 1125 if (path == null || description == null) { 1126 Slog.e(TAG, "path or description is null in readStorageList"); 1127 } else { 1128 String pathString = path.toString(); 1129 StorageVolume volume = new StorageVolume(pathString, 1130 description.toString(), removable, emulated, mtpReserve); 1131 if (primary) { 1132 if (mPrimaryVolume == null) { 1133 mPrimaryVolume = volume; 1134 } else { 1135 Slog.e(TAG, "multiple primary volumes in storage list"); 1136 } 1137 } 1138 if (mPrimaryVolume == volume) { 1139 // primay volume must be first 1140 mVolumes.add(0, volume); 1141 } else { 1142 mVolumes.add(volume); 1143 } 1144 mVolumeMap.put(pathString, volume); 1145 } 1146 a.recycle(); 1147 } 1148 } 1149 } catch (XmlPullParserException e) { 1150 throw new RuntimeException(e); 1151 } catch (IOException e) { 1152 throw new RuntimeException(e); 1153 } finally { 1154 // compute storage ID for each volume 1155 int length = mVolumes.size(); 1156 for (int i = 0; i < length; i++) { 1157 mVolumes.get(i).setStorageId(i); 1158 } 1159 parser.close(); 1160 } 1161 } 1162 1163 /** 1164 * Constructs a new MountService instance 1165 * 1166 * @param context Binder context for this service 1167 */ 1168 public MountService(Context context) { 1169 mContext = context; 1170 Resources resources = context.getResources(); 1171 readStorageList(resources); 1172 1173 if (mPrimaryVolume != null) { 1174 mExternalStoragePath = mPrimaryVolume.getPath(); 1175 mEmulateExternalStorage = mPrimaryVolume.isEmulated(); 1176 if (mEmulateExternalStorage) { 1177 Slog.d(TAG, "using emulated external storage"); 1178 mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); 1179 } 1180 } 1181 1182 // XXX: This will go away soon in favor of IMountServiceObserver 1183 mPms = (PackageManagerService) ServiceManager.getService("package"); 1184 1185 mContext.registerReceiver(mBroadcastReceiver, 1186 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 1187 1188 mHandlerThread = new HandlerThread("MountService"); 1189 mHandlerThread.start(); 1190 mHandler = new MountServiceHandler(mHandlerThread.getLooper()); 1191 1192 // Add OBB Action Handler to MountService thread. 1193 mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); 1194 1195 /* 1196 * Vold does not run in the simulator, so pretend the connector thread 1197 * ran and did its thing. 1198 */ 1199 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 1200 mReady = true; 1201 mUmsEnabling = true; 1202 return; 1203 } 1204 1205 /* 1206 * Create the connection to vold with a maximum queue of twice the 1207 * amount of containers we'd ever expect to have. This keeps an 1208 * "asec list" from blocking a thread repeatedly. 1209 */ 1210 mConnector = new NativeDaemonConnector(this, "vold", 1211 PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG); 1212 mReady = false; 1213 Thread thread = new Thread(mConnector, VOLD_TAG); 1214 thread.start(); 1215 } 1216 1217 /** 1218 * Exposed API calls below here 1219 */ 1220 1221 public void registerListener(IMountServiceListener listener) { 1222 synchronized (mListeners) { 1223 MountServiceBinderListener bl = new MountServiceBinderListener(listener); 1224 try { 1225 listener.asBinder().linkToDeath(bl, 0); 1226 mListeners.add(bl); 1227 } catch (RemoteException rex) { 1228 Slog.e(TAG, "Failed to link to listener death"); 1229 } 1230 } 1231 } 1232 1233 public void unregisterListener(IMountServiceListener listener) { 1234 synchronized (mListeners) { 1235 for(MountServiceBinderListener bl : mListeners) { 1236 if (bl.mListener == listener) { 1237 mListeners.remove(mListeners.indexOf(bl)); 1238 return; 1239 } 1240 } 1241 } 1242 } 1243 1244 public void shutdown(final IMountShutdownObserver observer) { 1245 validatePermission(android.Manifest.permission.SHUTDOWN); 1246 1247 Slog.i(TAG, "Shutting down"); 1248 synchronized (mVolumeStates) { 1249 for (String path : mVolumeStates.keySet()) { 1250 String state = mVolumeStates.get(path); 1251 1252 if (state.equals(Environment.MEDIA_SHARED)) { 1253 /* 1254 * If the media is currently shared, unshare it. 1255 * XXX: This is still dangerous!. We should not 1256 * be rebooting at *all* if UMS is enabled, since 1257 * the UMS host could have dirty FAT cache entries 1258 * yet to flush. 1259 */ 1260 setUsbMassStorageEnabled(false); 1261 } else if (state.equals(Environment.MEDIA_CHECKING)) { 1262 /* 1263 * If the media is being checked, then we need to wait for 1264 * it to complete before being able to proceed. 1265 */ 1266 // XXX: @hackbod - Should we disable the ANR timer here? 1267 int retries = 30; 1268 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 1269 try { 1270 Thread.sleep(1000); 1271 } catch (InterruptedException iex) { 1272 Slog.e(TAG, "Interrupted while waiting for media", iex); 1273 break; 1274 } 1275 state = Environment.getExternalStorageState(); 1276 } 1277 if (retries == 0) { 1278 Slog.e(TAG, "Timed out waiting for media to check"); 1279 } 1280 } 1281 1282 if (state.equals(Environment.MEDIA_MOUNTED)) { 1283 // Post a unmount message. 1284 ShutdownCallBack ucb = new ShutdownCallBack(path, observer); 1285 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1286 } else if (observer != null) { 1287 /* 1288 * Observer is waiting for onShutDownComplete when we are done. 1289 * Since nothing will be done send notification directly so shutdown 1290 * sequence can continue. 1291 */ 1292 try { 1293 observer.onShutDownComplete(StorageResultCode.OperationSucceeded); 1294 } catch (RemoteException e) { 1295 Slog.w(TAG, "RemoteException when shutting down"); 1296 } 1297 } 1298 } 1299 } 1300 } 1301 1302 private boolean getUmsEnabling() { 1303 synchronized (mListeners) { 1304 return mUmsEnabling; 1305 } 1306 } 1307 1308 private void setUmsEnabling(boolean enable) { 1309 synchronized (mListeners) { 1310 mUmsEnabling = enable; 1311 } 1312 } 1313 1314 public boolean isUsbMassStorageConnected() { 1315 waitForReady(); 1316 1317 if (getUmsEnabling()) { 1318 return true; 1319 } 1320 return doGetShareMethodAvailable("ums"); 1321 } 1322 1323 public void setUsbMassStorageEnabled(boolean enable) { 1324 waitForReady(); 1325 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1326 1327 // TODO: Add support for multiple share methods 1328 1329 /* 1330 * If the volume is mounted and we're enabling then unmount it 1331 */ 1332 String path = Environment.getExternalStorageDirectory().getPath(); 1333 String vs = getVolumeState(path); 1334 String method = "ums"; 1335 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 1336 // Override for isUsbMassStorageEnabled() 1337 setUmsEnabling(enable); 1338 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true); 1339 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb)); 1340 // Clear override 1341 setUmsEnabling(false); 1342 } 1343 /* 1344 * If we disabled UMS then mount the volume 1345 */ 1346 if (!enable) { 1347 doShareUnshareVolume(path, method, enable); 1348 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { 1349 Slog.e(TAG, "Failed to remount " + path + 1350 " after disabling share method " + method); 1351 /* 1352 * Even though the mount failed, the unshare didn't so don't indicate an error. 1353 * The mountVolume() call will have set the storage state and sent the necessary 1354 * broadcasts. 1355 */ 1356 } 1357 } 1358 } 1359 1360 public boolean isUsbMassStorageEnabled() { 1361 waitForReady(); 1362 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); 1363 } 1364 1365 /** 1366 * @return state of the volume at the specified mount point 1367 */ 1368 public String getVolumeState(String mountPoint) { 1369 synchronized (mVolumeStates) { 1370 String state = mVolumeStates.get(mountPoint); 1371 if (state == null) { 1372 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 1373 throw new IllegalArgumentException(); 1374 } 1375 1376 return state; 1377 } 1378 } 1379 1380 public boolean isExternalStorageEmulated() { 1381 return mEmulateExternalStorage; 1382 } 1383 1384 public int mountVolume(String path) { 1385 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1386 1387 waitForReady(); 1388 return doMountVolume(path); 1389 } 1390 1391 public void unmountVolume(String path, boolean force) { 1392 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1393 waitForReady(); 1394 1395 String volState = getVolumeState(path); 1396 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path + " force = " + force); 1397 if (Environment.MEDIA_UNMOUNTED.equals(volState) || 1398 Environment.MEDIA_REMOVED.equals(volState) || 1399 Environment.MEDIA_SHARED.equals(volState) || 1400 Environment.MEDIA_UNMOUNTABLE.equals(volState)) { 1401 // Media already unmounted or cannot be unmounted. 1402 // TODO return valid return code when adding observer call back. 1403 return; 1404 } 1405 UnmountCallBack ucb = new UnmountCallBack(path, force); 1406 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1407 } 1408 1409 public int formatVolume(String path) { 1410 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); 1411 waitForReady(); 1412 1413 return doFormatVolume(path); 1414 } 1415 1416 public int []getStorageUsers(String path) { 1417 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1418 waitForReady(); 1419 try { 1420 String[] r = mConnector.doListCommand( 1421 String.format("storage users %s", path), 1422 VoldResponseCode.StorageUsersListResult); 1423 // FMT: <pid> <process name> 1424 int[] data = new int[r.length]; 1425 for (int i = 0; i < r.length; i++) { 1426 String []tok = r[i].split(" "); 1427 try { 1428 data[i] = Integer.parseInt(tok[0]); 1429 } catch (NumberFormatException nfe) { 1430 Slog.e(TAG, String.format("Error parsing pid %s", tok[0])); 1431 return new int[0]; 1432 } 1433 } 1434 return data; 1435 } catch (NativeDaemonConnectorException e) { 1436 Slog.e(TAG, "Failed to retrieve storage users list", e); 1437 return new int[0]; 1438 } 1439 } 1440 1441 private void warnOnNotMounted() { 1442 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 1443 Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); 1444 } 1445 } 1446 1447 public String[] getSecureContainerList() { 1448 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1449 waitForReady(); 1450 warnOnNotMounted(); 1451 1452 try { 1453 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult); 1454 } catch (NativeDaemonConnectorException e) { 1455 return new String[0]; 1456 } 1457 } 1458 1459 public int createSecureContainer(String id, int sizeMb, String fstype, 1460 String key, int ownerUid) { 1461 validatePermission(android.Manifest.permission.ASEC_CREATE); 1462 waitForReady(); 1463 warnOnNotMounted(); 1464 1465 int rc = StorageResultCode.OperationSucceeded; 1466 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid); 1467 try { 1468 mConnector.doCommand(cmd); 1469 } catch (NativeDaemonConnectorException e) { 1470 rc = StorageResultCode.OperationFailedInternalError; 1471 } 1472 1473 if (rc == StorageResultCode.OperationSucceeded) { 1474 synchronized (mAsecMountSet) { 1475 mAsecMountSet.add(id); 1476 } 1477 } 1478 return rc; 1479 } 1480 1481 public int finalizeSecureContainer(String id) { 1482 validatePermission(android.Manifest.permission.ASEC_CREATE); 1483 warnOnNotMounted(); 1484 1485 int rc = StorageResultCode.OperationSucceeded; 1486 try { 1487 mConnector.doCommand(String.format("asec finalize %s", id)); 1488 /* 1489 * Finalization does a remount, so no need 1490 * to update mAsecMountSet 1491 */ 1492 } catch (NativeDaemonConnectorException e) { 1493 rc = StorageResultCode.OperationFailedInternalError; 1494 } 1495 return rc; 1496 } 1497 1498 public int destroySecureContainer(String id, boolean force) { 1499 validatePermission(android.Manifest.permission.ASEC_DESTROY); 1500 waitForReady(); 1501 warnOnNotMounted(); 1502 1503 /* 1504 * Force a GC to make sure AssetManagers in other threads of the 1505 * system_server are cleaned up. We have to do this since AssetManager 1506 * instances are kept as a WeakReference and it's possible we have files 1507 * open on the external storage. 1508 */ 1509 Runtime.getRuntime().gc(); 1510 1511 int rc = StorageResultCode.OperationSucceeded; 1512 try { 1513 mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : ""))); 1514 } catch (NativeDaemonConnectorException e) { 1515 int code = e.getCode(); 1516 if (code == VoldResponseCode.OpFailedStorageBusy) { 1517 rc = StorageResultCode.OperationFailedStorageBusy; 1518 } else { 1519 rc = StorageResultCode.OperationFailedInternalError; 1520 } 1521 } 1522 1523 if (rc == StorageResultCode.OperationSucceeded) { 1524 synchronized (mAsecMountSet) { 1525 if (mAsecMountSet.contains(id)) { 1526 mAsecMountSet.remove(id); 1527 } 1528 } 1529 } 1530 1531 return rc; 1532 } 1533 1534 public int mountSecureContainer(String id, String key, int ownerUid) { 1535 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1536 waitForReady(); 1537 warnOnNotMounted(); 1538 1539 synchronized (mAsecMountSet) { 1540 if (mAsecMountSet.contains(id)) { 1541 return StorageResultCode.OperationFailedStorageMounted; 1542 } 1543 } 1544 1545 int rc = StorageResultCode.OperationSucceeded; 1546 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid); 1547 try { 1548 mConnector.doCommand(cmd); 1549 } catch (NativeDaemonConnectorException e) { 1550 int code = e.getCode(); 1551 if (code != VoldResponseCode.OpFailedStorageBusy) { 1552 rc = StorageResultCode.OperationFailedInternalError; 1553 } 1554 } 1555 1556 if (rc == StorageResultCode.OperationSucceeded) { 1557 synchronized (mAsecMountSet) { 1558 mAsecMountSet.add(id); 1559 } 1560 } 1561 return rc; 1562 } 1563 1564 public int unmountSecureContainer(String id, boolean force) { 1565 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1566 waitForReady(); 1567 warnOnNotMounted(); 1568 1569 synchronized (mAsecMountSet) { 1570 if (!mAsecMountSet.contains(id)) { 1571 return StorageResultCode.OperationFailedStorageNotMounted; 1572 } 1573 } 1574 1575 /* 1576 * Force a GC to make sure AssetManagers in other threads of the 1577 * system_server are cleaned up. We have to do this since AssetManager 1578 * instances are kept as a WeakReference and it's possible we have files 1579 * open on the external storage. 1580 */ 1581 Runtime.getRuntime().gc(); 1582 1583 int rc = StorageResultCode.OperationSucceeded; 1584 String cmd = String.format("asec unmount %s%s", id, (force ? " force" : "")); 1585 try { 1586 mConnector.doCommand(cmd); 1587 } catch (NativeDaemonConnectorException e) { 1588 int code = e.getCode(); 1589 if (code == VoldResponseCode.OpFailedStorageBusy) { 1590 rc = StorageResultCode.OperationFailedStorageBusy; 1591 } else { 1592 rc = StorageResultCode.OperationFailedInternalError; 1593 } 1594 } 1595 1596 if (rc == StorageResultCode.OperationSucceeded) { 1597 synchronized (mAsecMountSet) { 1598 mAsecMountSet.remove(id); 1599 } 1600 } 1601 return rc; 1602 } 1603 1604 public boolean isSecureContainerMounted(String id) { 1605 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1606 waitForReady(); 1607 warnOnNotMounted(); 1608 1609 synchronized (mAsecMountSet) { 1610 return mAsecMountSet.contains(id); 1611 } 1612 } 1613 1614 public int renameSecureContainer(String oldId, String newId) { 1615 validatePermission(android.Manifest.permission.ASEC_RENAME); 1616 waitForReady(); 1617 warnOnNotMounted(); 1618 1619 synchronized (mAsecMountSet) { 1620 /* 1621 * Because a mounted container has active internal state which cannot be 1622 * changed while active, we must ensure both ids are not currently mounted. 1623 */ 1624 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { 1625 return StorageResultCode.OperationFailedStorageMounted; 1626 } 1627 } 1628 1629 int rc = StorageResultCode.OperationSucceeded; 1630 String cmd = String.format("asec rename %s %s", oldId, newId); 1631 try { 1632 mConnector.doCommand(cmd); 1633 } catch (NativeDaemonConnectorException e) { 1634 rc = StorageResultCode.OperationFailedInternalError; 1635 } 1636 1637 return rc; 1638 } 1639 1640 public String getSecureContainerPath(String id) { 1641 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1642 waitForReady(); 1643 warnOnNotMounted(); 1644 1645 try { 1646 ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id)); 1647 String []tok = rsp.get(0).split(" "); 1648 int code = Integer.parseInt(tok[0]); 1649 if (code != VoldResponseCode.AsecPathResult) { 1650 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1651 } 1652 return tok[1]; 1653 } catch (NativeDaemonConnectorException e) { 1654 int code = e.getCode(); 1655 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1656 throw new IllegalArgumentException(String.format("Container '%s' not found", id)); 1657 } else { 1658 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1659 } 1660 } 1661 } 1662 1663 public void finishMediaUpdate() { 1664 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); 1665 } 1666 1667 private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { 1668 if (callerUid == android.os.Process.SYSTEM_UID) { 1669 return true; 1670 } 1671 1672 if (packageName == null) { 1673 return false; 1674 } 1675 1676 final int packageUid = mPms.getPackageUid(packageName); 1677 1678 if (DEBUG_OBB) { 1679 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + 1680 packageUid + ", callerUid = " + callerUid); 1681 } 1682 1683 return callerUid == packageUid; 1684 } 1685 1686 public String getMountedObbPath(String filename) { 1687 if (filename == null) { 1688 throw new IllegalArgumentException("filename cannot be null"); 1689 } 1690 1691 waitForReady(); 1692 warnOnNotMounted(); 1693 1694 try { 1695 ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); 1696 String []tok = rsp.get(0).split(" "); 1697 int code = Integer.parseInt(tok[0]); 1698 if (code != VoldResponseCode.AsecPathResult) { 1699 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1700 } 1701 return tok[1]; 1702 } catch (NativeDaemonConnectorException e) { 1703 int code = e.getCode(); 1704 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1705 return null; 1706 } else { 1707 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1708 } 1709 } 1710 } 1711 1712 public boolean isObbMounted(String filename) { 1713 if (filename == null) { 1714 throw new IllegalArgumentException("filename cannot be null"); 1715 } 1716 1717 synchronized (mObbMounts) { 1718 return mObbPathToStateMap.containsKey(filename); 1719 } 1720 } 1721 1722 public void mountObb(String filename, String key, IObbActionListener token, int nonce) 1723 throws RemoteException { 1724 if (filename == null) { 1725 throw new IllegalArgumentException("filename cannot be null"); 1726 } 1727 1728 if (token == null) { 1729 throw new IllegalArgumentException("token cannot be null"); 1730 } 1731 1732 final int callerUid = Binder.getCallingUid(); 1733 final ObbState obbState = new ObbState(filename, callerUid, token, nonce); 1734 final ObbAction action = new MountObbAction(obbState, key); 1735 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1736 1737 if (DEBUG_OBB) 1738 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1739 } 1740 1741 public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce) 1742 throws RemoteException { 1743 if (filename == null) { 1744 throw new IllegalArgumentException("filename cannot be null"); 1745 } 1746 1747 final int callerUid = Binder.getCallingUid(); 1748 final ObbState obbState = new ObbState(filename, callerUid, token, nonce); 1749 final ObbAction action = new UnmountObbAction(obbState, force); 1750 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1751 1752 if (DEBUG_OBB) 1753 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1754 } 1755 1756 public int decryptStorage(String password) { 1757 if (TextUtils.isEmpty(password)) { 1758 throw new IllegalArgumentException("password cannot be empty"); 1759 } 1760 1761 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1762 "no permission to access the crypt keeper"); 1763 1764 waitForReady(); 1765 1766 if (DEBUG_EVENTS) { 1767 Slog.i(TAG, "decrypting storage..."); 1768 } 1769 1770 try { 1771 ArrayList<String> rsp = mConnector.doCommand("cryptfs checkpw " + password); 1772 String[] tokens = rsp.get(0).split(" "); 1773 1774 if (tokens == null || tokens.length != 2) { 1775 return -1; 1776 } 1777 1778 int code = Integer.parseInt(tokens[1]); 1779 1780 if (code == 0) { 1781 // Decrypt was successful. Post a delayed message before restarting in order 1782 // to let the UI to clear itself 1783 mHandler.postDelayed(new Runnable() { 1784 public void run() { 1785 mConnector.doCommand(String.format("cryptfs restart")); 1786 } 1787 }, 1000); // 1 second 1788 } 1789 1790 return code; 1791 } catch (NativeDaemonConnectorException e) { 1792 // Decryption failed 1793 return e.getCode(); 1794 } 1795 } 1796 1797 public int encryptStorage(String password) { 1798 if (TextUtils.isEmpty(password)) { 1799 throw new IllegalArgumentException("password cannot be empty"); 1800 } 1801 1802 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1803 "no permission to access the crypt keeper"); 1804 1805 waitForReady(); 1806 1807 if (DEBUG_EVENTS) { 1808 Slog.i(TAG, "encrypting storage..."); 1809 } 1810 1811 try { 1812 mConnector.doCommand(String.format("cryptfs enablecrypto inplace %s", password)); 1813 } catch (NativeDaemonConnectorException e) { 1814 // Encryption failed 1815 return e.getCode(); 1816 } 1817 1818 return 0; 1819 } 1820 1821 public int changeEncryptionPassword(String password) { 1822 if (TextUtils.isEmpty(password)) { 1823 throw new IllegalArgumentException("password cannot be empty"); 1824 } 1825 1826 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1827 "no permission to access the crypt keeper"); 1828 1829 waitForReady(); 1830 1831 if (DEBUG_EVENTS) { 1832 Slog.i(TAG, "changing encryption password..."); 1833 } 1834 1835 try { 1836 ArrayList<String> response = mConnector.doCommand("cryptfs changepw " + password); 1837 1838 String[] tokens = response.get(0).split(" "); 1839 1840 if (tokens == null || tokens.length != 2) { 1841 return -1; 1842 } 1843 1844 return Integer.parseInt(tokens[1]); 1845 } catch (NativeDaemonConnectorException e) { 1846 // Encryption failed 1847 return e.getCode(); 1848 } 1849 } 1850 1851 public Parcelable[] getVolumeList() { 1852 synchronized(mVolumes) { 1853 int size = mVolumes.size(); 1854 Parcelable[] result = new Parcelable[size]; 1855 for (int i = 0; i < size; i++) { 1856 result[i] = mVolumes.get(i); 1857 } 1858 return result; 1859 } 1860 } 1861 1862 private void addObbStateLocked(ObbState obbState) throws RemoteException { 1863 final IBinder binder = obbState.getBinder(); 1864 List<ObbState> obbStates = mObbMounts.get(binder); 1865 1866 if (obbStates == null) { 1867 obbStates = new ArrayList<ObbState>(); 1868 mObbMounts.put(binder, obbStates); 1869 } else { 1870 for (final ObbState o : obbStates) { 1871 if (o.filename.equals(obbState.filename)) { 1872 throw new IllegalStateException("Attempt to add ObbState twice. " 1873 + "This indicates an error in the MountService logic."); 1874 } 1875 } 1876 } 1877 1878 obbStates.add(obbState); 1879 try { 1880 obbState.link(); 1881 } catch (RemoteException e) { 1882 /* 1883 * The binder died before we could link it, so clean up our state 1884 * and return failure. 1885 */ 1886 obbStates.remove(obbState); 1887 if (obbStates.isEmpty()) { 1888 mObbMounts.remove(binder); 1889 } 1890 1891 // Rethrow the error so mountObb can get it 1892 throw e; 1893 } 1894 1895 mObbPathToStateMap.put(obbState.filename, obbState); 1896 } 1897 1898 private void removeObbStateLocked(ObbState obbState) { 1899 final IBinder binder = obbState.getBinder(); 1900 final List<ObbState> obbStates = mObbMounts.get(binder); 1901 if (obbStates != null) { 1902 if (obbStates.remove(obbState)) { 1903 obbState.unlink(); 1904 } 1905 if (obbStates.isEmpty()) { 1906 mObbMounts.remove(binder); 1907 } 1908 } 1909 1910 mObbPathToStateMap.remove(obbState.filename); 1911 } 1912 1913 private class ObbActionHandler extends Handler { 1914 private boolean mBound = false; 1915 private final List<ObbAction> mActions = new LinkedList<ObbAction>(); 1916 1917 ObbActionHandler(Looper l) { 1918 super(l); 1919 } 1920 1921 @Override 1922 public void handleMessage(Message msg) { 1923 switch (msg.what) { 1924 case OBB_RUN_ACTION: { 1925 final ObbAction action = (ObbAction) msg.obj; 1926 1927 if (DEBUG_OBB) 1928 Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); 1929 1930 // If a bind was already initiated we don't really 1931 // need to do anything. The pending install 1932 // will be processed later on. 1933 if (!mBound) { 1934 // If this is the only one pending we might 1935 // have to bind to the service again. 1936 if (!connectToService()) { 1937 Slog.e(TAG, "Failed to bind to media container service"); 1938 action.handleError(); 1939 return; 1940 } 1941 } 1942 1943 mActions.add(action); 1944 break; 1945 } 1946 case OBB_MCS_BOUND: { 1947 if (DEBUG_OBB) 1948 Slog.i(TAG, "OBB_MCS_BOUND"); 1949 if (msg.obj != null) { 1950 mContainerService = (IMediaContainerService) msg.obj; 1951 } 1952 if (mContainerService == null) { 1953 // Something seriously wrong. Bail out 1954 Slog.e(TAG, "Cannot bind to media container service"); 1955 for (ObbAction action : mActions) { 1956 // Indicate service bind error 1957 action.handleError(); 1958 } 1959 mActions.clear(); 1960 } else if (mActions.size() > 0) { 1961 final ObbAction action = mActions.get(0); 1962 if (action != null) { 1963 action.execute(this); 1964 } 1965 } else { 1966 // Should never happen ideally. 1967 Slog.w(TAG, "Empty queue"); 1968 } 1969 break; 1970 } 1971 case OBB_MCS_RECONNECT: { 1972 if (DEBUG_OBB) 1973 Slog.i(TAG, "OBB_MCS_RECONNECT"); 1974 if (mActions.size() > 0) { 1975 if (mBound) { 1976 disconnectService(); 1977 } 1978 if (!connectToService()) { 1979 Slog.e(TAG, "Failed to bind to media container service"); 1980 for (ObbAction action : mActions) { 1981 // Indicate service bind error 1982 action.handleError(); 1983 } 1984 mActions.clear(); 1985 } 1986 } 1987 break; 1988 } 1989 case OBB_MCS_UNBIND: { 1990 if (DEBUG_OBB) 1991 Slog.i(TAG, "OBB_MCS_UNBIND"); 1992 1993 // Delete pending install 1994 if (mActions.size() > 0) { 1995 mActions.remove(0); 1996 } 1997 if (mActions.size() == 0) { 1998 if (mBound) { 1999 disconnectService(); 2000 } 2001 } else { 2002 // There are more pending requests in queue. 2003 // Just post MCS_BOUND message to trigger processing 2004 // of next pending install. 2005 mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); 2006 } 2007 break; 2008 } 2009 case OBB_FLUSH_MOUNT_STATE: { 2010 final String path = (String) msg.obj; 2011 2012 if (DEBUG_OBB) 2013 Slog.i(TAG, "Flushing all OBB state for path " + path); 2014 2015 synchronized (mObbMounts) { 2016 final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>(); 2017 2018 final Iterator<Entry<String, ObbState>> i = 2019 mObbPathToStateMap.entrySet().iterator(); 2020 while (i.hasNext()) { 2021 final Entry<String, ObbState> obbEntry = i.next(); 2022 2023 /* 2024 * If this entry's source file is in the volume path 2025 * that got unmounted, remove it because it's no 2026 * longer valid. 2027 */ 2028 if (obbEntry.getKey().startsWith(path)) { 2029 obbStatesToRemove.add(obbEntry.getValue()); 2030 } 2031 } 2032 2033 for (final ObbState obbState : obbStatesToRemove) { 2034 if (DEBUG_OBB) 2035 Slog.i(TAG, "Removing state for " + obbState.filename); 2036 2037 removeObbStateLocked(obbState); 2038 2039 try { 2040 obbState.token.onObbResult(obbState.filename, obbState.nonce, 2041 OnObbStateChangeListener.UNMOUNTED); 2042 } catch (RemoteException e) { 2043 Slog.i(TAG, "Couldn't send unmount notification for OBB: " 2044 + obbState.filename); 2045 } 2046 } 2047 } 2048 break; 2049 } 2050 } 2051 } 2052 2053 private boolean connectToService() { 2054 if (DEBUG_OBB) 2055 Slog.i(TAG, "Trying to bind to DefaultContainerService"); 2056 2057 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); 2058 if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { 2059 mBound = true; 2060 return true; 2061 } 2062 return false; 2063 } 2064 2065 private void disconnectService() { 2066 mContainerService = null; 2067 mBound = false; 2068 mContext.unbindService(mDefContainerConn); 2069 } 2070 } 2071 2072 abstract class ObbAction { 2073 private static final int MAX_RETRIES = 3; 2074 private int mRetries; 2075 2076 ObbState mObbState; 2077 2078 ObbAction(ObbState obbState) { 2079 mObbState = obbState; 2080 } 2081 2082 public void execute(ObbActionHandler handler) { 2083 try { 2084 if (DEBUG_OBB) 2085 Slog.i(TAG, "Starting to execute action: " + this.toString()); 2086 mRetries++; 2087 if (mRetries > MAX_RETRIES) { 2088 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); 2089 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2090 handleError(); 2091 return; 2092 } else { 2093 handleExecute(); 2094 if (DEBUG_OBB) 2095 Slog.i(TAG, "Posting install MCS_UNBIND"); 2096 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2097 } 2098 } catch (RemoteException e) { 2099 if (DEBUG_OBB) 2100 Slog.i(TAG, "Posting install MCS_RECONNECT"); 2101 mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); 2102 } catch (Exception e) { 2103 if (DEBUG_OBB) 2104 Slog.d(TAG, "Error handling OBB action", e); 2105 handleError(); 2106 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2107 } 2108 } 2109 2110 abstract void handleExecute() throws RemoteException, IOException; 2111 abstract void handleError(); 2112 2113 protected ObbInfo getObbInfo() throws IOException { 2114 ObbInfo obbInfo; 2115 try { 2116 obbInfo = mContainerService.getObbInfo(mObbState.filename); 2117 } catch (RemoteException e) { 2118 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for " 2119 + mObbState.filename); 2120 obbInfo = null; 2121 } 2122 if (obbInfo == null) { 2123 throw new IOException("Couldn't read OBB file: " + mObbState.filename); 2124 } 2125 return obbInfo; 2126 } 2127 2128 protected void sendNewStatusOrIgnore(int status) { 2129 if (mObbState == null || mObbState.token == null) { 2130 return; 2131 } 2132 2133 try { 2134 mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status); 2135 } catch (RemoteException e) { 2136 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); 2137 } 2138 } 2139 } 2140 2141 class MountObbAction extends ObbAction { 2142 private String mKey; 2143 2144 MountObbAction(ObbState obbState, String key) { 2145 super(obbState); 2146 mKey = key; 2147 } 2148 2149 @Override 2150 public void handleExecute() throws IOException, RemoteException { 2151 waitForReady(); 2152 warnOnNotMounted(); 2153 2154 final ObbInfo obbInfo = getObbInfo(); 2155 2156 if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) { 2157 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename 2158 + " which is owned by " + obbInfo.packageName); 2159 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2160 return; 2161 } 2162 2163 final boolean isMounted; 2164 synchronized (mObbMounts) { 2165 isMounted = mObbPathToStateMap.containsKey(obbInfo.filename); 2166 } 2167 if (isMounted) { 2168 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename); 2169 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); 2170 return; 2171 } 2172 2173 /* 2174 * The filename passed in might not be the canonical name, so just 2175 * set the filename to the canonicalized version. 2176 */ 2177 mObbState.filename = obbInfo.filename; 2178 2179 final String hashedKey; 2180 if (mKey == null) { 2181 hashedKey = "none"; 2182 } else { 2183 try { 2184 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 2185 2186 KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt, 2187 PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE); 2188 SecretKey key = factory.generateSecret(ks); 2189 BigInteger bi = new BigInteger(key.getEncoded()); 2190 hashedKey = bi.toString(16); 2191 } catch (NoSuchAlgorithmException e) { 2192 Slog.e(TAG, "Could not load PBKDF2 algorithm", e); 2193 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2194 return; 2195 } catch (InvalidKeySpecException e) { 2196 Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e); 2197 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2198 return; 2199 } 2200 } 2201 2202 int rc = StorageResultCode.OperationSucceeded; 2203 String cmd = String.format("obb mount %s %s %d", mObbState.filename, hashedKey, 2204 mObbState.callerUid); 2205 try { 2206 mConnector.doCommand(cmd); 2207 } catch (NativeDaemonConnectorException e) { 2208 int code = e.getCode(); 2209 if (code != VoldResponseCode.OpFailedStorageBusy) { 2210 rc = StorageResultCode.OperationFailedInternalError; 2211 } 2212 } 2213 2214 if (rc == StorageResultCode.OperationSucceeded) { 2215 if (DEBUG_OBB) 2216 Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename); 2217 2218 synchronized (mObbMounts) { 2219 addObbStateLocked(mObbState); 2220 } 2221 2222 sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED); 2223 } else { 2224 Slog.e(TAG, "Couldn't mount OBB file: " + rc); 2225 2226 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT); 2227 } 2228 } 2229 2230 @Override 2231 public void handleError() { 2232 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2233 } 2234 2235 @Override 2236 public String toString() { 2237 StringBuilder sb = new StringBuilder(); 2238 sb.append("MountObbAction{"); 2239 sb.append("filename="); 2240 sb.append(mObbState.filename); 2241 sb.append(",callerUid="); 2242 sb.append(mObbState.callerUid); 2243 sb.append(",token="); 2244 sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL"); 2245 sb.append(",binder="); 2246 sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null"); 2247 sb.append('}'); 2248 return sb.toString(); 2249 } 2250 } 2251 2252 class UnmountObbAction extends ObbAction { 2253 private boolean mForceUnmount; 2254 2255 UnmountObbAction(ObbState obbState, boolean force) { 2256 super(obbState); 2257 mForceUnmount = force; 2258 } 2259 2260 @Override 2261 public void handleExecute() throws IOException { 2262 waitForReady(); 2263 warnOnNotMounted(); 2264 2265 final ObbInfo obbInfo = getObbInfo(); 2266 2267 final ObbState obbState; 2268 synchronized (mObbMounts) { 2269 obbState = mObbPathToStateMap.get(obbInfo.filename); 2270 } 2271 2272 if (obbState == null) { 2273 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED); 2274 return; 2275 } 2276 2277 if (obbState.callerUid != mObbState.callerUid) { 2278 Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename 2279 + " (owned by " + obbInfo.packageName + ")"); 2280 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2281 return; 2282 } 2283 2284 mObbState.filename = obbInfo.filename; 2285 2286 int rc = StorageResultCode.OperationSucceeded; 2287 String cmd = String.format("obb unmount %s%s", mObbState.filename, 2288 (mForceUnmount ? " force" : "")); 2289 try { 2290 mConnector.doCommand(cmd); 2291 } catch (NativeDaemonConnectorException e) { 2292 int code = e.getCode(); 2293 if (code == VoldResponseCode.OpFailedStorageBusy) { 2294 rc = StorageResultCode.OperationFailedStorageBusy; 2295 } else if (code == VoldResponseCode.OpFailedStorageNotFound) { 2296 // If it's not mounted then we've already won. 2297 rc = StorageResultCode.OperationSucceeded; 2298 } else { 2299 rc = StorageResultCode.OperationFailedInternalError; 2300 } 2301 } 2302 2303 if (rc == StorageResultCode.OperationSucceeded) { 2304 synchronized (mObbMounts) { 2305 removeObbStateLocked(obbState); 2306 } 2307 2308 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED); 2309 } else { 2310 Slog.w(TAG, "Could not mount OBB: " + mObbState.filename); 2311 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT); 2312 } 2313 } 2314 2315 @Override 2316 public void handleError() { 2317 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2318 } 2319 2320 @Override 2321 public String toString() { 2322 StringBuilder sb = new StringBuilder(); 2323 sb.append("UnmountObbAction{"); 2324 sb.append("filename="); 2325 sb.append(mObbState.filename != null ? mObbState.filename : "null"); 2326 sb.append(",force="); 2327 sb.append(mForceUnmount); 2328 sb.append(",callerUid="); 2329 sb.append(mObbState.callerUid); 2330 sb.append(",token="); 2331 sb.append(mObbState.token != null ? mObbState.token.toString() : "null"); 2332 sb.append(",binder="); 2333 sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null"); 2334 sb.append('}'); 2335 return sb.toString(); 2336 } 2337 } 2338 2339 @Override 2340 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2341 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { 2342 pw.println("Permission Denial: can't dump ActivityManager from from pid=" 2343 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 2344 + " without permission " + android.Manifest.permission.DUMP); 2345 return; 2346 } 2347 2348 synchronized (mObbMounts) { 2349 pw.println(" mObbMounts:"); 2350 2351 final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator(); 2352 while (binders.hasNext()) { 2353 Entry<IBinder, List<ObbState>> e = binders.next(); 2354 pw.print(" Key="); pw.println(e.getKey().toString()); 2355 final List<ObbState> obbStates = e.getValue(); 2356 for (final ObbState obbState : obbStates) { 2357 pw.print(" "); pw.println(obbState.toString()); 2358 } 2359 } 2360 2361 pw.println(""); 2362 pw.println(" mObbPathToStateMap:"); 2363 final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); 2364 while (maps.hasNext()) { 2365 final Entry<String, ObbState> e = maps.next(); 2366 pw.print(" "); pw.print(e.getKey()); 2367 pw.print(" -> "); pw.println(e.getValue().toString()); 2368 } 2369 } 2370 } 2371} 2372 2373