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