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