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