FileDocCategorySizeDatePackage
MountService.javaAPI DocAndroid 5.1 API121130Thu Mar 12 22:22:42 GMT 2015com.android.server

MountService

public class MountService extends IMountService.Stub implements Watchdog.Monitor, INativeDaemonConnectorCallbacks
MountService implements back-end services for platform storage management.
hide
- Applications should use android.os.storage.StorageManager to access the MountService.

Fields Summary
static MountService
sSelf
private static final boolean
LOCAL_LOGD
private static final boolean
DEBUG_UNMOUNT
private static final boolean
DEBUG_EVENTS
private static final boolean
DEBUG_OBB
private static final boolean
WATCHDOG_ENABLE
private static final String
TAG
private static final String
VOLD_TAG
private static final int
MAX_CONTAINERS
Maximum number of ASEC containers allowed to be mounted.
public static final String[]
CRYPTO_TYPES
List of crypto types. These must match CRYPT_TYPE_XXX in cryptfs.h AND their corresponding commands in CommandListener.cpp
private final android.content.Context
mContext
private final NativeDaemonConnector
mConnector
private final Object
mVolumesLock
private android.os.storage.StorageVolume
mEmulatedTemplate
When defined, base template for user-specific {@link StorageVolume}.
private final ArrayList
mVolumes
private final HashMap
mVolumesByPath
Map from path to {@link StorageVolume}
private final HashMap
mVolumeStates
Map from path to state
private volatile boolean
mSystemReady
private com.android.server.pm.PackageManagerService
mPms
private boolean
mUmsEnabling
private boolean
mUmsAvailable
private final ArrayList
mListeners
private final CountDownLatch
mConnectedSignal
private final CountDownLatch
mAsecsScanned
private boolean
mSendUmsConnectedOnBoot
private final HashSet
mAsecMountSet
Private hash of currently mounted secure containers. Used as a lock in methods to manipulate secure containers.
private static final int
CRYPTO_ALGORITHM_KEY_SIZE
The size of the crypto algorithm key in bits for OBB files. Currently Twofish is used which takes 128-bit keys.
private static final int
PBKDF2_HASH_ROUNDS
The number of times to run SHA1 in the PBKDF2 function for OBB files. 1024 is reasonably secure and not too slow.
private final Map
mObbMounts
Mounted OBB tracking information. Used to track the current state of all OBBs.
private final Map
mObbPathToStateMap
Map from raw paths to {@link ObbState}.
private final ObbActionHandler
mObbActionHandler
private static final int
OBB_RUN_ACTION
private static final int
OBB_MCS_BOUND
private static final int
OBB_MCS_UNBIND
private static final int
OBB_MCS_RECONNECT
private static final int
OBB_FLUSH_MOUNT_STATE
static final android.content.ComponentName
DEFAULT_CONTAINER_COMPONENT
private final DefaultContainerConnection
mDefContainerConn
private com.android.internal.app.IMediaContainerService
mContainerService
private static final String
LAST_FSTRIM_FILE
private final File
mLastMaintenanceFile
private long
mLastMaintenance
private static final int
H_UNMOUNT_PM_UPDATE
private static final int
H_UNMOUNT_PM_DONE
private static final int
H_UNMOUNT_MS
private static final int
H_SYSTEM_READY
private static final int
H_FSTRIM
private static final int
RETRY_UNMOUNT_DELAY
private static final int
MAX_UNMOUNT_RETRIES
private final android.os.Handler
mHandler
private final android.content.BroadcastReceiver
mUserReceiver
private final android.content.BroadcastReceiver
mUsbReceiver
private static final String
TAG_STORAGE_LIST
private static final String
TAG_STORAGE
Constructors Summary
public MountService(android.content.Context context)
Constructs a new MountService instance

param
context Binder context for this service

        sSelf = this;

        mContext = context;

        synchronized (mVolumesLock) {
            readStorageListLocked();
        }

        // XXX: This will go away soon in favor of IMountServiceObserver
        mPms = (PackageManagerService) ServiceManager.getService("package");

        HandlerThread hthread = new HandlerThread(TAG);
        hthread.start();
        mHandler = new MountServiceHandler(hthread.getLooper());

        // Watch for user changes
        final IntentFilter userFilter = new IntentFilter();
        userFilter.addAction(Intent.ACTION_USER_ADDED);
        userFilter.addAction(Intent.ACTION_USER_REMOVED);
        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);

        // Watch for USB changes on primary volume
        final StorageVolume primary = getPrimaryPhysicalVolume();
        if (primary != null && primary.allowMassStorage()) {
            mContext.registerReceiver(
                    mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
        }

        // Add OBB Action Handler to MountService thread.
        mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());

        // Initialize the last-fstrim tracking if necessary
        File dataDir = Environment.getDataDirectory();
        File systemDir = new File(dataDir, "system");
        mLastMaintenanceFile = new File(systemDir, LAST_FSTRIM_FILE);
        if (!mLastMaintenanceFile.exists()) {
            // Not setting mLastMaintenance here means that we will force an
            // fstrim during reboot following the OTA that installs this code.
            try {
                (new FileOutputStream(mLastMaintenanceFile)).close();
            } catch (IOException e) {
                Slog.e(TAG, "Unable to create fstrim record " + mLastMaintenanceFile.getPath());
            }
        } else {
            mLastMaintenance = mLastMaintenanceFile.lastModified();
        }

        /*
         * Create the connection to vold with a maximum queue of twice the
         * amount of containers we'd ever expect to have. This keeps an
         * "asec list" from blocking a thread repeatedly.
         */
        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
                null);

        Thread thread = new Thread(mConnector, VOLD_TAG);
        thread.start();

        // Add ourself to the Watchdog monitors if enabled.
        if (WATCHDOG_ENABLE) {
            Watchdog.getInstance().addMonitor(this);
        }
    
Methods Summary
private voidaddObbStateLocked(com.android.server.MountService$ObbState obbState)

        final IBinder binder = obbState.getBinder();
        List<ObbState> obbStates = mObbMounts.get(binder);

        if (obbStates == null) {
            obbStates = new ArrayList<ObbState>();
            mObbMounts.put(binder, obbStates);
        } else {
            for (final ObbState o : obbStates) {
                if (o.rawPath.equals(obbState.rawPath)) {
                    throw new IllegalStateException("Attempt to add ObbState twice. "
                            + "This indicates an error in the MountService logic.");
                }
            }
        }

        obbStates.add(obbState);
        try {
            obbState.link();
        } catch (RemoteException e) {
            /*
             * The binder died before we could link it, so clean up our state
             * and return failure.
             */
            obbStates.remove(obbState);
            if (obbStates.isEmpty()) {
                mObbMounts.remove(binder);
            }

            // Rethrow the error so mountObb can get it
            throw e;
        }

        mObbPathToStateMap.put(obbState.rawPath, obbState);
    
private voidaddVolumeLocked(android.os.storage.StorageVolume volume)

        Slog.d(TAG, "addVolumeLocked() " + volume);
        mVolumes.add(volume);
        final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume);
        if (existing != null) {
            throw new IllegalStateException(
                    "Volume at " + volume.getPath() + " already exists: " + existing);
        }
    
public static java.lang.StringbuildObbPath(java.lang.String canonicalPath, int userId, boolean forVold)

        // TODO: allow caller to provide Environment for full testing
        // TODO: extend to support OBB mounts on secondary external storage

        // Only adjust paths when storage is emulated
        if (!Environment.isExternalStorageEmulated()) {
            return canonicalPath;
        }

        String path = canonicalPath.toString();

        // First trim off any external storage prefix
        final UserEnvironment userEnv = new UserEnvironment(userId);

        // /storage/emulated/0
        final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath();
        // /storage/emulated_legacy
        final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
                .getAbsolutePath();

        if (path.startsWith(externalPath)) {
            path = path.substring(externalPath.length() + 1);
        } else if (path.startsWith(legacyExternalPath)) {
            path = path.substring(legacyExternalPath.length() + 1);
        } else {
            return canonicalPath;
        }

        // Handle special OBB paths on emulated storage
        final String obbPath = "Android/obb";
        if (path.startsWith(obbPath)) {
            path = path.substring(obbPath.length() + 1);

            if (forVold) {
                return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
            } else {
                final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
                return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
                        .getAbsolutePath();
            }
        }

        // Handle normal external storage paths
        if (forVold) {
            return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
        } else {
            return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
        }
    
public intchangeEncryptionPassword(int type, java.lang.String password)
Set the password for encrypting the master key.

param
type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
param
password The password to set.

        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
            "no permission to access the crypt keeper");

        waitForReady();

        if (DEBUG_EVENTS) {
            Slog.i(TAG, "changing encryption password...");
        }

        try {
            NativeDaemonEvent event = mConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type],
                        new SensitiveArg(toHex(password)));
            return Integer.parseInt(event.getMessage());
        } catch (NativeDaemonConnectorException e) {
            // Encryption failed
            return e.getCode();
        }
    
public voidclearPassword()

        if (!isReady()) {
            return;
        }

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "clearpw");
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    
private voidcopyLocaleFromMountService()

        String systemLocale;
        try {
            systemLocale = getField(StorageManager.SYSTEM_LOCALE_KEY);
        } catch (RemoteException e) {
            return;
        }
        if (TextUtils.isEmpty(systemLocale)) {
            return;
        }

        Slog.d(TAG, "Got locale " + systemLocale + " from mount service");
        Locale locale = Locale.forLanguageTag(systemLocale);
        Configuration config = new Configuration();
        config.setLocale(locale);
        try {
            ActivityManagerNative.getDefault().updateConfiguration(config);
        } catch (RemoteException e) {
            Slog.e(TAG, "Error setting system locale from mount service", e);
        }

        // Temporary workaround for http://b/17945169.
        Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service");
        SystemProperties.set("persist.sys.language", locale.getLanguage());
        SystemProperties.set("persist.sys.country", locale.getCountry());
    
private voidcreateEmulatedVolumeForUserLocked(android.os.UserHandle user)
Create and add new {@link StorageVolume} for given {@link UserHandle} using {@link #mEmulatedTemplate} as template.

        if (mEmulatedTemplate == null) {
            throw new IllegalStateException("Missing emulated volume multi-user template");
        }

        final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
        final File path = userEnv.getExternalStorageDirectory();
        final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
        volume.setStorageId(0);
        addVolumeLocked(volume);

        if (mSystemReady) {
            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
        } else {
            // Place stub status for early callers to find
            mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
            volume.setState(Environment.MEDIA_MOUNTED);
        }
    
public intcreateSecureContainer(java.lang.String id, int sizeMb, java.lang.String fstype, java.lang.String key, int ownerUid, boolean external)

        validatePermission(android.Manifest.permission.ASEC_CREATE);
        waitForReady();
        warnOnNotMounted();

        int rc = StorageResultCode.OperationSucceeded;
        try {
            mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
                    ownerUid, external ? "1" : "0");
        } catch (NativeDaemonConnectorException e) {
            rc = StorageResultCode.OperationFailedInternalError;
        }

        if (rc == StorageResultCode.OperationSucceeded) {
            synchronized (mAsecMountSet) {
                mAsecMountSet.add(id);
            }
        }
        return rc;
    
public intdecryptStorage(java.lang.String password)

        if (TextUtils.isEmpty(password)) {
            throw new IllegalArgumentException("password cannot be empty");
        }

        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                "no permission to access the crypt keeper");

        waitForReady();

        if (DEBUG_EVENTS) {
            Slog.i(TAG, "decrypting storage...");
        }

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(toHex(password)));

            final int code = Integer.parseInt(event.getMessage());
            if (code == 0) {
                // Decrypt was successful. Post a delayed message before restarting in order
                // to let the UI to clear itself
                mHandler.postDelayed(new Runnable() {
                    public void run() {
                        try {
                            mConnector.execute("cryptfs", "restart");
                        } catch (NativeDaemonConnectorException e) {
                            Slog.e(TAG, "problem executing in background", e);
                        }
                    }
                }, 1000); // 1 second
            }

            return code;
        } catch (NativeDaemonConnectorException e) {
            // Decryption failed
            return e.getCode();
        }
    
public intdestroySecureContainer(java.lang.String id, boolean force)

        validatePermission(android.Manifest.permission.ASEC_DESTROY);
        waitForReady();
        warnOnNotMounted();

        /*
         * Force a GC to make sure AssetManagers in other threads of the
         * system_server are cleaned up. We have to do this since AssetManager
         * instances are kept as a WeakReference and it's possible we have files
         * open on the external storage.
         */
        Runtime.getRuntime().gc();

        int rc = StorageResultCode.OperationSucceeded;
        try {
            final Command cmd = new Command("asec", "destroy", id);
            if (force) {
                cmd.appendArg("force");
            }
            mConnector.execute(cmd);
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedStorageBusy) {
                rc = StorageResultCode.OperationFailedStorageBusy;
            } else {
                rc = StorageResultCode.OperationFailedInternalError;
            }
        }

        if (rc == StorageResultCode.OperationSucceeded) {
            synchronized (mAsecMountSet) {
                if (mAsecMountSet.contains(id)) {
                    mAsecMountSet.remove(id);
                }
            }
        }

        return rc;
    
private intdoFormatVolume(java.lang.String path)

        try {
            mConnector.execute("volume", "format", path);
            return StorageResultCode.OperationSucceeded;
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedNoMedia) {
                return StorageResultCode.OperationFailedNoMedia;
            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
                return StorageResultCode.OperationFailedMediaCorrupt;
            } else {
                return StorageResultCode.OperationFailedInternalError;
            }
        }
    
private booleandoGetVolumeShared(java.lang.String path, java.lang.String method)

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("volume", "shared", path, method);
        } catch (NativeDaemonConnectorException ex) {
            Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
            return false;
        }

        if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
            return event.getMessage().endsWith("enabled");
        } else {
            return false;
        }
    
private intdoMountVolume(java.lang.String path)

        int rc = StorageResultCode.OperationSucceeded;

        final StorageVolume volume;
        synchronized (mVolumesLock) {
            volume = mVolumesByPath.get(path);
        }

        if (!volume.isEmulated() && hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
            Slog.w(TAG, "User has restriction DISALLOW_MOUNT_PHYSICAL_MEDIA; cannot mount volume.");
            return StorageResultCode.OperationFailedInternalError;
        }

        if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
        try {
            mConnector.execute("volume", "mount", path);
        } catch (NativeDaemonConnectorException e) {
            /*
             * Mount failed for some reason
             */
            String action = null;
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedNoMedia) {
                /*
                 * Attempt to mount but no media inserted
                 */
                rc = StorageResultCode.OperationFailedNoMedia;
            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
                if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
                /*
                 * Media is blank or does not contain a supported filesystem
                 */
                updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
                action = Intent.ACTION_MEDIA_NOFS;
                rc = StorageResultCode.OperationFailedMediaBlank;
            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
                /*
                 * Volume consistency check failed
                 */
                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
                action = Intent.ACTION_MEDIA_UNMOUNTABLE;
                rc = StorageResultCode.OperationFailedMediaCorrupt;
            } else {
                rc = StorageResultCode.OperationFailedInternalError;
            }

            /*
             * Send broadcast intent (if required for the failure)
             */
            if (action != null) {
                sendStorageIntent(action, volume, UserHandle.ALL);
            }
        }

        return rc;
    
private voiddoShareUnshareVolume(java.lang.String path, java.lang.String method, boolean enable)

        // TODO: Add support for multiple share methods
        if (!method.equals("ums")) {
            throw new IllegalArgumentException(String.format("Method %s not supported", method));
        }

        try {
            mConnector.execute("volume", enable ? "share" : "unshare", path, method);
        } catch (NativeDaemonConnectorException e) {
            Slog.e(TAG, "Failed to share/unshare", e);
        }
    
private intdoUnmountVolume(java.lang.String path, boolean force, boolean removeEncryption)

        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
            return VoldResponseCode.OpFailedVolNotMounted;
        }

        /*
         * Force a GC to make sure AssetManagers in other threads of the
         * system_server are cleaned up. We have to do this since AssetManager
         * instances are kept as a WeakReference and it's possible we have files
         * open on the external storage.
         */
        Runtime.getRuntime().gc();

        // Redundant probably. But no harm in updating state again.
        mPms.updateExternalMediaStatus(false, false);
        try {
            final Command cmd = new Command("volume", "unmount", path);
            if (removeEncryption) {
                cmd.appendArg("force_and_revert");
            } else if (force) {
                cmd.appendArg("force");
            }
            mConnector.execute(cmd);
            // We unmounted the volume. None of the asec containers are available now.
            synchronized (mAsecMountSet) {
                mAsecMountSet.clear();
            }
            return StorageResultCode.OperationSucceeded;
        } catch (NativeDaemonConnectorException e) {
            // Don't worry about mismatch in PackageManager since the
            // call back will handle the status changes any way.
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedVolNotMounted) {
                return StorageResultCode.OperationFailedStorageNotMounted;
            } else if (code == VoldResponseCode.OpFailedStorageBusy) {
                return StorageResultCode.OperationFailedStorageBusy;
            } else {
                return StorageResultCode.OperationFailedInternalError;
            }
        }
    
protected voiddump(java.io.FileDescriptor fd, java.io.PrintWriter writer, java.lang.String[] args)

        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);

        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);

        synchronized (mObbMounts) {
            pw.println("mObbMounts:");
            pw.increaseIndent();
            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
                    .iterator();
            while (binders.hasNext()) {
                Entry<IBinder, List<ObbState>> e = binders.next();
                pw.println(e.getKey() + ":");
                pw.increaseIndent();
                final List<ObbState> obbStates = e.getValue();
                for (final ObbState obbState : obbStates) {
                    pw.println(obbState);
                }
                pw.decreaseIndent();
            }
            pw.decreaseIndent();

            pw.println();
            pw.println("mObbPathToStateMap:");
            pw.increaseIndent();
            final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
            while (maps.hasNext()) {
                final Entry<String, ObbState> e = maps.next();
                pw.print(e.getKey());
                pw.print(" -> ");
                pw.println(e.getValue());
            }
            pw.decreaseIndent();
        }

        synchronized (mVolumesLock) {
            pw.println();
            pw.println("mVolumes:");
            pw.increaseIndent();
            for (StorageVolume volume : mVolumes) {
                pw.println(volume);
                pw.increaseIndent();
                pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
                pw.decreaseIndent();
            }
            pw.decreaseIndent();
        }

        pw.println();
        pw.println("mConnection:");
        pw.increaseIndent();
        mConnector.dump(fd, pw, args);
        pw.decreaseIndent();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        pw.println();
        pw.print("Last maintenance: ");
        pw.println(sdf.format(new Date(mLastMaintenance)));
    
public intencryptStorage(int type, java.lang.String password)

        if (TextUtils.isEmpty(password) && type != StorageManager.CRYPT_TYPE_DEFAULT) {
            throw new IllegalArgumentException("password cannot be empty");
        }

        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
            "no permission to access the crypt keeper");

        waitForReady();

        if (DEBUG_EVENTS) {
            Slog.i(TAG, "encrypting storage...");
        }

        try {
            mConnector.execute("cryptfs", "enablecrypto", "inplace", CRYPTO_TYPES[type],
                               new SensitiveArg(toHex(password)));
        } catch (NativeDaemonConnectorException e) {
            // Encryption failed
            return e.getCode();
        }

        return 0;
    
public intfinalizeSecureContainer(java.lang.String id)

        validatePermission(android.Manifest.permission.ASEC_CREATE);
        warnOnNotMounted();

        int rc = StorageResultCode.OperationSucceeded;
        try {
            mConnector.execute("asec", "finalize", id);
            /*
             * Finalization does a remount, so no need
             * to update mAsecMountSet
             */
        } catch (NativeDaemonConnectorException e) {
            rc = StorageResultCode.OperationFailedInternalError;
        }
        return rc;
    
public voidfinishMediaUpdate()

        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
    
public intfixPermissionsSecureContainer(java.lang.String id, int gid, java.lang.String filename)

        validatePermission(android.Manifest.permission.ASEC_CREATE);
        warnOnNotMounted();

        int rc = StorageResultCode.OperationSucceeded;
        try {
            mConnector.execute("asec", "fixperms", id, gid, filename);
            /*
             * Fix permissions does a remount, so no need to update
             * mAsecMountSet
             */
        } catch (NativeDaemonConnectorException e) {
            rc = StorageResultCode.OperationFailedInternalError;
        }
        return rc;
    
public intformatVolume(java.lang.String path)

        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
        waitForReady();

        return doFormatVolume(path);
    
private java.lang.StringfromHex(java.lang.String hexPassword)

        if (hexPassword == null) {
            return null;
        }

        try {
            byte[] bytes = Hex.decodeHex(hexPassword.toCharArray());
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (DecoderException e) {
            return null;
        }
    
public intgetEncryptionState()

        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                "no permission to access the crypt keeper");

        waitForReady();

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "cryptocomplete");
            return Integer.parseInt(event.getMessage());
        } catch (NumberFormatException e) {
            // Bad result - unexpected.
            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
            return ENCRYPTION_STATE_ERROR_UNKNOWN;
        } catch (NativeDaemonConnectorException e) {
            // Something bad happened.
            Slog.w(TAG, "Error in communicating with cryptfs in validating");
            return ENCRYPTION_STATE_ERROR_UNKNOWN;
        }
    
public java.lang.StringgetField(java.lang.String field)
Gets a field from the crypto header.

param
field field to get
return
contents of field


        waitForReady();

        final NativeDaemonEvent event;
        try {
            final String[] contents = NativeDaemonEvent.filterMessageList(
                    mConnector.executeForList("cryptfs", "getfield", field),
                    VoldResponseCode.CryptfsGetfieldResult);
            String result = new String();
            for (String content : contents) {
                result += content;
            }
            return result;
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    
public java.lang.StringgetMountedObbPath(java.lang.String rawPath)

        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");

        waitForReady();
        warnOnNotMounted();

        final ObbState state;
        synchronized (mObbPathToStateMap) {
            state = mObbPathToStateMap.get(rawPath);
        }
        if (state == null) {
            Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
            return null;
        }

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("obb", "path", state.voldPath);
            event.checkCode(VoldResponseCode.AsecPathResult);
            return event.getMessage();
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedStorageNotFound) {
                return null;
            } else {
                throw new IllegalStateException(String.format("Unexpected response code %d", code));
            }
        }
    
public java.lang.StringgetPassword()

        if (!isReady()) {
            return new String();
        }

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "getpw");
            return fromHex(event.getMessage());
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    
public intgetPasswordType()
Get the type of encryption used to encrypt the master key.

return
The type, one of the CRYPT_TYPE_XXX consts from StorageManager.


        waitForReady();

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "getpwtype");
            for (int i = 0; i < CRYPTO_TYPES.length; ++i) {
                if (CRYPTO_TYPES[i].equals(event.getMessage()))
                    return i;
            }

            throw new IllegalStateException("unexpected return from cryptfs");
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    
private android.os.storage.StorageVolumegetPrimaryPhysicalVolume()

        synchronized (mVolumesLock) {
            for (StorageVolume volume : mVolumes) {
                if (volume.isPrimary() && !volume.isEmulated()) {
                    return volume;
                }
            }
        }
        return null;
    
public java.lang.StringgetSecureContainerFilesystemPath(java.lang.String id)

        validatePermission(android.Manifest.permission.ASEC_ACCESS);
        waitForReady();
        warnOnNotMounted();

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("asec", "fspath", id);
            event.checkCode(VoldResponseCode.AsecPathResult);
            return event.getMessage();
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedStorageNotFound) {
                Slog.i(TAG, String.format("Container '%s' not found", id));
                return null;
            } else {
                throw new IllegalStateException(String.format("Unexpected response code %d", code));
            }
        }
    
public java.lang.String[]getSecureContainerList()

        validatePermission(android.Manifest.permission.ASEC_ACCESS);
        waitForReady();
        warnOnNotMounted();

        try {
            return NativeDaemonEvent.filterMessageList(
                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
        } catch (NativeDaemonConnectorException e) {
            return new String[0];
        }
    
public java.lang.StringgetSecureContainerPath(java.lang.String id)

        validatePermission(android.Manifest.permission.ASEC_ACCESS);
        waitForReady();
        warnOnNotMounted();

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("asec", "path", id);
            event.checkCode(VoldResponseCode.AsecPathResult);
            return event.getMessage();
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedStorageNotFound) {
                Slog.i(TAG, String.format("Container '%s' not found", id));
                return null;
            } else {
                throw new IllegalStateException(String.format("Unexpected response code %d", code));
            }
        }
    
public int[]getStorageUsers(java.lang.String path)

        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
        waitForReady();
        try {
            final String[] r = NativeDaemonEvent.filterMessageList(
                    mConnector.executeForList("storage", "users", path),
                    VoldResponseCode.StorageUsersListResult);

            // FMT: <pid> <process name>
            int[] data = new int[r.length];
            for (int i = 0; i < r.length; i++) {
                String[] tok = r[i].split(" ");
                try {
                    data[i] = Integer.parseInt(tok[0]);
                } catch (NumberFormatException nfe) {
                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
                    return new int[0];
                }
            }
            return data;
        } catch (NativeDaemonConnectorException e) {
            Slog.e(TAG, "Failed to retrieve storage users list", e);
            return new int[0];
        }
    
private booleangetUmsEnabling()

        synchronized (mListeners) {
            return mUmsEnabling;
        }
    
public android.os.storage.StorageVolume[]getVolumeList()

        final int callingUserId = UserHandle.getCallingUserId();
        final boolean accessAll = (mContext.checkPermission(
                android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
                Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);

        synchronized (mVolumesLock) {
            final ArrayList<StorageVolume> filtered = Lists.newArrayList();
            for (StorageVolume volume : mVolumes) {
                final UserHandle owner = volume.getOwner();
                final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
                if (accessAll || ownerMatch) {
                    filtered.add(volume);
                }
            }
            return filtered.toArray(new StorageVolume[filtered.size()]);
        }
    
public java.lang.StringgetVolumeState(java.lang.String mountPoint)

return
state of the volume at the specified mount point

        synchronized (mVolumesLock) {
            String state = mVolumeStates.get(mountPoint);
            if (state == null) {
                Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
                if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
                    state = Environment.MEDIA_REMOVED;
                } else {
                    throw new IllegalArgumentException();
                }
            }

            return state;
        }
    
private voidhandleSystemReady()

        // Snapshot current volume states since it's not safe to call into vold
        // while holding locks.
        final HashMap<String, String> snapshot;
        synchronized (mVolumesLock) {
            snapshot = new HashMap<String, String>(mVolumeStates);
        }

        for (Map.Entry<String, String> entry : snapshot.entrySet()) {
            final String path = entry.getKey();
            final String state = entry.getValue();

            if (state.equals(Environment.MEDIA_UNMOUNTED)) {
                int rc = doMountVolume(path);
                if (rc != StorageResultCode.OperationSucceeded) {
                    Slog.e(TAG, String.format("Boot-time mount failed (%d)",
                            rc));
                }
            } else if (state.equals(Environment.MEDIA_SHARED)) {
                /*
                 * Bootstrap UMS enabled state since vold indicates
                 * the volume is shared (runtime restart while ums enabled)
                 */
                notifyVolumeStateChange(null, path, VolumeState.NoMedia,
                        VolumeState.Shared);
            }
        }

        // Push mounted state for all emulated storage
        synchronized (mVolumesLock) {
            for (StorageVolume volume : mVolumes) {
                if (volume.isEmulated()) {
                    updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
                }
            }
        }

        /*
         * If UMS was connected on boot, send the connected event
         * now that we're up.
         */
        if (mSendUmsConnectedOnBoot) {
            sendUmsIntent(true);
            mSendUmsConnectedOnBoot = false;
        }

        /*
         * Start scheduling nominally-daily fstrim operations
         */
        MountServiceIdler.scheduleIdlePass(mContext);
    
private booleanhasUserRestriction(java.lang.String restriction)

        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        return um.hasUserRestriction(restriction, Binder.getCallingUserHandle());
    
public booleanisExternalStorageEmulated()

        return mEmulatedTemplate != null;
    
public booleanisObbMounted(java.lang.String rawPath)

        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
        synchronized (mObbMounts) {
            return mObbPathToStateMap.containsKey(rawPath);
        }
    
private booleanisReady()

        try {
            return mConnectedSignal.await(0, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    
public booleanisSecureContainerMounted(java.lang.String id)

        validatePermission(android.Manifest.permission.ASEC_ACCESS);
        waitForReady();
        warnOnNotMounted();

        synchronized (mAsecMountSet) {
            return mAsecMountSet.contains(id);
        }
    
private booleanisUidOwnerOfPackageOrSystem(java.lang.String packageName, int callerUid)

        if (callerUid == android.os.Process.SYSTEM_UID) {
            return true;
        }

        if (packageName == null) {
            return false;
        }

        final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));

        if (DEBUG_OBB) {
            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
                    packageUid + ", callerUid = " + callerUid);
        }

        return callerUid == packageUid;
    
public booleanisUsbMassStorageConnected()

        waitForReady();

        if (getUmsEnabling()) {
            return true;
        }
        synchronized (mListeners) {
            return mUmsAvailable;
        }
    
public booleanisUsbMassStorageEnabled()

        waitForReady();

        final StorageVolume primary = getPrimaryPhysicalVolume();
        if (primary != null) {
            return doGetVolumeShared(primary.getPath(), "ums");
        } else {
            return false;
        }
    
public longlastMaintenance()

        return mLastMaintenance;
    
public static java.lang.StringmaybeTranslatePathForVold(java.lang.String path, java.io.File[] appPaths, java.io.File[] voldPaths)
Translate the given path from an app-visible path to a vold-visible path, but only if it's under the given whitelisted paths.

param
path a canonicalized app-visible path.
param
appPaths list of app-visible paths that are allowed.
param
voldPaths list of vold-visible paths directly corresponding to the allowed app-visible paths argument.
return
a vold-visible path representing the original path, or {@code null} if the given path didn't have an app-to-vold mapping.

        if (appPaths.length != voldPaths.length) {
            throw new IllegalStateException("Paths must be 1:1 mapping");
        }

        for (int i = 0; i < appPaths.length; i++) {
            final String appPath = appPaths[i].getAbsolutePath() + "/";
            if (path.startsWith(appPath)) {
                path = new File(voldPaths[i], path.substring(appPath.length()))
                        .getAbsolutePath();
                if (!path.endsWith("/")) {
                    path = path + "/";
                }
                return path;
            }
        }
        return null;
    
public intmkdirs(java.lang.String callingPkg, java.lang.String appPath)

        final int userId = UserHandle.getUserId(Binder.getCallingUid());
        final UserEnvironment userEnv = new UserEnvironment(userId);

        // Validate that reported package name belongs to caller
        final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
                Context.APP_OPS_SERVICE);
        appOps.checkPackage(Binder.getCallingUid(), callingPkg);

        try {
            appPath = new File(appPath).getCanonicalPath();
        } catch (IOException e) {
            Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
            return -1;
        }

        if (!appPath.endsWith("/")) {
            appPath = appPath + "/";
        }

        // Try translating the app path into a vold path, but require that it
        // belong to the calling package.
        String voldPath = maybeTranslatePathForVold(appPath,
                userEnv.buildExternalStorageAppDataDirs(callingPkg),
                userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
        if (voldPath != null) {
            try {
                mConnector.execute("volume", "mkdirs", voldPath);
                return 0;
            } catch (NativeDaemonConnectorException e) {
                return e.getCode();
            }
        }

        voldPath = maybeTranslatePathForVold(appPath,
                userEnv.buildExternalStorageAppObbDirs(callingPkg),
                userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
        if (voldPath != null) {
            try {
                mConnector.execute("volume", "mkdirs", voldPath);
                return 0;
            } catch (NativeDaemonConnectorException e) {
                return e.getCode();
            }
        }

        voldPath = maybeTranslatePathForVold(appPath,
                userEnv.buildExternalStorageAppMediaDirs(callingPkg),
                userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg));
        if (voldPath != null) {
            try {
                mConnector.execute("volume", "mkdirs", voldPath);
                return 0;
            } catch (NativeDaemonConnectorException e) {
                return e.getCode();
            }
        }

        throw new SecurityException("Invalid mkdirs path: " + appPath);
    
public voidmonitor()
{@inheritDoc}

        if (mConnector != null) {
            mConnector.monitor();
        }
    
public voidmountObb(java.lang.String rawPath, java.lang.String canonicalPath, java.lang.String key, android.os.storage.IObbActionListener token, int nonce)

        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
        Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
        Preconditions.checkNotNull(token, "token cannot be null");

        final int callingUid = Binder.getCallingUid();
        final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
        final ObbAction action = new MountObbAction(obbState, key, callingUid);
        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));

        if (DEBUG_OBB)
            Slog.i(TAG, "Send to OBB handler: " + action.toString());
    
public intmountSecureContainer(java.lang.String id, java.lang.String key, int ownerUid, boolean readOnly)

        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
        waitForReady();
        warnOnNotMounted();

        synchronized (mAsecMountSet) {
            if (mAsecMountSet.contains(id)) {
                return StorageResultCode.OperationFailedStorageMounted;
            }
        }

        int rc = StorageResultCode.OperationSucceeded;
        try {
            mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid,
                    readOnly ? "ro" : "rw");
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code != VoldResponseCode.OpFailedStorageBusy) {
                rc = StorageResultCode.OperationFailedInternalError;
            }
        }

        if (rc == StorageResultCode.OperationSucceeded) {
            synchronized (mAsecMountSet) {
                mAsecMountSet.add(id);
            }
        }
        return rc;
    
public intmountVolume(java.lang.String path)

        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
        waitForReady();
        return doMountVolume(path);
    
private voidnotifyShareAvailabilityChange(boolean avail)

        synchronized (mListeners) {
            mUmsAvailable = avail;
            for (int i = mListeners.size() -1; i >= 0; i--) {
                MountServiceBinderListener bl = mListeners.get(i);
                try {
                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
                } catch (RemoteException rex) {
                    Slog.e(TAG, "Listener dead");
                    mListeners.remove(i);
                } catch (Exception ex) {
                    Slog.e(TAG, "Listener failed", ex);
                }
            }
        }

        if (mSystemReady == true) {
            sendUmsIntent(avail);
        } else {
            mSendUmsConnectedOnBoot = avail;
        }

        final StorageVolume primary = getPrimaryPhysicalVolume();
        if (avail == false && primary != null
                && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) {
            final String path = primary.getPath();
            /*
             * USB mass storage disconnected while enabled
             */
            new Thread("MountService#AvailabilityChange") {
                @Override
                public void run() {
                    try {
                        int rc;
                        Slog.w(TAG, "Disabling UMS after cable disconnect");
                        doShareUnshareVolume(path, "ums", false);
                        if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
                            Slog.e(TAG, String.format(
                                    "Failed to remount {%s} on UMS enabled-disconnect (%d)",
                                            path, rc));
                        }
                    } catch (Exception ex) {
                        Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
                    }
                }
            }.start();
        }
    
private voidnotifyVolumeStateChange(java.lang.String label, java.lang.String path, int oldState, int newState)

        final StorageVolume volume;
        final String state;
        synchronized (mVolumesLock) {
            volume = mVolumesByPath.get(path);
            state = getVolumeState(path);
        }

        if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);

        String action = null;

        if (oldState == VolumeState.Shared && newState != oldState) {
            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
            sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
        }

        if (newState == VolumeState.Init) {
        } else if (newState == VolumeState.NoMedia) {
            // NoMedia is handled via Disk Remove events
        } else if (newState == VolumeState.Idle) {
            /*
             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
             * if we're in the process of enabling UMS
             */
            if (!state.equals(
                    Environment.MEDIA_BAD_REMOVAL) && !state.equals(
                            Environment.MEDIA_NOFS) && !state.equals(
                                    Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
                action = Intent.ACTION_MEDIA_UNMOUNTED;
            }
        } else if (newState == VolumeState.Pending) {
        } else if (newState == VolumeState.Checking) {
            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
            updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
            action = Intent.ACTION_MEDIA_CHECKING;
        } else if (newState == VolumeState.Mounted) {
            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
            action = Intent.ACTION_MEDIA_MOUNTED;
        } else if (newState == VolumeState.Unmounting) {
            action = Intent.ACTION_MEDIA_EJECT;
        } else if (newState == VolumeState.Formatting) {
        } else if (newState == VolumeState.Shared) {
            if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
            /* Send the media unmounted event first */
            updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
            sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);

            if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
            updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
            action = Intent.ACTION_MEDIA_SHARED;
            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
        } else if (newState == VolumeState.SharedMnt) {
            Slog.e(TAG, "Live shared mounts not supported yet!");
            return;
        } else {
            Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
        }

        if (action != null) {
            sendStorageIntent(action, volume, UserHandle.ALL);
        }
    
public booleanonCheckHoldWakeLock(int code)
Callback from NativeDaemonConnector

        return false;
    
public voidonDaemonConnected()
Callback from NativeDaemonConnector

        /*
         * Since we'll be calling back into the NativeDaemonConnector,
         * we need to do our work in a new thread.
         */
        new Thread("MountService#onDaemonConnected") {
            @Override
            public void run() {
                /**
                 * Determine media state and UMS detection status
                 */
                try {
                    final String[] vols = NativeDaemonEvent.filterMessageList(
                            mConnector.executeForList("volume", "list", "broadcast"),
                            VoldResponseCode.VolumeListResult);
                    for (String volstr : vols) {
                        String[] tok = volstr.split(" ");
                        // FMT: <label> <mountpoint> <state>
                        String path = tok[1];
                        String state = Environment.MEDIA_REMOVED;

                        final StorageVolume volume;
                        synchronized (mVolumesLock) {
                            volume = mVolumesByPath.get(path);
                        }

                        int st = Integer.parseInt(tok[2]);
                        if (st == VolumeState.NoMedia) {
                            state = Environment.MEDIA_REMOVED;
                        } else if (st == VolumeState.Idle) {
                            state = Environment.MEDIA_UNMOUNTED;
                        } else if (st == VolumeState.Mounted) {
                            state = Environment.MEDIA_MOUNTED;
                            Slog.i(TAG, "Media already mounted on daemon connection");
                        } else if (st == VolumeState.Shared) {
                            state = Environment.MEDIA_SHARED;
                            Slog.i(TAG, "Media shared on daemon connection");
                        } else {
                            throw new Exception(String.format("Unexpected state %d", st));
                        }

                        if (state != null) {
                            if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
                            updatePublicVolumeState(volume, state);
                        }
                    }
                } catch (Exception e) {
                    Slog.e(TAG, "Error processing initial volume state", e);
                    final StorageVolume primary = getPrimaryPhysicalVolume();
                    if (primary != null) {
                        updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
                    }
                }

                /*
                 * Now that we've done our initialization, release
                 * the hounds!
                 */
                mConnectedSignal.countDown();

                // On an encrypted device we can't see system properties yet, so pull
                // the system locale out of the mount service.
                if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
                    copyLocaleFromMountService();
                }

                // Let package manager load internal ASECs.
                mPms.scanAvailableAsecs();

                // Notify people waiting for ASECs to be scanned that it's done.
                mAsecsScanned.countDown();
            }
        }.start();
    
public booleanonEvent(int code, java.lang.String raw, java.lang.String[] cooked)
Callback from NativeDaemonConnector

        if (DEBUG_EVENTS) {
            StringBuilder builder = new StringBuilder();
            builder.append("onEvent::");
            builder.append(" raw= " + raw);
            if (cooked != null) {
                builder.append(" cooked = " );
                for (String str : cooked) {
                    builder.append(" " + str);
                }
            }
            Slog.i(TAG, builder.toString());
        }
        if (code == VoldResponseCode.VolumeStateChange) {
            /*
             * One of the volumes we're managing has changed state.
             * Format: "NNN Volume <label> <path> state changed
             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
             */
            notifyVolumeStateChange(
                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
                            Integer.parseInt(cooked[10]));
        } else if (code == VoldResponseCode.VolumeUuidChange) {
            // Format: nnn <label> <path> <uuid>
            final String path = cooked[2];
            final String uuid = (cooked.length > 3) ? cooked[3] : null;

            final StorageVolume vol = mVolumesByPath.get(path);
            if (vol != null) {
                vol.setUuid(uuid);
            }

        } else if (code == VoldResponseCode.VolumeUserLabelChange) {
            // Format: nnn <label> <path> <label>
            final String path = cooked[2];
            final String userLabel = (cooked.length > 3) ? cooked[3] : null;

            final StorageVolume vol = mVolumesByPath.get(path);
            if (vol != null) {
                vol.setUserLabel(userLabel);
            }

        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
                   (code == VoldResponseCode.VolumeDiskRemoved) ||
                   (code == VoldResponseCode.VolumeBadRemoval)) {
            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
            String action = null;
            final String label = cooked[2];
            final String path = cooked[3];
            int major = -1;
            int minor = -1;

            try {
                String devComp = cooked[6].substring(1, cooked[6].length() -1);
                String[] devTok = devComp.split(":");
                major = Integer.parseInt(devTok[0]);
                minor = Integer.parseInt(devTok[1]);
            } catch (Exception ex) {
                Slog.e(TAG, "Failed to parse major/minor", ex);
            }

            final StorageVolume volume;
            final String state;
            synchronized (mVolumesLock) {
                volume = mVolumesByPath.get(path);
                state = mVolumeStates.get(path);
            }

            if (code == VoldResponseCode.VolumeDiskInserted) {
                new Thread("MountService#VolumeDiskInserted") {
                    @Override
                    public void run() {
                        try {
                            int rc;
                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
                                Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
                            }
                        } catch (Exception ex) {
                            Slog.w(TAG, "Failed to mount media on insertion", ex);
                        }
                    }
                }.start();
            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
                /*
                 * This event gets trumped if we're already in BAD_REMOVAL state
                 */
                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
                    return true;
                }
                /* Send the media unmounted event first */
                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
                sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);

                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
                updatePublicVolumeState(volume, Environment.MEDIA_REMOVED);
                action = Intent.ACTION_MEDIA_REMOVED;
            } else if (code == VoldResponseCode.VolumeBadRemoval) {
                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
                /* Send the media unmounted event first */
                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
                sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);

                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
                updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
                action = Intent.ACTION_MEDIA_BAD_REMOVAL;
            } else if (code == VoldResponseCode.FstrimCompleted) {
                EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
            } else {
                Slog.e(TAG, String.format("Unknown code {%d}", code));
            }

            if (action != null) {
                sendStorageIntent(action, volume, UserHandle.ALL);
            }
        } else {
            return false;
        }

        return true;
    
private voidreadStorageListLocked()


       
        mVolumes.clear();
        mVolumeStates.clear();

        Resources resources = mContext.getResources();

        int id = com.android.internal.R.xml.storage_list;
        XmlResourceParser parser = resources.getXml(id);
        AttributeSet attrs = Xml.asAttributeSet(parser);

        try {
            XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
            while (true) {
                XmlUtils.nextElement(parser);

                String element = parser.getName();
                if (element == null) break;

                if (TAG_STORAGE.equals(element)) {
                    TypedArray a = resources.obtainAttributes(attrs,
                            com.android.internal.R.styleable.Storage);

                    String path = a.getString(
                            com.android.internal.R.styleable.Storage_mountPoint);
                    int descriptionId = a.getResourceId(
                            com.android.internal.R.styleable.Storage_storageDescription, -1);
                    CharSequence description = a.getText(
                            com.android.internal.R.styleable.Storage_storageDescription);
                    boolean primary = a.getBoolean(
                            com.android.internal.R.styleable.Storage_primary, false);
                    boolean removable = a.getBoolean(
                            com.android.internal.R.styleable.Storage_removable, false);
                    boolean emulated = a.getBoolean(
                            com.android.internal.R.styleable.Storage_emulated, false);
                    int mtpReserve = a.getInt(
                            com.android.internal.R.styleable.Storage_mtpReserve, 0);
                    boolean allowMassStorage = a.getBoolean(
                            com.android.internal.R.styleable.Storage_allowMassStorage, false);
                    // resource parser does not support longs, so XML value is in megabytes
                    long maxFileSize = a.getInt(
                            com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;

                    Slog.d(TAG, "got storage path: " + path + " description: " + description +
                            " primary: " + primary + " removable: " + removable +
                            " emulated: " + emulated +  " mtpReserve: " + mtpReserve +
                            " allowMassStorage: " + allowMassStorage +
                            " maxFileSize: " + maxFileSize);

                    if (emulated) {
                        // For devices with emulated storage, we create separate
                        // volumes for each known user.
                        mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
                                true, mtpReserve, false, maxFileSize, null);

                        final UserManagerService userManager = UserManagerService.getInstance();
                        for (UserInfo user : userManager.getUsers(false)) {
                            createEmulatedVolumeForUserLocked(user.getUserHandle());
                        }

                    } else {
                        if (path == null || description == null) {
                            Slog.e(TAG, "Missing storage path or description in readStorageList");
                        } else {
                            final StorageVolume volume = new StorageVolume(new File(path),
                                    descriptionId, primary, removable, emulated, mtpReserve,
                                    allowMassStorage, maxFileSize, null);
                            addVolumeLocked(volume);

                            // Until we hear otherwise, treat as unmounted
                            mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
                            volume.setState(Environment.MEDIA_UNMOUNTED);
                        }
                    }

                    a.recycle();
                }
            }
        } catch (XmlPullParserException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // Compute storage ID for each physical volume; emulated storage is
            // always 0 when defined.
            int index = isExternalStorageEmulated() ? 1 : 0;
            for (StorageVolume volume : mVolumes) {
                if (!volume.isEmulated()) {
                    volume.setStorageId(index++);
                }
            }
            parser.close();
        }
    
public voidregisterListener(android.os.storage.IMountServiceListener listener)
Exposed API calls below here

        synchronized (mListeners) {
            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
            try {
                listener.asBinder().linkToDeath(bl, 0);
                mListeners.add(bl);
            } catch (RemoteException rex) {
                Slog.e(TAG, "Failed to link to listener death");
            }
        }
    
private voidremoveObbStateLocked(com.android.server.MountService$ObbState obbState)

        final IBinder binder = obbState.getBinder();
        final List<ObbState> obbStates = mObbMounts.get(binder);
        if (obbStates != null) {
            if (obbStates.remove(obbState)) {
                obbState.unlink();
            }
            if (obbStates.isEmpty()) {
                mObbMounts.remove(binder);
            }
        }

        mObbPathToStateMap.remove(obbState.rawPath);
    
private voidremoveVolumeLocked(android.os.storage.StorageVolume volume)

        Slog.d(TAG, "removeVolumeLocked() " + volume);
        mVolumes.remove(volume);
        mVolumesByPath.remove(volume.getPath());
        mVolumeStates.remove(volume.getPath());
    
public intrenameSecureContainer(java.lang.String oldId, java.lang.String newId)

        validatePermission(android.Manifest.permission.ASEC_RENAME);
        waitForReady();
        warnOnNotMounted();

        synchronized (mAsecMountSet) {
            /*
             * Because a mounted container has active internal state which cannot be
             * changed while active, we must ensure both ids are not currently mounted.
             */
            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
                return StorageResultCode.OperationFailedStorageMounted;
            }
        }

        int rc = StorageResultCode.OperationSucceeded;
        try {
            mConnector.execute("asec", "rename", oldId, newId);
        } catch (NativeDaemonConnectorException e) {
            rc = StorageResultCode.OperationFailedInternalError;
        }

        return rc;
    
public intresizeSecureContainer(java.lang.String id, int sizeMb, java.lang.String key)

        validatePermission(android.Manifest.permission.ASEC_CREATE);
        waitForReady();
        warnOnNotMounted();

        int rc = StorageResultCode.OperationSucceeded;
        try {
            mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key));
        } catch (NativeDaemonConnectorException e) {
            rc = StorageResultCode.OperationFailedInternalError;
        }
        return rc;
    
voidrunIdleMaintenance(java.lang.Runnable callback)

        mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
    
public voidrunMaintenance()

        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
        runIdleMaintenance(null);
    
private voidsendStorageIntent(java.lang.String action, android.os.storage.StorageVolume volume, android.os.UserHandle user)

        final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath()));
        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
        mContext.sendBroadcastAsUser(intent, user);
    
private voidsendUmsIntent(boolean c)

        mContext.sendBroadcastAsUser(
                new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
                UserHandle.ALL);
    
public voidsetField(java.lang.String field, java.lang.String contents)
Set a field in the crypto header.

param
field field to set
param
contents contents to set in field


        waitForReady();

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "setfield", field, contents);
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    
private voidsetUmsEnabling(boolean enable)

        synchronized (mListeners) {
            mUmsEnabling = enable;
        }
    
public voidsetUsbMassStorageEnabled(boolean enable)

        waitForReady();
        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
        validateUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);

        final StorageVolume primary = getPrimaryPhysicalVolume();
        if (primary == null) return;

        // TODO: Add support for multiple share methods

        /*
         * If the volume is mounted and we're enabling then unmount it
         */
        String path = primary.getPath();
        String vs = getVolumeState(path);
        String method = "ums";
        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
            // Override for isUsbMassStorageEnabled()
            setUmsEnabling(enable);
            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
            // Clear override
            setUmsEnabling(false);
        }
        /*
         * If we disabled UMS then mount the volume
         */
        if (!enable) {
            doShareUnshareVolume(path, method, enable);
            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
                Slog.e(TAG, "Failed to remount " + path +
                        " after disabling share method " + method);
                /*
                 * Even though the mount failed, the unshare didn't so don't indicate an error.
                 * The mountVolume() call will have set the storage state and sent the necessary
                 * broadcasts.
                 */
            }
        }
    
public voidshutdown(android.os.storage.IMountShutdownObserver observer)

        validatePermission(android.Manifest.permission.SHUTDOWN);

        Slog.i(TAG, "Shutting down");
        synchronized (mVolumesLock) {
            // Get all volumes to be unmounted.
            MountShutdownLatch mountShutdownLatch = new MountShutdownLatch(observer,
                                                            mVolumeStates.size());

            for (String path : mVolumeStates.keySet()) {
                String state = mVolumeStates.get(path);

                if (state.equals(Environment.MEDIA_SHARED)) {
                    /*
                     * If the media is currently shared, unshare it.
                     * XXX: This is still dangerous!. We should not
                     * be rebooting at *all* if UMS is enabled, since
                     * the UMS host could have dirty FAT cache entries
                     * yet to flush.
                     */
                    setUsbMassStorageEnabled(false);
                } else if (state.equals(Environment.MEDIA_CHECKING)) {
                    /*
                     * If the media is being checked, then we need to wait for
                     * it to complete before being able to proceed.
                     */
                    // XXX: @hackbod - Should we disable the ANR timer here?
                    int retries = 30;
                    while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException iex) {
                            Slog.e(TAG, "Interrupted while waiting for media", iex);
                            break;
                        }
                        state = Environment.getExternalStorageState();
                    }
                    if (retries == 0) {
                        Slog.e(TAG, "Timed out waiting for media to check");
                    }
                }

                if (state.equals(Environment.MEDIA_MOUNTED)) {
                    // Post a unmount message.
                    ShutdownCallBack ucb = new ShutdownCallBack(path, mountShutdownLatch);
                    mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
                } else if (observer != null) {
                    /*
                     * Count down, since nothing will be done. The observer will be
                     * notified when we are done so shutdown sequence can continue.
                     */
                    mountShutdownLatch.countDown();
                    Slog.i(TAG, "Unmount completed: " + path +
                        ", result code: " + StorageResultCode.OperationSucceeded);
                }
            }
        }
    
public voidsystemReady()

        mSystemReady = true;
        mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
    
private java.lang.StringtoHex(java.lang.String password)

        if (password == null) {
            return new String();
        }
        byte[] bytes = password.getBytes(StandardCharsets.UTF_8);
        return new String(Hex.encodeHex(bytes));
    
public voidunmountObb(java.lang.String rawPath, boolean force, android.os.storage.IObbActionListener token, int nonce)

        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");

        final ObbState existingState;
        synchronized (mObbPathToStateMap) {
            existingState = mObbPathToStateMap.get(rawPath);
        }

        if (existingState != null) {
            // TODO: separate state object from request data
            final int callingUid = Binder.getCallingUid();
            final ObbState newState = new ObbState(
                    rawPath, existingState.canonicalPath, callingUid, token, nonce);
            final ObbAction action = new UnmountObbAction(newState, force);
            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));

            if (DEBUG_OBB)
                Slog.i(TAG, "Send to OBB handler: " + action.toString());
        } else {
            Slog.w(TAG, "Unknown OBB mount at " + rawPath);
        }
    
public intunmountSecureContainer(java.lang.String id, boolean force)

        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
        waitForReady();
        warnOnNotMounted();

        synchronized (mAsecMountSet) {
            if (!mAsecMountSet.contains(id)) {
                return StorageResultCode.OperationFailedStorageNotMounted;
            }
         }

        /*
         * Force a GC to make sure AssetManagers in other threads of the
         * system_server are cleaned up. We have to do this since AssetManager
         * instances are kept as a WeakReference and it's possible we have files
         * open on the external storage.
         */
        Runtime.getRuntime().gc();

        int rc = StorageResultCode.OperationSucceeded;
        try {
            final Command cmd = new Command("asec", "unmount", id);
            if (force) {
                cmd.appendArg("force");
            }
            mConnector.execute(cmd);
        } catch (NativeDaemonConnectorException e) {
            int code = e.getCode();
            if (code == VoldResponseCode.OpFailedStorageBusy) {
                rc = StorageResultCode.OperationFailedStorageBusy;
            } else {
                rc = StorageResultCode.OperationFailedInternalError;
            }
        }

        if (rc == StorageResultCode.OperationSucceeded) {
            synchronized (mAsecMountSet) {
                mAsecMountSet.remove(id);
            }
        }
        return rc;
    
public voidunmountVolume(java.lang.String path, boolean force, boolean removeEncryption)

        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
        waitForReady();

        String volState = getVolumeState(path);
        if (DEBUG_UNMOUNT) {
            Slog.i(TAG, "Unmounting " + path
                    + " force = " + force
                    + " removeEncryption = " + removeEncryption);
        }
        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
                Environment.MEDIA_REMOVED.equals(volState) ||
                Environment.MEDIA_SHARED.equals(volState) ||
                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
            // Media already unmounted or cannot be unmounted.
            // TODO return valid return code when adding observer call back.
            return;
        }
        UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
    
public voidunregisterListener(android.os.storage.IMountServiceListener listener)

        synchronized (mListeners) {
            for(MountServiceBinderListener bl : mListeners) {
                if (bl.mListener.asBinder() == listener.asBinder()) {
                    mListeners.remove(mListeners.indexOf(bl));
                    listener.asBinder().unlinkToDeath(bl, 0);
                    return;
                }
            }
        }
    
private voidupdatePublicVolumeState(android.os.storage.StorageVolume volume, java.lang.String state)

        final String path = volume.getPath();
        final String oldState;
        synchronized (mVolumesLock) {
            oldState = mVolumeStates.put(path, state);
            volume.setState(state);
        }

        if (state.equals(oldState)) {
            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
                    state, state, path));
            return;
        }

        Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");

        // Tell PackageManager about changes to primary volume state, but only
        // when not emulated.
        if (volume.isPrimary() && !volume.isEmulated()) {
            if (Environment.MEDIA_UNMOUNTED.equals(state)) {
                mPms.updateExternalMediaStatus(false, false);

                /*
                 * Some OBBs might have been unmounted when this volume was
                 * unmounted, so send a message to the handler to let it know to
                 * remove those from the list of mounted OBBS.
                 */
                mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
                        OBB_FLUSH_MOUNT_STATE, path));
            } else if (Environment.MEDIA_MOUNTED.equals(state)) {
                mPms.updateExternalMediaStatus(true, false);
            }
        }

        synchronized (mListeners) {
            for (int i = mListeners.size() -1; i >= 0; i--) {
                MountServiceBinderListener bl = mListeners.get(i);
                try {
                    bl.mListener.onStorageStateChanged(path, oldState, state);
                } catch (RemoteException rex) {
                    Slog.e(TAG, "Listener dead");
                    mListeners.remove(i);
                } catch (Exception ex) {
                    Slog.e(TAG, "Listener failed", ex);
                }
            }
        }
    
private voidvalidatePermission(java.lang.String perm)

        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException(String.format("Requires %s permission", perm));
        }
    
private voidvalidateUserRestriction(java.lang.String restriction)

        if (hasUserRestriction(restriction)) {
            throw new SecurityException("User has restriction " + restriction);
        }
    
public intverifyEncryptionPassword(java.lang.String password)
Validate a user-supplied password string with cryptfs

        // Only the system process is permitted to validate passwords
        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
            throw new SecurityException("no permission to access the crypt keeper");
        }

        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
            "no permission to access the crypt keeper");

        if (TextUtils.isEmpty(password)) {
            throw new IllegalArgumentException("password cannot be empty");
        }

        waitForReady();

        if (DEBUG_EVENTS) {
            Slog.i(TAG, "validating encryption password...");
        }

        final NativeDaemonEvent event;
        try {
            event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(toHex(password)));
            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
            return Integer.parseInt(event.getMessage());
        } catch (NativeDaemonConnectorException e) {
            // Encryption failed
            return e.getCode();
        }
    
voidwaitForAsecScan()

        waitForLatch(mAsecsScanned);
    
private voidwaitForLatch(java.util.concurrent.CountDownLatch latch)

        for (;;) {
            try {
                if (latch.await(5000, TimeUnit.MILLISECONDS)) {
                    return;
                } else {
                    Slog.w(TAG, "Thread " + Thread.currentThread().getName()
                            + " still waiting for MountService ready...");
                }
            } catch (InterruptedException e) {
                Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
            }
        }
    
private voidwaitForReady()

        waitForLatch(mConnectedSignal);
    
private voidwarnOnNotMounted()

        final StorageVolume primary = getPrimaryPhysicalVolume();
        if (primary != null) {
            boolean mounted = false;
            try {
                mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
            } catch (IllegalArgumentException e) {
            }

            if (!mounted) {
                Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
            }
        }