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