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