blob: d3ac026d430a3667d2dd862b7c33b79b28613fb5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -080019import com.android.server.am.ActivityManagerService;
20
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.PackageManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.net.Uri;
San Mehatb1043402010-02-05 08:26:50 -080027import android.os.storage.IMountService;
28import android.os.storage.IMountServiceListener;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -080029import android.os.storage.IMountShutdownObserver;
San Mehatb1043402010-02-05 08:26:50 -080030import android.os.storage.StorageResultCode;
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -080031import android.os.Handler;
Daniel Sandler5f27ef42010-03-16 15:42:02 -040032import android.os.HandlerThread;
33import android.os.Looper;
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -080034import android.os.Message;
San Mehat4270e1e2010-01-29 05:32:19 -080035import android.os.RemoteException;
36import android.os.IBinder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.os.Environment;
Suchi Amalapurapufd3530f2010-01-18 00:15:59 -080038import android.os.ServiceManager;
San Mehat207e5382010-02-04 20:46:54 -080039import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.os.SystemProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.util.Log;
San Mehat22dd86e2010-01-12 12:21:18 -080042import java.util.ArrayList;
San Mehat6cdd9c02010-02-09 14:45:20 -080043import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045/**
San Mehatb1043402010-02-05 08:26:50 -080046 * MountService implements back-end services for platform storage
47 * management.
48 * @hide - Applications should use android.os.storage.StorageManager
49 * to access the MountService.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 */
San Mehat22dd86e2010-01-12 12:21:18 -080051class MountService extends IMountService.Stub
52 implements INativeDaemonConnectorCallbacks {
San Mehatb1043402010-02-05 08:26:50 -080053 private static final boolean LOCAL_LOGD = false;
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -080054 private static final boolean DEBUG_UNMOUNT = false;
55 private static final boolean DEBUG_EVENTS = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
57 private static final String TAG = "MountService";
58
San Mehat4270e1e2010-01-29 05:32:19 -080059 /*
60 * Internal vold volume state constants
61 */
San Mehat7fd0fee2009-12-17 07:12:23 -080062 class VolumeState {
63 public static final int Init = -1;
64 public static final int NoMedia = 0;
65 public static final int Idle = 1;
66 public static final int Pending = 2;
67 public static final int Checking = 3;
68 public static final int Mounted = 4;
69 public static final int Unmounting = 5;
70 public static final int Formatting = 6;
71 public static final int Shared = 7;
72 public static final int SharedMnt = 8;
73 }
74
San Mehat4270e1e2010-01-29 05:32:19 -080075 /*
76 * Internal vold response code constants
77 */
San Mehat22dd86e2010-01-12 12:21:18 -080078 class VoldResponseCode {
San Mehat4270e1e2010-01-29 05:32:19 -080079 /*
80 * 100 series - Requestion action was initiated; expect another reply
81 * before proceeding with a new command.
82 */
San Mehat22dd86e2010-01-12 12:21:18 -080083 public static final int VolumeListResult = 110;
84 public static final int AsecListResult = 111;
San Mehatc1b4ce92010-02-16 17:13:03 -080085 public static final int StorageUsersListResult = 112;
San Mehat22dd86e2010-01-12 12:21:18 -080086
San Mehat4270e1e2010-01-29 05:32:19 -080087 /*
88 * 200 series - Requestion action has been successfully completed.
89 */
90 public static final int ShareStatusResult = 210;
San Mehat22dd86e2010-01-12 12:21:18 -080091 public static final int AsecPathResult = 211;
San Mehat4270e1e2010-01-29 05:32:19 -080092 public static final int ShareEnabledResult = 212;
San Mehat22dd86e2010-01-12 12:21:18 -080093
San Mehat4270e1e2010-01-29 05:32:19 -080094 /*
95 * 400 series - Command was accepted, but the requested action
96 * did not take place.
97 */
98 public static final int OpFailedNoMedia = 401;
99 public static final int OpFailedMediaBlank = 402;
100 public static final int OpFailedMediaCorrupt = 403;
101 public static final int OpFailedVolNotMounted = 404;
San Mehatd9709982010-02-18 11:43:03 -0800102 public static final int OpFailedStorageBusy = 405;
San Mehat4270e1e2010-01-29 05:32:19 -0800103
104 /*
105 * 600 series - Unsolicited broadcasts.
106 */
San Mehat22dd86e2010-01-12 12:21:18 -0800107 public static final int VolumeStateChange = 605;
San Mehat22dd86e2010-01-12 12:21:18 -0800108 public static final int ShareAvailabilityChange = 620;
109 public static final int VolumeDiskInserted = 630;
110 public static final int VolumeDiskRemoved = 631;
111 public static final int VolumeBadRemoval = 632;
112 }
113
San Mehat4270e1e2010-01-29 05:32:19 -0800114 private Context mContext;
115 private NativeDaemonConnector mConnector;
116 private String mLegacyState = Environment.MEDIA_REMOVED;
117 private PackageManagerService mPms;
118 private boolean mUmsEnabling;
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800119 // Used as a lock for methods that register/unregister listeners.
120 final private ArrayList<MountServiceBinderListener> mListeners =
121 new ArrayList<MountServiceBinderListener>();
San Mehat6a965af22010-02-24 17:47:30 -0800122 private boolean mBooted = false;
123 private boolean mReady = false;
124 private boolean mSendUmsConnectedOnBoot = false;
Suchi Amalapurapufd3530f2010-01-18 00:15:59 -0800125
San Mehat6cdd9c02010-02-09 14:45:20 -0800126 /**
127 * Private hash of currently mounted secure containers.
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800128 * Used as a lock in methods to manipulate secure containers.
San Mehat6cdd9c02010-02-09 14:45:20 -0800129 */
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800130 final private HashSet<String> mAsecMountSet = new HashSet<String>();
San Mehat6cdd9c02010-02-09 14:45:20 -0800131
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800132 private static final int H_UNMOUNT_PM_UPDATE = 1;
133 private static final int H_UNMOUNT_PM_DONE = 2;
134 private static final int H_UNMOUNT_MS = 3;
135 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
136 private static final int MAX_UNMOUNT_RETRIES = 4;
137
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800138 class UnmountCallBack {
139 String path;
140 int retries;
141 boolean force;
142
143 UnmountCallBack(String path, boolean force) {
144 retries = 0;
145 this.path = path;
146 this.force = force;
147 }
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800148
149 void handleFinished() {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800150 if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path);
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800151 doUnmountVolume(path, true);
152 }
153 }
154
155 class UmsEnableCallBack extends UnmountCallBack {
156 String method;
157
158 UmsEnableCallBack(String path, String method, boolean force) {
159 super(path, force);
160 this.method = method;
161 }
162
163 @Override
164 void handleFinished() {
165 super.handleFinished();
166 doShareUnshareVolume(path, method, true);
167 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800168 }
169
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800170 class ShutdownCallBack extends UnmountCallBack {
171 IMountShutdownObserver observer;
172 ShutdownCallBack(String path, IMountShutdownObserver observer) {
173 super(path, true);
174 this.observer = observer;
175 }
176
177 @Override
178 void handleFinished() {
179 int ret = doUnmountVolume(path, true);
180 if (observer != null) {
181 try {
182 observer.onShutDownComplete(ret);
183 } catch (RemoteException e) {
184 Log.w(TAG, "RemoteException when shutting down");
185 }
186 }
187 }
188 }
189
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400190 class MountServiceHandler extends Handler {
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800191 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700192 boolean mUpdatingStatus = false;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800193
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400194 MountServiceHandler(Looper l) {
195 super(l);
196 }
197
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800198 public void handleMessage(Message msg) {
199 switch (msg.what) {
200 case H_UNMOUNT_PM_UPDATE: {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800201 if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_UPDATE");
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800202 UnmountCallBack ucb = (UnmountCallBack) msg.obj;
203 mForceUnmounts.add(ucb);
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700204 if (DEBUG_UNMOUNT) Log.i(TAG, " registered = " + mUpdatingStatus);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800205 // Register only if needed.
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700206 if (!mUpdatingStatus) {
207 if (DEBUG_UNMOUNT) Log.i(TAG, "Updating external media status on PackageManager");
208 mUpdatingStatus = true;
209 mPms.updateExternalMediaStatus(false, true);
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800210 }
211 break;
212 }
213 case H_UNMOUNT_PM_DONE: {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800214 if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_DONE");
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700215 if (!mUpdatingStatus) {
216 // Does not correspond to unmount's status update.
217 return;
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800218 }
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700219 if (DEBUG_UNMOUNT) Log.i(TAG, "Updated status. Processing requests");
220 mUpdatingStatus = false;
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800221 int size = mForceUnmounts.size();
222 int sizeArr[] = new int[size];
223 int sizeArrN = 0;
224 for (int i = 0; i < size; i++) {
225 UnmountCallBack ucb = mForceUnmounts.get(i);
226 String path = ucb.path;
227 boolean done = false;
228 if (!ucb.force) {
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800229 done = true;
230 } else {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800231 int pids[] = getStorageUsers(path);
232 if (pids == null || pids.length == 0) {
233 done = true;
234 } else {
235 // Kill processes holding references first
236 ActivityManagerService ams = (ActivityManagerService)
237 ServiceManager.getService("activity");
238 // Eliminate system process here?
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700239 boolean ret = ams.killPids(pids, "Unmount media");
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800240 if (ret) {
241 // Confirm if file references have been freed.
242 pids = getStorageUsers(path);
243 if (pids == null || pids.length == 0) {
244 done = true;
245 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800246 }
247 }
248 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800249 if (done) {
250 sizeArr[sizeArrN++] = i;
251 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
252 ucb));
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800253 } else {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800254 if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700255 Log.i(TAG, "Cannot unmount media inspite of " +
256 MAX_UNMOUNT_RETRIES + " retries");
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800257 // Send final broadcast indicating failure to unmount.
258 } else {
259 mHandler.sendMessageDelayed(
260 mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
261 ucb.retries++),
262 RETRY_UNMOUNT_DELAY);
263 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800264 }
265 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800266 // Remove already processed elements from list.
267 for (int i = (sizeArrN-1); i >= 0; i--) {
268 mForceUnmounts.remove(sizeArr[i]);
269 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800270 break;
271 }
272 case H_UNMOUNT_MS : {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800273 if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_MS");
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800274 UnmountCallBack ucb = (UnmountCallBack) msg.obj;
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800275 ucb.handleFinished();
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800276 break;
277 }
278 }
279 }
280 };
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400281 final private HandlerThread mHandlerThread;
282 final private Handler mHandler;
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800283
San Mehat207e5382010-02-04 20:46:54 -0800284 private void waitForReady() {
285 while (mReady == false) {
286 for (int retries = 5; retries > 0; retries--) {
287 if (mReady) {
288 return;
289 }
290 SystemClock.sleep(1000);
291 }
292 Log.w(TAG, "Waiting too long for mReady!");
293 }
San Mehat1f6301e2010-01-07 22:40:27 -0800294 }
295
San Mehat207e5382010-02-04 20:46:54 -0800296 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 public void onReceive(Context context, Intent intent) {
San Mehat91c77612010-01-07 10:39:41 -0800298 String action = intent.getAction();
299
300 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
San Mehat207e5382010-02-04 20:46:54 -0800301 mBooted = true;
San Mehat22dd86e2010-01-12 12:21:18 -0800302
Marco Nelissenc34ebce2010-02-18 13:39:41 -0800303 /*
304 * In the simulator, we need to broadcast a volume mounted event
305 * to make the media scanner run.
306 */
307 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
308 notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
309 return;
310 }
San Mehatfafb0412010-02-18 19:40:04 -0800311 new Thread() {
312 public void run() {
313 try {
314 String path = Environment.getExternalStorageDirectory().getPath();
San Mehat6a254402010-03-22 10:21:00 -0700315 String state = getVolumeState(path);
316
317 if (state.equals(Environment.MEDIA_UNMOUNTED)) {
San Mehatfafb0412010-02-18 19:40:04 -0800318 int rc = doMountVolume(path);
319 if (rc != StorageResultCode.OperationSucceeded) {
320 Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
321 }
San Mehat6a254402010-03-22 10:21:00 -0700322 } else if (state.equals(Environment.MEDIA_SHARED)) {
323 /*
324 * Bootstrap UMS enabled state since vold indicates
325 * the volume is shared (runtime restart while ums enabled)
326 */
327 notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
San Mehatfafb0412010-02-18 19:40:04 -0800328 }
San Mehat6a254402010-03-22 10:21:00 -0700329
San Mehat6a965af22010-02-24 17:47:30 -0800330 /*
San Mehat6a254402010-03-22 10:21:00 -0700331 * If UMS was connected on boot, send the connected event
San Mehat6a965af22010-02-24 17:47:30 -0800332 * now that we're up.
333 */
334 if (mSendUmsConnectedOnBoot) {
335 sendUmsIntent(true);
336 mSendUmsConnectedOnBoot = false;
337 }
San Mehatfafb0412010-02-18 19:40:04 -0800338 } catch (Exception ex) {
339 Log.e(TAG, "Boot-time mount exception", ex);
340 }
San Mehat207e5382010-02-04 20:46:54 -0800341 }
San Mehatfafb0412010-02-18 19:40:04 -0800342 }.start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 }
344 }
345 };
346
San Mehat4270e1e2010-01-29 05:32:19 -0800347 private final class MountServiceBinderListener implements IBinder.DeathRecipient {
348 final IMountServiceListener mListener;
349
350 MountServiceBinderListener(IMountServiceListener listener) {
351 mListener = listener;
352
San Mehat91c77612010-01-07 10:39:41 -0800353 }
354
San Mehat4270e1e2010-01-29 05:32:19 -0800355 public void binderDied() {
San Mehatb1043402010-02-05 08:26:50 -0800356 if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
San Mehat4270e1e2010-01-29 05:32:19 -0800357 synchronized(mListeners) {
358 mListeners.remove(this);
359 mListener.asBinder().unlinkToDeath(this, 0);
360 }
361 }
362 }
363
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800364 private void doShareUnshareVolume(String path, String method, boolean enable) {
San Mehat4270e1e2010-01-29 05:32:19 -0800365 // TODO: Add support for multiple share methods
366 if (!method.equals("ums")) {
367 throw new IllegalArgumentException(String.format("Method %s not supported", method));
368 }
369
San Mehat4270e1e2010-01-29 05:32:19 -0800370 try {
371 mConnector.doCommand(String.format(
372 "volume %sshare %s %s", (enable ? "" : "un"), path, method));
373 } catch (NativeDaemonConnectorException e) {
374 Log.e(TAG, "Failed to share/unshare", e);
San Mehat4270e1e2010-01-29 05:32:19 -0800375 }
San Mehat4270e1e2010-01-29 05:32:19 -0800376 }
377
San Mehat207e5382010-02-04 20:46:54 -0800378 private void updatePublicVolumeState(String path, String state) {
San Mehat4270e1e2010-01-29 05:32:19 -0800379 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
380 Log.w(TAG, "Multiple volumes not currently supported");
381 return;
382 }
San Mehatb1043402010-02-05 08:26:50 -0800383
384 if (mLegacyState.equals(state)) {
385 Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
386 return;
387 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800388 // Update state on PackageManager
389 if (Environment.MEDIA_UNMOUNTED.equals(state)) {
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700390 mPms.updateExternalMediaStatus(false, false);
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800391 } else if (Environment.MEDIA_MOUNTED.equals(state)) {
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700392 mPms.updateExternalMediaStatus(true, false);
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800393 }
San Mehat4270e1e2010-01-29 05:32:19 -0800394 String oldState = mLegacyState;
395 mLegacyState = state;
396
397 synchronized (mListeners) {
398 for (int i = mListeners.size() -1; i >= 0; i--) {
399 MountServiceBinderListener bl = mListeners.get(i);
400 try {
San Mehatb1043402010-02-05 08:26:50 -0800401 bl.mListener.onStorageStateChanged(path, oldState, state);
San Mehat4270e1e2010-01-29 05:32:19 -0800402 } catch (RemoteException rex) {
403 Log.e(TAG, "Listener dead");
404 mListeners.remove(i);
405 } catch (Exception ex) {
406 Log.e(TAG, "Listener failed", ex);
407 }
408 }
409 }
410 }
411
412 /**
413 *
414 * Callback from NativeDaemonConnector
415 */
416 public void onDaemonConnected() {
417 /*
418 * Since we'll be calling back into the NativeDaemonConnector,
419 * we need to do our work in a new thread.
420 */
421 new Thread() {
422 public void run() {
423 /**
424 * Determine media state and UMS detection status
425 */
426 String path = Environment.getExternalStorageDirectory().getPath();
427 String state = Environment.MEDIA_REMOVED;
428
429 try {
430 String[] vols = mConnector.doListCommand(
431 "volume list", VoldResponseCode.VolumeListResult);
432 for (String volstr : vols) {
433 String[] tok = volstr.split(" ");
434 // FMT: <label> <mountpoint> <state>
435 if (!tok[1].equals(path)) {
436 Log.w(TAG, String.format(
437 "Skipping unknown volume '%s'",tok[1]));
438 continue;
439 }
440 int st = Integer.parseInt(tok[2]);
441 if (st == VolumeState.NoMedia) {
442 state = Environment.MEDIA_REMOVED;
443 } else if (st == VolumeState.Idle) {
San Mehat207e5382010-02-04 20:46:54 -0800444 state = Environment.MEDIA_UNMOUNTED;
San Mehat4270e1e2010-01-29 05:32:19 -0800445 } else if (st == VolumeState.Mounted) {
446 state = Environment.MEDIA_MOUNTED;
447 Log.i(TAG, "Media already mounted on daemon connection");
448 } else if (st == VolumeState.Shared) {
449 state = Environment.MEDIA_SHARED;
450 Log.i(TAG, "Media shared on daemon connection");
451 } else {
452 throw new Exception(String.format("Unexpected state %d", st));
453 }
454 }
455 if (state != null) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800456 if (DEBUG_EVENTS) Log.i(TAG, "Updating valid state " + state);
San Mehat4270e1e2010-01-29 05:32:19 -0800457 updatePublicVolumeState(path, state);
458 }
459 } catch (Exception e) {
460 Log.e(TAG, "Error processing initial volume state", e);
461 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
462 }
463
464 try {
San Mehat207e5382010-02-04 20:46:54 -0800465 boolean avail = doGetShareMethodAvailable("ums");
San Mehat4270e1e2010-01-29 05:32:19 -0800466 notifyShareAvailabilityChange("ums", avail);
467 } catch (Exception ex) {
468 Log.w(TAG, "Failed to get share availability");
469 }
San Mehat207e5382010-02-04 20:46:54 -0800470 /*
471 * Now that we've done our initialization, release
472 * the hounds!
473 */
474 mReady = true;
San Mehat4270e1e2010-01-29 05:32:19 -0800475 }
476 }.start();
477 }
478
479 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800480 * Callback from NativeDaemonConnector
481 */
482 public boolean onEvent(int code, String raw, String[] cooked) {
483 Intent in = null;
484
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800485 if (DEBUG_EVENTS) {
486 StringBuilder builder = new StringBuilder();
487 builder.append("onEvent::");
488 builder.append(" raw= " + raw);
489 if (cooked != null) {
490 builder.append(" cooked = " );
491 for (String str : cooked) {
492 builder.append(" " + str);
493 }
494 }
495 Log.i(TAG, builder.toString());
496 }
San Mehat4270e1e2010-01-29 05:32:19 -0800497 if (code == VoldResponseCode.VolumeStateChange) {
498 /*
499 * One of the volumes we're managing has changed state.
500 * Format: "NNN Volume <label> <path> state changed
501 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
502 */
503 notifyVolumeStateChange(
504 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
505 Integer.parseInt(cooked[10]));
506 } else if (code == VoldResponseCode.ShareAvailabilityChange) {
507 // FMT: NNN Share method <method> now <available|unavailable>
508 boolean avail = false;
509 if (cooked[5].equals("available")) {
510 avail = true;
511 }
512 notifyShareAvailabilityChange(cooked[3], avail);
513 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
514 (code == VoldResponseCode.VolumeDiskRemoved) ||
515 (code == VoldResponseCode.VolumeBadRemoval)) {
516 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
517 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
518 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
519 final String label = cooked[2];
520 final String path = cooked[3];
521 int major = -1;
522 int minor = -1;
523
524 try {
525 String devComp = cooked[6].substring(1, cooked[6].length() -1);
526 String[] devTok = devComp.split(":");
527 major = Integer.parseInt(devTok[0]);
528 minor = Integer.parseInt(devTok[1]);
529 } catch (Exception ex) {
530 Log.e(TAG, "Failed to parse major/minor", ex);
531 }
532
San Mehat4270e1e2010-01-29 05:32:19 -0800533 if (code == VoldResponseCode.VolumeDiskInserted) {
534 new Thread() {
535 public void run() {
536 try {
537 int rc;
San Mehatb1043402010-02-05 08:26:50 -0800538 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800539 Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
540 }
541 } catch (Exception ex) {
542 Log.w(TAG, "Failed to mount media on insertion", ex);
543 }
544 }
545 }.start();
546 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
547 /*
548 * This event gets trumped if we're already in BAD_REMOVAL state
549 */
550 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
551 return true;
552 }
553 /* Send the media unmounted event first */
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800554 if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
San Mehat4270e1e2010-01-29 05:32:19 -0800555 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
556 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
557 mContext.sendBroadcast(in);
558
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800559 if (DEBUG_EVENTS) Log.i(TAG, "Sending media removed");
San Mehat4270e1e2010-01-29 05:32:19 -0800560 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
561 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
562 } else if (code == VoldResponseCode.VolumeBadRemoval) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800563 if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
San Mehat4270e1e2010-01-29 05:32:19 -0800564 /* Send the media unmounted event first */
565 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
566 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
567 mContext.sendBroadcast(in);
568
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800569 if (DEBUG_EVENTS) Log.i(TAG, "Sending media bad removal");
San Mehat4270e1e2010-01-29 05:32:19 -0800570 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
571 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
572 } else {
573 Log.e(TAG, String.format("Unknown code {%d}", code));
574 }
575 } else {
576 return false;
577 }
578
579 if (in != null) {
580 mContext.sendBroadcast(in);
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400581 }
582 return true;
San Mehat4270e1e2010-01-29 05:32:19 -0800583 }
584
San Mehat207e5382010-02-04 20:46:54 -0800585 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
San Mehat4270e1e2010-01-29 05:32:19 -0800586 String vs = getVolumeState(path);
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800587 if (DEBUG_EVENTS) Log.i(TAG, "notifyVolumeStateChanged::" + vs);
San Mehat4270e1e2010-01-29 05:32:19 -0800588
589 Intent in = null;
590
Mike Lockwoodbf2dd442010-03-03 06:16:52 -0500591 if (oldState == VolumeState.Shared && newState != oldState) {
San Mehat2fe718a2010-03-11 12:01:49 -0800592 if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
Mike Lockwoodbf2dd442010-03-03 06:16:52 -0500593 mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED,
594 Uri.parse("file://" + path)));
595 }
596
San Mehat4270e1e2010-01-29 05:32:19 -0800597 if (newState == VolumeState.Init) {
598 } else if (newState == VolumeState.NoMedia) {
599 // NoMedia is handled via Disk Remove events
600 } else if (newState == VolumeState.Idle) {
601 /*
602 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
603 * if we're in the process of enabling UMS
604 */
605 if (!vs.equals(
606 Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
607 Environment.MEDIA_NOFS) && !vs.equals(
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800608 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800609 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state for media bad removal nofs and unmountable");
San Mehat4270e1e2010-01-29 05:32:19 -0800610 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
611 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
612 }
613 } else if (newState == VolumeState.Pending) {
614 } else if (newState == VolumeState.Checking) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800615 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state checking");
San Mehat4270e1e2010-01-29 05:32:19 -0800616 updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
617 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
618 } else if (newState == VolumeState.Mounted) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800619 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state mounted");
San Mehat4270e1e2010-01-29 05:32:19 -0800620 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
San Mehat4270e1e2010-01-29 05:32:19 -0800621 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
622 in.putExtra("read-only", false);
623 } else if (newState == VolumeState.Unmounting) {
San Mehat4270e1e2010-01-29 05:32:19 -0800624 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
625 } else if (newState == VolumeState.Formatting) {
626 } else if (newState == VolumeState.Shared) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800627 if (DEBUG_EVENTS) Log.i(TAG, "Updating volume state media mounted");
San Mehat4270e1e2010-01-29 05:32:19 -0800628 /* Send the media unmounted event first */
629 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
630 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
631 mContext.sendBroadcast(in);
632
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800633 if (DEBUG_EVENTS) Log.i(TAG, "Updating media shared");
San Mehat4270e1e2010-01-29 05:32:19 -0800634 updatePublicVolumeState(path, Environment.MEDIA_SHARED);
635 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
San Mehat2fe718a2010-03-11 12:01:49 -0800636 if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
San Mehat4270e1e2010-01-29 05:32:19 -0800637 } else if (newState == VolumeState.SharedMnt) {
638 Log.e(TAG, "Live shared mounts not supported yet!");
639 return;
640 } else {
641 Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
642 }
643
644 if (in != null) {
645 mContext.sendBroadcast(in);
646 }
647 }
648
San Mehat207e5382010-02-04 20:46:54 -0800649 private boolean doGetShareMethodAvailable(String method) {
650 ArrayList<String> rsp = mConnector.doCommand("share status " + method);
651
652 for (String line : rsp) {
653 String []tok = line.split(" ");
654 int code;
655 try {
656 code = Integer.parseInt(tok[0]);
657 } catch (NumberFormatException nfe) {
658 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
659 return false;
660 }
661 if (code == VoldResponseCode.ShareStatusResult) {
662 if (tok[2].equals("available"))
663 return true;
664 return false;
665 } else {
666 Log.e(TAG, String.format("Unexpected response code %d", code));
667 return false;
668 }
669 }
670 Log.e(TAG, "Got an empty response");
671 return false;
672 }
673
674 private int doMountVolume(String path) {
San Mehatb1043402010-02-05 08:26:50 -0800675 int rc = StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800676
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800677 if (DEBUG_EVENTS) Log.i(TAG, "doMountVolume: Mouting " + path);
San Mehat207e5382010-02-04 20:46:54 -0800678 try {
679 mConnector.doCommand(String.format("volume mount %s", path));
680 } catch (NativeDaemonConnectorException e) {
681 /*
682 * Mount failed for some reason
683 */
684 Intent in = null;
685 int code = e.getCode();
686 if (code == VoldResponseCode.OpFailedNoMedia) {
687 /*
688 * Attempt to mount but no media inserted
689 */
San Mehatb1043402010-02-05 08:26:50 -0800690 rc = StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800691 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800692 if (DEBUG_EVENTS) Log.i(TAG, " updating volume state :: media nofs");
San Mehat207e5382010-02-04 20:46:54 -0800693 /*
694 * Media is blank or does not contain a supported filesystem
695 */
696 updatePublicVolumeState(path, Environment.MEDIA_NOFS);
697 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800698 rc = StorageResultCode.OperationFailedMediaBlank;
San Mehat207e5382010-02-04 20:46:54 -0800699 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800700 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state media corrupt");
San Mehat207e5382010-02-04 20:46:54 -0800701 /*
702 * Volume consistency check failed
703 */
704 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
705 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800706 rc = StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800707 } else {
San Mehatb1043402010-02-05 08:26:50 -0800708 rc = StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800709 }
710
711 /*
712 * Send broadcast intent (if required for the failure)
713 */
714 if (in != null) {
715 mContext.sendBroadcast(in);
716 }
717 }
718
719 return rc;
720 }
721
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800722 /*
723 * If force is not set, we do not unmount if there are
724 * processes holding references to the volume about to be unmounted.
725 * If force is set, all the processes holding references need to be
726 * killed via the ActivityManager before actually unmounting the volume.
727 * This might even take a while and might be retried after timed delays
728 * to make sure we dont end up in an instable state and kill some core
729 * processes.
730 */
San Mehatd9709982010-02-18 11:43:03 -0800731 private int doUnmountVolume(String path, boolean force) {
San Mehat59443a62010-02-09 13:28:45 -0800732 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
San Mehat207e5382010-02-04 20:46:54 -0800733 return VoldResponseCode.OpFailedVolNotMounted;
734 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800735 // Redundant probably. But no harm in updating state again.
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700736 mPms.updateExternalMediaStatus(false, false);
San Mehat207e5382010-02-04 20:46:54 -0800737 try {
San Mehatd9709982010-02-18 11:43:03 -0800738 mConnector.doCommand(String.format(
739 "volume unmount %s%s", path, (force ? " force" : "")));
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -0700740 // We unmounted the volume. None of the asec containers are available now.
741 synchronized (mAsecMountSet) {
742 mAsecMountSet.clear();
743 }
San Mehatb1043402010-02-05 08:26:50 -0800744 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800745 } catch (NativeDaemonConnectorException e) {
746 // Don't worry about mismatch in PackageManager since the
747 // call back will handle the status changes any way.
748 int code = e.getCode();
749 if (code == VoldResponseCode.OpFailedVolNotMounted) {
San Mehata181b212010-02-11 06:50:20 -0800750 return StorageResultCode.OperationFailedStorageNotMounted;
San Mehatd9709982010-02-18 11:43:03 -0800751 } else if (code == VoldResponseCode.OpFailedStorageBusy) {
752 return StorageResultCode.OperationFailedStorageBusy;
San Mehat207e5382010-02-04 20:46:54 -0800753 } else {
San Mehatb1043402010-02-05 08:26:50 -0800754 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800755 }
756 }
757 }
758
759 private int doFormatVolume(String path) {
760 try {
761 String cmd = String.format("volume format %s", path);
762 mConnector.doCommand(cmd);
San Mehatb1043402010-02-05 08:26:50 -0800763 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800764 } catch (NativeDaemonConnectorException e) {
765 int code = e.getCode();
766 if (code == VoldResponseCode.OpFailedNoMedia) {
San Mehatb1043402010-02-05 08:26:50 -0800767 return StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800768 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
San Mehatb1043402010-02-05 08:26:50 -0800769 return StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800770 } else {
San Mehatb1043402010-02-05 08:26:50 -0800771 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800772 }
773 }
774 }
775
San Mehatb1043402010-02-05 08:26:50 -0800776 private boolean doGetVolumeShared(String path, String method) {
777 String cmd = String.format("volume shared %s %s", path, method);
778 ArrayList<String> rsp = mConnector.doCommand(cmd);
779
780 for (String line : rsp) {
781 String []tok = line.split(" ");
782 int code;
783 try {
784 code = Integer.parseInt(tok[0]);
785 } catch (NumberFormatException nfe) {
786 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
787 return false;
788 }
789 if (code == VoldResponseCode.ShareEnabledResult) {
790 if (tok[2].equals("enabled"))
791 return true;
792 return false;
793 } else {
794 Log.e(TAG, String.format("Unexpected response code %d", code));
795 return false;
796 }
797 }
798 Log.e(TAG, "Got an empty response");
799 return false;
800 }
801
San Mehat207e5382010-02-04 20:46:54 -0800802 private void notifyShareAvailabilityChange(String method, final boolean avail) {
San Mehat4270e1e2010-01-29 05:32:19 -0800803 if (!method.equals("ums")) {
804 Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
805 return;
806 }
807
808 synchronized (mListeners) {
809 for (int i = mListeners.size() -1; i >= 0; i--) {
810 MountServiceBinderListener bl = mListeners.get(i);
811 try {
San Mehatb1043402010-02-05 08:26:50 -0800812 bl.mListener.onUsbMassStorageConnectionChanged(avail);
San Mehat4270e1e2010-01-29 05:32:19 -0800813 } catch (RemoteException rex) {
814 Log.e(TAG, "Listener dead");
815 mListeners.remove(i);
816 } catch (Exception ex) {
817 Log.e(TAG, "Listener failed", ex);
818 }
819 }
820 }
821
San Mehat207e5382010-02-04 20:46:54 -0800822 if (mBooted == true) {
San Mehat6a965af22010-02-24 17:47:30 -0800823 sendUmsIntent(avail);
824 } else {
825 mSendUmsConnectedOnBoot = avail;
San Mehat4270e1e2010-01-29 05:32:19 -0800826 }
San Mehat2fe718a2010-03-11 12:01:49 -0800827
828 final String path = Environment.getExternalStorageDirectory().getPath();
829 if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
830 /*
831 * USB mass storage disconnected while enabled
832 */
833 new Thread() {
834 public void run() {
835 try {
836 int rc;
837 Log.w(TAG, "Disabling UMS after cable disconnect");
838 doShareUnshareVolume(path, "ums", false);
839 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
840 Log.e(TAG, String.format(
841 "Failed to remount {%s} on UMS enabled-disconnect (%d)",
842 path, rc));
843 }
844 } catch (Exception ex) {
845 Log.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
846 }
847 }
848 }.start();
849 }
San Mehat4270e1e2010-01-29 05:32:19 -0800850 }
851
San Mehat6a965af22010-02-24 17:47:30 -0800852 private void sendUmsIntent(boolean c) {
853 mContext.sendBroadcast(
854 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
855 }
856
San Mehat207e5382010-02-04 20:46:54 -0800857 private void validatePermission(String perm) {
San Mehat4270e1e2010-01-29 05:32:19 -0800858 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
859 throw new SecurityException(String.format("Requires %s permission", perm));
860 }
861 }
862
863 /**
San Mehat207e5382010-02-04 20:46:54 -0800864 * Constructs a new MountService instance
865 *
866 * @param context Binder context for this service
867 */
868 public MountService(Context context) {
869 mContext = context;
870
San Mehat207e5382010-02-04 20:46:54 -0800871 // XXX: This will go away soon in favor of IMountServiceObserver
872 mPms = (PackageManagerService) ServiceManager.getService("package");
873
874 mContext.registerReceiver(mBroadcastReceiver,
875 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
876
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400877 mHandlerThread = new HandlerThread("MountService");
878 mHandlerThread.start();
879 mHandler = new MountServiceHandler(mHandlerThread.getLooper());
880
Marco Nelissenc34ebce2010-02-18 13:39:41 -0800881 /*
882 * Vold does not run in the simulator, so pretend the connector thread
883 * ran and did its thing.
884 */
885 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
886 mReady = true;
887 mUmsEnabling = true;
888 return;
889 }
890
San Mehat207e5382010-02-04 20:46:54 -0800891 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
892 mReady = false;
893 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
894 thread.start();
895 }
896
897 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800898 * Exposed API calls below here
899 */
900
901 public void registerListener(IMountServiceListener listener) {
902 synchronized (mListeners) {
903 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
904 try {
905 listener.asBinder().linkToDeath(bl, 0);
906 mListeners.add(bl);
907 } catch (RemoteException rex) {
908 Log.e(TAG, "Failed to link to listener death");
909 }
910 }
911 }
912
913 public void unregisterListener(IMountServiceListener listener) {
914 synchronized (mListeners) {
915 for(MountServiceBinderListener bl : mListeners) {
916 if (bl.mListener == listener) {
917 mListeners.remove(mListeners.indexOf(bl));
918 return;
919 }
920 }
921 }
922 }
923
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800924 public void shutdown(final IMountShutdownObserver observer) {
San Mehat4270e1e2010-01-29 05:32:19 -0800925 validatePermission(android.Manifest.permission.SHUTDOWN);
926
927 Log.i(TAG, "Shutting down");
928
929 String path = Environment.getExternalStorageDirectory().getPath();
930 String state = getVolumeState(path);
San Mehat91c77612010-01-07 10:39:41 -0800931
932 if (state.equals(Environment.MEDIA_SHARED)) {
933 /*
934 * If the media is currently shared, unshare it.
935 * XXX: This is still dangerous!. We should not
936 * be rebooting at *all* if UMS is enabled, since
937 * the UMS host could have dirty FAT cache entries
938 * yet to flush.
939 */
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800940 setUsbMassStorageEnabled(false);
San Mehat91c77612010-01-07 10:39:41 -0800941 } else if (state.equals(Environment.MEDIA_CHECKING)) {
942 /*
943 * If the media is being checked, then we need to wait for
944 * it to complete before being able to proceed.
945 */
946 // XXX: @hackbod - Should we disable the ANR timer here?
947 int retries = 30;
948 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
949 try {
950 Thread.sleep(1000);
951 } catch (InterruptedException iex) {
952 Log.e(TAG, "Interrupted while waiting for media", iex);
953 break;
954 }
955 state = Environment.getExternalStorageState();
956 }
957 if (retries == 0) {
958 Log.e(TAG, "Timed out waiting for media to check");
959 }
960 }
961
962 if (state.equals(Environment.MEDIA_MOUNTED)) {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800963 // Post a unmount message.
964 ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
965 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
San Mehat4270e1e2010-01-29 05:32:19 -0800966 }
967 }
968
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800969 private boolean getUmsEnabling() {
970 synchronized (mListeners) {
971 return mUmsEnabling;
972 }
973 }
974
975 private void setUmsEnabling(boolean enable) {
976 synchronized (mListeners) {
977 mUmsEnabling = true;
978 }
979 }
980
San Mehatb1043402010-02-05 08:26:50 -0800981 public boolean isUsbMassStorageConnected() {
San Mehat207e5382010-02-04 20:46:54 -0800982 waitForReady();
San Mehat91c77612010-01-07 10:39:41 -0800983
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800984 if (getUmsEnabling()) {
San Mehatb1043402010-02-05 08:26:50 -0800985 return true;
San Mehat7fd0fee2009-12-17 07:12:23 -0800986 }
San Mehatb1043402010-02-05 08:26:50 -0800987 return doGetShareMethodAvailable("ums");
988 }
989
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800990 public void setUsbMassStorageEnabled(boolean enable) {
San Mehatb1043402010-02-05 08:26:50 -0800991 waitForReady();
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800992 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehatb1043402010-02-05 08:26:50 -0800993
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800994 // TODO: Add support for multiple share methods
995
996 /*
997 * If the volume is mounted and we're enabling then unmount it
998 */
999 String path = Environment.getExternalStorageDirectory().getPath();
1000 String vs = getVolumeState(path);
1001 String method = "ums";
1002 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1003 // Override for isUsbMassStorageEnabled()
1004 setUmsEnabling(enable);
1005 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1006 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1007 // Clear override
1008 setUmsEnabling(false);
1009 }
1010 /*
1011 * If we disabled UMS then mount the volume
1012 */
1013 if (!enable) {
1014 doShareUnshareVolume(path, method, enable);
1015 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1016 Log.e(TAG, "Failed to remount " + path +
1017 " after disabling share method " + method);
1018 /*
1019 * Even though the mount failed, the unshare didn't so don't indicate an error.
1020 * The mountVolume() call will have set the storage state and sent the necessary
1021 * broadcasts.
1022 */
1023 }
1024 }
San Mehatb1043402010-02-05 08:26:50 -08001025 }
1026
1027 public boolean isUsbMassStorageEnabled() {
1028 waitForReady();
1029 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 }
San Mehat4270e1e2010-01-29 05:32:19 -08001031
San Mehat7fd0fee2009-12-17 07:12:23 -08001032 /**
1033 * @return state of the volume at the specified mount point
1034 */
San Mehat4270e1e2010-01-29 05:32:19 -08001035 public String getVolumeState(String mountPoint) {
San Mehat7fd0fee2009-12-17 07:12:23 -08001036 /*
1037 * XXX: Until we have multiple volume discovery, just hardwire
1038 * this to /sdcard
1039 */
1040 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
1041 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1042 throw new IllegalArgumentException();
1043 }
1044
1045 return mLegacyState;
1046 }
1047
San Mehat4270e1e2010-01-29 05:32:19 -08001048 public int mountVolume(String path) {
1049 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat4270e1e2010-01-29 05:32:19 -08001050
San Mehat207e5382010-02-04 20:46:54 -08001051 waitForReady();
1052 return doMountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053 }
1054
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -08001055 public void unmountVolume(String path, boolean force) {
San Mehat4270e1e2010-01-29 05:32:19 -08001056 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -08001057 waitForReady();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -08001059 String volState = getVolumeState(path);
1060 if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path + " force = " + force);
1061 if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1062 Environment.MEDIA_REMOVED.equals(volState) ||
1063 Environment.MEDIA_SHARED.equals(volState) ||
1064 Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1065 // Media already unmounted or cannot be unmounted.
1066 // TODO return valid return code when adding observer call back.
1067 return;
1068 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -08001069 UnmountCallBack ucb = new UnmountCallBack(path, force);
1070 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071 }
1072
San Mehat4270e1e2010-01-29 05:32:19 -08001073 public int formatVolume(String path) {
1074 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -08001075 waitForReady();
San Mehat5b77dab2010-01-26 13:28:50 -08001076
San Mehat207e5382010-02-04 20:46:54 -08001077 return doFormatVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 }
1079
San Mehatc1b4ce92010-02-16 17:13:03 -08001080 public int []getStorageUsers(String path) {
1081 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1082 waitForReady();
1083 try {
1084 String[] r = mConnector.doListCommand(
1085 String.format("storage users %s", path),
1086 VoldResponseCode.StorageUsersListResult);
1087 // FMT: <pid> <process name>
1088 int[] data = new int[r.length];
1089 for (int i = 0; i < r.length; i++) {
1090 String []tok = r[i].split(" ");
1091 try {
1092 data[i] = Integer.parseInt(tok[0]);
1093 } catch (NumberFormatException nfe) {
1094 Log.e(TAG, String.format("Error parsing pid %s", tok[0]));
1095 return new int[0];
1096 }
1097 }
1098 return data;
1099 } catch (NativeDaemonConnectorException e) {
1100 Log.e(TAG, "Failed to retrieve storage users list", e);
1101 return new int[0];
1102 }
1103 }
1104
San Mehatb1043402010-02-05 08:26:50 -08001105 private void warnOnNotMounted() {
1106 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1107 Log.w(TAG, "getSecureContainerList() called when storage not mounted");
1108 }
1109 }
1110
San Mehat4270e1e2010-01-29 05:32:19 -08001111 public String[] getSecureContainerList() {
1112 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -08001113 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001114 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -08001115
San Mehat4270e1e2010-01-29 05:32:19 -08001116 try {
1117 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
1118 } catch (NativeDaemonConnectorException e) {
1119 return new String[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 }
1121 }
San Mehat36972292010-01-06 11:06:32 -08001122
San Mehat4270e1e2010-01-29 05:32:19 -08001123 public int createSecureContainer(String id, int sizeMb, String fstype,
1124 String key, int ownerUid) {
1125 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehat207e5382010-02-04 20:46:54 -08001126 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001127 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001128
San Mehatb1043402010-02-05 08:26:50 -08001129 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001130 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
1131 try {
1132 mConnector.doCommand(cmd);
1133 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001134 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001135 }
San Mehata181b212010-02-11 06:50:20 -08001136
1137 if (rc == StorageResultCode.OperationSucceeded) {
1138 synchronized (mAsecMountSet) {
1139 mAsecMountSet.add(id);
1140 }
1141 }
San Mehat4270e1e2010-01-29 05:32:19 -08001142 return rc;
San Mehat36972292010-01-06 11:06:32 -08001143 }
1144
San Mehat4270e1e2010-01-29 05:32:19 -08001145 public int finalizeSecureContainer(String id) {
1146 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehatb1043402010-02-05 08:26:50 -08001147 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001148
San Mehatb1043402010-02-05 08:26:50 -08001149 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001150 try {
1151 mConnector.doCommand(String.format("asec finalize %s", id));
San Mehata181b212010-02-11 06:50:20 -08001152 /*
1153 * Finalization does a remount, so no need
1154 * to update mAsecMountSet
1155 */
San Mehat4270e1e2010-01-29 05:32:19 -08001156 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001157 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001158 }
San Mehat4270e1e2010-01-29 05:32:19 -08001159 return rc;
San Mehat36972292010-01-06 11:06:32 -08001160 }
1161
San Mehatd9709982010-02-18 11:43:03 -08001162 public int destroySecureContainer(String id, boolean force) {
San Mehat4270e1e2010-01-29 05:32:19 -08001163 validatePermission(android.Manifest.permission.ASEC_DESTROY);
San Mehat207e5382010-02-04 20:46:54 -08001164 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001165 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -08001166
San Mehatb1043402010-02-05 08:26:50 -08001167 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001168 try {
San Mehatd9709982010-02-18 11:43:03 -08001169 mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
San Mehat4270e1e2010-01-29 05:32:19 -08001170 } catch (NativeDaemonConnectorException e) {
San Mehatd9709982010-02-18 11:43:03 -08001171 int code = e.getCode();
1172 if (code == VoldResponseCode.OpFailedStorageBusy) {
1173 rc = StorageResultCode.OperationFailedStorageBusy;
1174 } else {
1175 rc = StorageResultCode.OperationFailedInternalError;
1176 }
San Mehat02735bc2010-01-26 15:18:08 -08001177 }
San Mehata181b212010-02-11 06:50:20 -08001178
1179 if (rc == StorageResultCode.OperationSucceeded) {
1180 synchronized (mAsecMountSet) {
1181 if (mAsecMountSet.contains(id)) {
1182 mAsecMountSet.remove(id);
1183 }
1184 }
1185 }
1186
San Mehat4270e1e2010-01-29 05:32:19 -08001187 return rc;
San Mehat36972292010-01-06 11:06:32 -08001188 }
1189
San Mehat4270e1e2010-01-29 05:32:19 -08001190 public int mountSecureContainer(String id, String key, int ownerUid) {
1191 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -08001192 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001193 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001194
San Mehata181b212010-02-11 06:50:20 -08001195 synchronized (mAsecMountSet) {
1196 if (mAsecMountSet.contains(id)) {
1197 return StorageResultCode.OperationFailedStorageMounted;
1198 }
1199 }
1200
San Mehatb1043402010-02-05 08:26:50 -08001201 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001202 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
1203 try {
1204 mConnector.doCommand(cmd);
1205 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001206 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001207 }
San Mehat6cdd9c02010-02-09 14:45:20 -08001208
1209 if (rc == StorageResultCode.OperationSucceeded) {
1210 synchronized (mAsecMountSet) {
1211 mAsecMountSet.add(id);
1212 }
1213 }
San Mehat4270e1e2010-01-29 05:32:19 -08001214 return rc;
San Mehat36972292010-01-06 11:06:32 -08001215 }
1216
San Mehatd9709982010-02-18 11:43:03 -08001217 public int unmountSecureContainer(String id, boolean force) {
San Mehat4270e1e2010-01-29 05:32:19 -08001218 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -08001219 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001220 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001221
San Mehat6cdd9c02010-02-09 14:45:20 -08001222 synchronized (mAsecMountSet) {
1223 if (!mAsecMountSet.contains(id)) {
San Mehata181b212010-02-11 06:50:20 -08001224 return StorageResultCode.OperationFailedStorageNotMounted;
San Mehat6cdd9c02010-02-09 14:45:20 -08001225 }
1226 }
1227
San Mehatb1043402010-02-05 08:26:50 -08001228 int rc = StorageResultCode.OperationSucceeded;
San Mehatd9709982010-02-18 11:43:03 -08001229 String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
San Mehat4270e1e2010-01-29 05:32:19 -08001230 try {
1231 mConnector.doCommand(cmd);
1232 } catch (NativeDaemonConnectorException e) {
San Mehatd9709982010-02-18 11:43:03 -08001233 int code = e.getCode();
1234 if (code == VoldResponseCode.OpFailedStorageBusy) {
1235 rc = StorageResultCode.OperationFailedStorageBusy;
1236 } else {
1237 rc = StorageResultCode.OperationFailedInternalError;
1238 }
San Mehat02735bc2010-01-26 15:18:08 -08001239 }
San Mehat6cdd9c02010-02-09 14:45:20 -08001240
1241 if (rc == StorageResultCode.OperationSucceeded) {
1242 synchronized (mAsecMountSet) {
1243 mAsecMountSet.remove(id);
1244 }
1245 }
San Mehat4270e1e2010-01-29 05:32:19 -08001246 return rc;
San Mehat9dba7092010-01-18 06:47:41 -08001247 }
1248
San Mehat6cdd9c02010-02-09 14:45:20 -08001249 public boolean isSecureContainerMounted(String id) {
1250 validatePermission(android.Manifest.permission.ASEC_ACCESS);
1251 waitForReady();
1252 warnOnNotMounted();
1253
1254 synchronized (mAsecMountSet) {
1255 return mAsecMountSet.contains(id);
1256 }
1257 }
1258
San Mehat4270e1e2010-01-29 05:32:19 -08001259 public int renameSecureContainer(String oldId, String newId) {
1260 validatePermission(android.Manifest.permission.ASEC_RENAME);
San Mehat207e5382010-02-04 20:46:54 -08001261 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001262 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001263
San Mehata181b212010-02-11 06:50:20 -08001264 synchronized (mAsecMountSet) {
San Mehat85451ee2010-02-24 08:54:18 -08001265 /*
1266 * Because a mounted container has active internal state which cannot be
1267 * changed while active, we must ensure both ids are not currently mounted.
1268 */
1269 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
San Mehata181b212010-02-11 06:50:20 -08001270 return StorageResultCode.OperationFailedStorageMounted;
1271 }
1272 }
1273
San Mehatb1043402010-02-05 08:26:50 -08001274 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001275 String cmd = String.format("asec rename %s %s", oldId, newId);
1276 try {
1277 mConnector.doCommand(cmd);
1278 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001279 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001280 }
San Mehata181b212010-02-11 06:50:20 -08001281
San Mehat4270e1e2010-01-29 05:32:19 -08001282 return rc;
San Mehat45f61042010-01-23 08:12:43 -08001283 }
1284
San Mehat4270e1e2010-01-29 05:32:19 -08001285 public String getSecureContainerPath(String id) {
1286 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -08001287 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001288 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -08001289
San Mehat4270e1e2010-01-29 05:32:19 -08001290 ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
San Mehat36972292010-01-06 11:06:32 -08001291
San Mehat22dd86e2010-01-12 12:21:18 -08001292 for (String line : rsp) {
1293 String []tok = line.split(" ");
1294 int code = Integer.parseInt(tok[0]);
1295 if (code == VoldResponseCode.AsecPathResult) {
1296 return tok[1];
1297 } else {
San Mehat4270e1e2010-01-29 05:32:19 -08001298 Log.e(TAG, String.format("Unexpected response code %d", code));
1299 return "";
San Mehat22dd86e2010-01-12 12:21:18 -08001300 }
1301 }
San Mehat4270e1e2010-01-29 05:32:19 -08001302
1303 Log.e(TAG, "Got an empty response");
1304 return "";
San Mehat22dd86e2010-01-12 12:21:18 -08001305 }
Suchi Amalapurapue99bb5f2010-03-19 14:36:49 -07001306
1307 public void finishMediaUpdate() {
1308 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1309 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001310}
1311