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