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