/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.StorageResultCode; import android.os.RemoteException; import android.os.IBinder; import android.os.Environment; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UEventObserver; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; import java.util.HashSet; import java.io.File; import java.io.FileReader; /** * MountService implements back-end services for platform storage * management. * @hide - Applications should use android.os.storage.StorageManager * to access the MountService. */ class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks { private static final boolean LOCAL_LOGD = false; private static final String TAG = "MountService"; /* * Internal vold volume state constants */ class VolumeState { public static final int Init = -1; public static final int NoMedia = 0; public static final int Idle = 1; public static final int Pending = 2; public static final int Checking = 3; public static final int Mounted = 4; public static final int Unmounting = 5; public static final int Formatting = 6; public static final int Shared = 7; public static final int SharedMnt = 8; } /* * Internal vold response code constants */ class VoldResponseCode { /* * 100 series - Requestion action was initiated; expect another reply * before proceeding with a new command. */ public static final int VolumeListResult = 110; public static final int AsecListResult = 111; /* * 200 series - Requestion action has been successfully completed. */ public static final int ShareStatusResult = 210; public static final int AsecPathResult = 211; public static final int ShareEnabledResult = 212; /* * 400 series - Command was accepted, but the requested action * did not take place. */ public static final int OpFailedNoMedia = 401; public static final int OpFailedMediaBlank = 402; public static final int OpFailedMediaCorrupt = 403; public static final int OpFailedVolNotMounted = 404; public static final int OpFailedVolBusy = 405; /* * 600 series - Unsolicited broadcasts. */ public static final int VolumeStateChange = 605; public static final int ShareAvailabilityChange = 620; public static final int VolumeDiskInserted = 630; public static final int VolumeDiskRemoved = 631; public static final int VolumeBadRemoval = 632; } private Context mContext; private NativeDaemonConnector mConnector; private String mLegacyState = Environment.MEDIA_REMOVED; private PackageManagerService mPms; private boolean mUmsEnabling; private ArrayList mListeners; private boolean mBooted; private boolean mReady; /** * Private hash of currently mounted secure containers. */ private HashSet mAsecMountSet = new HashSet(); private void waitForReady() { while (mReady == false) { for (int retries = 5; retries > 0; retries--) { if (mReady) { return; } SystemClock.sleep(1000); } Log.w(TAG, "Waiting too long for mReady!"); } } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { mBooted = true; String path = Environment.getExternalStorageDirectory().getPath(); if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) { int rc = doMountVolume(path); if (rc != StorageResultCode.OperationSucceeded) { Log.e(TAG, String.format("Boot-time mount failed (%d)", rc)); } } } } }; private final class MountServiceBinderListener implements IBinder.DeathRecipient { final IMountServiceListener mListener; MountServiceBinderListener(IMountServiceListener listener) { mListener = listener; } public void binderDied() { if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!"); synchronized(mListeners) { mListeners.remove(this); mListener.asBinder().unlinkToDeath(this, 0); } } } private int doShareUnshareVolume(String path, String method, boolean enable) { validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); // TODO: Add support for multiple share methods if (!method.equals("ums")) { throw new IllegalArgumentException(String.format("Method %s not supported", method)); } /* * If the volume is mounted and we're enabling then unmount it */ String vs = getVolumeState(path); if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { mUmsEnabling = enable; // Override for isUsbMassStorageEnabled() int rc = doUnmountVolume(path); mUmsEnabling = false; // Clear override if (rc != StorageResultCode.OperationSucceeded) { Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc)); return rc; } } try { mConnector.doCommand(String.format( "volume %sshare %s %s", (enable ? "" : "un"), path, method)); } catch (NativeDaemonConnectorException e) { Log.e(TAG, "Failed to share/unshare", e); return StorageResultCode.OperationFailedInternalError; } /* * If we disabled UMS then mount the volume */ if (!enable) { if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { Log.e(TAG, String.format( "Failed to remount %s after disabling share method %s", path, 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. */ } } return StorageResultCode.OperationSucceeded; } private void updatePublicVolumeState(String path, String state) { if (!path.equals(Environment.getExternalStorageDirectory().getPath())) { Log.w(TAG, "Multiple volumes not currently supported"); return; } if (mLegacyState.equals(state)) { Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); return; } String oldState = mLegacyState; mLegacyState = state; 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) { Log.e(TAG, "Listener dead"); mListeners.remove(i); } catch (Exception ex) { Log.e(TAG, "Listener failed", ex); } } } } /** * * Callback from NativeDaemonConnector */ public void onDaemonConnected() { /* * Since we'll be calling back into the NativeDaemonConnector, * we need to do our work in a new thread. */ new Thread() { public void run() { /** * Determine media state and UMS detection status */ String path = Environment.getExternalStorageDirectory().getPath(); String state = Environment.MEDIA_REMOVED; try { String[] vols = mConnector.doListCommand( "volume list", VoldResponseCode.VolumeListResult); for (String volstr : vols) { String[] tok = volstr.split(" "); // FMT: