blob: 5bed54948860a18459be2c2ff596dbaef5f3f57c [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
138 private IntentFilter mPmFilter = new IntentFilter(
139 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
140 private BroadcastReceiver mPmReceiver = new BroadcastReceiver() {
141 public void onReceive(Context context, Intent intent) {
142 String action = intent.getAction();
143 if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
144 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
145 }
146 }
147 };
148
149 class UnmountCallBack {
150 String path;
151 int retries;
152 boolean force;
153
154 UnmountCallBack(String path, boolean force) {
155 retries = 0;
156 this.path = path;
157 this.force = force;
158 }
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800159
160 void handleFinished() {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800161 if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path);
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800162 doUnmountVolume(path, true);
163 }
164 }
165
166 class UmsEnableCallBack extends UnmountCallBack {
167 String method;
168
169 UmsEnableCallBack(String path, String method, boolean force) {
170 super(path, force);
171 this.method = method;
172 }
173
174 @Override
175 void handleFinished() {
176 super.handleFinished();
177 doShareUnshareVolume(path, method, true);
178 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800179 }
180
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800181 class ShutdownCallBack extends UnmountCallBack {
182 IMountShutdownObserver observer;
183 ShutdownCallBack(String path, IMountShutdownObserver observer) {
184 super(path, true);
185 this.observer = observer;
186 }
187
188 @Override
189 void handleFinished() {
190 int ret = doUnmountVolume(path, true);
191 if (observer != null) {
192 try {
193 observer.onShutDownComplete(ret);
194 } catch (RemoteException e) {
195 Log.w(TAG, "RemoteException when shutting down");
196 }
197 }
198 }
199 }
200
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400201 class MountServiceHandler extends Handler {
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800202 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800203 boolean mRegistered = false;
204
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400205 MountServiceHandler(Looper l) {
206 super(l);
207 }
208
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800209 void registerReceiver() {
210 mRegistered = true;
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800211 if (DEBUG_UNMOUNT) Log.i(TAG, "Registering receiver");
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800212 mContext.registerReceiver(mPmReceiver, mPmFilter);
213 }
214
215 void unregisterReceiver() {
216 mRegistered = false;
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800217 if (DEBUG_UNMOUNT) Log.i(TAG, "Unregistering receiver");
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800218 mContext.unregisterReceiver(mPmReceiver);
219 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800220
221 public void handleMessage(Message msg) {
222 switch (msg.what) {
223 case H_UNMOUNT_PM_UPDATE: {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800224 if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_UPDATE");
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800225 UnmountCallBack ucb = (UnmountCallBack) msg.obj;
226 mForceUnmounts.add(ucb);
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800227 if (DEBUG_UNMOUNT) Log.i(TAG, " registered = " + mRegistered);
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800228 // Register only if needed.
229 if (!mRegistered) {
230 registerReceiver();
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800231 if (DEBUG_UNMOUNT) Log.i(TAG, "Updating external media status");
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800232 boolean hasExtPkgs = mPms.updateExternalMediaStatus(false);
233 if (!hasExtPkgs) {
234 // Unregister right away
235 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
236 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800237 }
238 break;
239 }
240 case H_UNMOUNT_PM_DONE: {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800241 if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_DONE");
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800242 // Unregister now.
243 if (mRegistered) {
244 unregisterReceiver();
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800245 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800246 int size = mForceUnmounts.size();
247 int sizeArr[] = new int[size];
248 int sizeArrN = 0;
249 for (int i = 0; i < size; i++) {
250 UnmountCallBack ucb = mForceUnmounts.get(i);
251 String path = ucb.path;
252 boolean done = false;
253 if (!ucb.force) {
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800254 done = true;
255 } else {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800256 int pids[] = getStorageUsers(path);
257 if (pids == null || pids.length == 0) {
258 done = true;
259 } else {
260 // Kill processes holding references first
261 ActivityManagerService ams = (ActivityManagerService)
262 ServiceManager.getService("activity");
263 // Eliminate system process here?
264 boolean ret = ams.killPidsForMemory(pids);
265 if (ret) {
266 // Confirm if file references have been freed.
267 pids = getStorageUsers(path);
268 if (pids == null || pids.length == 0) {
269 done = true;
270 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800271 }
272 }
273 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800274 if (done) {
275 sizeArr[sizeArrN++] = i;
276 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
277 ucb));
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800278 } else {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800279 if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
280 Log.i(TAG, "Cannot unmount inspite of " +
281 MAX_UNMOUNT_RETRIES + " to unmount media");
282 // Send final broadcast indicating failure to unmount.
283 } else {
284 mHandler.sendMessageDelayed(
285 mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
286 ucb.retries++),
287 RETRY_UNMOUNT_DELAY);
288 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800289 }
290 }
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800291 // Remove already processed elements from list.
292 for (int i = (sizeArrN-1); i >= 0; i--) {
293 mForceUnmounts.remove(sizeArr[i]);
294 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800295 break;
296 }
297 case H_UNMOUNT_MS : {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800298 if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_MS");
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800299 UnmountCallBack ucb = (UnmountCallBack) msg.obj;
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800300 ucb.handleFinished();
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800301 break;
302 }
303 }
304 }
305 };
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400306 final private HandlerThread mHandlerThread;
307 final private Handler mHandler;
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800308
San Mehat207e5382010-02-04 20:46:54 -0800309 private void waitForReady() {
310 while (mReady == false) {
311 for (int retries = 5; retries > 0; retries--) {
312 if (mReady) {
313 return;
314 }
315 SystemClock.sleep(1000);
316 }
317 Log.w(TAG, "Waiting too long for mReady!");
318 }
San Mehat1f6301e2010-01-07 22:40:27 -0800319 }
320
San Mehat207e5382010-02-04 20:46:54 -0800321 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 public void onReceive(Context context, Intent intent) {
San Mehat91c77612010-01-07 10:39:41 -0800323 String action = intent.getAction();
324
325 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
San Mehat207e5382010-02-04 20:46:54 -0800326 mBooted = true;
San Mehat22dd86e2010-01-12 12:21:18 -0800327
Marco Nelissenc34ebce2010-02-18 13:39:41 -0800328 /*
329 * In the simulator, we need to broadcast a volume mounted event
330 * to make the media scanner run.
331 */
332 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
333 notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
334 return;
335 }
San Mehatfafb0412010-02-18 19:40:04 -0800336 new Thread() {
337 public void run() {
338 try {
339 String path = Environment.getExternalStorageDirectory().getPath();
San Mehat6a254402010-03-22 10:21:00 -0700340 String state = getVolumeState(path);
341
342 if (state.equals(Environment.MEDIA_UNMOUNTED)) {
San Mehatfafb0412010-02-18 19:40:04 -0800343 int rc = doMountVolume(path);
344 if (rc != StorageResultCode.OperationSucceeded) {
345 Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
346 }
San Mehat6a254402010-03-22 10:21:00 -0700347 } else if (state.equals(Environment.MEDIA_SHARED)) {
348 /*
349 * Bootstrap UMS enabled state since vold indicates
350 * the volume is shared (runtime restart while ums enabled)
351 */
352 notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
San Mehatfafb0412010-02-18 19:40:04 -0800353 }
San Mehat6a254402010-03-22 10:21:00 -0700354
San Mehat6a965af22010-02-24 17:47:30 -0800355 /*
San Mehat6a254402010-03-22 10:21:00 -0700356 * If UMS was connected on boot, send the connected event
San Mehat6a965af22010-02-24 17:47:30 -0800357 * now that we're up.
358 */
359 if (mSendUmsConnectedOnBoot) {
360 sendUmsIntent(true);
361 mSendUmsConnectedOnBoot = false;
362 }
San Mehatfafb0412010-02-18 19:40:04 -0800363 } catch (Exception ex) {
364 Log.e(TAG, "Boot-time mount exception", ex);
365 }
San Mehat207e5382010-02-04 20:46:54 -0800366 }
San Mehatfafb0412010-02-18 19:40:04 -0800367 }.start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 }
369 }
370 };
371
San Mehat4270e1e2010-01-29 05:32:19 -0800372 private final class MountServiceBinderListener implements IBinder.DeathRecipient {
373 final IMountServiceListener mListener;
374
375 MountServiceBinderListener(IMountServiceListener listener) {
376 mListener = listener;
377
San Mehat91c77612010-01-07 10:39:41 -0800378 }
379
San Mehat4270e1e2010-01-29 05:32:19 -0800380 public void binderDied() {
San Mehatb1043402010-02-05 08:26:50 -0800381 if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
San Mehat4270e1e2010-01-29 05:32:19 -0800382 synchronized(mListeners) {
383 mListeners.remove(this);
384 mListener.asBinder().unlinkToDeath(this, 0);
385 }
386 }
387 }
388
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800389 private void doShareUnshareVolume(String path, String method, boolean enable) {
San Mehat4270e1e2010-01-29 05:32:19 -0800390 // TODO: Add support for multiple share methods
391 if (!method.equals("ums")) {
392 throw new IllegalArgumentException(String.format("Method %s not supported", method));
393 }
394
San Mehat4270e1e2010-01-29 05:32:19 -0800395 try {
396 mConnector.doCommand(String.format(
397 "volume %sshare %s %s", (enable ? "" : "un"), path, method));
398 } catch (NativeDaemonConnectorException e) {
399 Log.e(TAG, "Failed to share/unshare", e);
San Mehat4270e1e2010-01-29 05:32:19 -0800400 }
San Mehat4270e1e2010-01-29 05:32:19 -0800401 }
402
San Mehat207e5382010-02-04 20:46:54 -0800403 private void updatePublicVolumeState(String path, String state) {
San Mehat4270e1e2010-01-29 05:32:19 -0800404 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
405 Log.w(TAG, "Multiple volumes not currently supported");
406 return;
407 }
San Mehatb1043402010-02-05 08:26:50 -0800408
409 if (mLegacyState.equals(state)) {
410 Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
411 return;
412 }
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800413 // Update state on PackageManager
414 if (Environment.MEDIA_UNMOUNTED.equals(state)) {
415 mPms.updateExternalMediaStatus(false);
416 } else if (Environment.MEDIA_MOUNTED.equals(state)) {
417 mPms.updateExternalMediaStatus(true);
418 }
San Mehat4270e1e2010-01-29 05:32:19 -0800419 String oldState = mLegacyState;
420 mLegacyState = state;
421
422 synchronized (mListeners) {
423 for (int i = mListeners.size() -1; i >= 0; i--) {
424 MountServiceBinderListener bl = mListeners.get(i);
425 try {
San Mehatb1043402010-02-05 08:26:50 -0800426 bl.mListener.onStorageStateChanged(path, oldState, state);
San Mehat4270e1e2010-01-29 05:32:19 -0800427 } catch (RemoteException rex) {
428 Log.e(TAG, "Listener dead");
429 mListeners.remove(i);
430 } catch (Exception ex) {
431 Log.e(TAG, "Listener failed", ex);
432 }
433 }
434 }
435 }
436
437 /**
438 *
439 * Callback from NativeDaemonConnector
440 */
441 public void onDaemonConnected() {
442 /*
443 * Since we'll be calling back into the NativeDaemonConnector,
444 * we need to do our work in a new thread.
445 */
446 new Thread() {
447 public void run() {
448 /**
449 * Determine media state and UMS detection status
450 */
451 String path = Environment.getExternalStorageDirectory().getPath();
452 String state = Environment.MEDIA_REMOVED;
453
454 try {
455 String[] vols = mConnector.doListCommand(
456 "volume list", VoldResponseCode.VolumeListResult);
457 for (String volstr : vols) {
458 String[] tok = volstr.split(" ");
459 // FMT: <label> <mountpoint> <state>
460 if (!tok[1].equals(path)) {
461 Log.w(TAG, String.format(
462 "Skipping unknown volume '%s'",tok[1]));
463 continue;
464 }
465 int st = Integer.parseInt(tok[2]);
466 if (st == VolumeState.NoMedia) {
467 state = Environment.MEDIA_REMOVED;
468 } else if (st == VolumeState.Idle) {
San Mehat207e5382010-02-04 20:46:54 -0800469 state = Environment.MEDIA_UNMOUNTED;
San Mehat4270e1e2010-01-29 05:32:19 -0800470 } else if (st == VolumeState.Mounted) {
471 state = Environment.MEDIA_MOUNTED;
472 Log.i(TAG, "Media already mounted on daemon connection");
473 } else if (st == VolumeState.Shared) {
474 state = Environment.MEDIA_SHARED;
475 Log.i(TAG, "Media shared on daemon connection");
476 } else {
477 throw new Exception(String.format("Unexpected state %d", st));
478 }
479 }
480 if (state != null) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800481 if (DEBUG_EVENTS) Log.i(TAG, "Updating valid state " + state);
San Mehat4270e1e2010-01-29 05:32:19 -0800482 updatePublicVolumeState(path, state);
483 }
484 } catch (Exception e) {
485 Log.e(TAG, "Error processing initial volume state", e);
486 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
487 }
488
489 try {
San Mehat207e5382010-02-04 20:46:54 -0800490 boolean avail = doGetShareMethodAvailable("ums");
San Mehat4270e1e2010-01-29 05:32:19 -0800491 notifyShareAvailabilityChange("ums", avail);
492 } catch (Exception ex) {
493 Log.w(TAG, "Failed to get share availability");
494 }
San Mehat207e5382010-02-04 20:46:54 -0800495 /*
496 * Now that we've done our initialization, release
497 * the hounds!
498 */
499 mReady = true;
San Mehat4270e1e2010-01-29 05:32:19 -0800500 }
501 }.start();
502 }
503
504 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800505 * Callback from NativeDaemonConnector
506 */
507 public boolean onEvent(int code, String raw, String[] cooked) {
508 Intent in = null;
509
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800510 if (DEBUG_EVENTS) {
511 StringBuilder builder = new StringBuilder();
512 builder.append("onEvent::");
513 builder.append(" raw= " + raw);
514 if (cooked != null) {
515 builder.append(" cooked = " );
516 for (String str : cooked) {
517 builder.append(" " + str);
518 }
519 }
520 Log.i(TAG, builder.toString());
521 }
San Mehat4270e1e2010-01-29 05:32:19 -0800522 if (code == VoldResponseCode.VolumeStateChange) {
523 /*
524 * One of the volumes we're managing has changed state.
525 * Format: "NNN Volume <label> <path> state changed
526 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
527 */
528 notifyVolumeStateChange(
529 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
530 Integer.parseInt(cooked[10]));
531 } else if (code == VoldResponseCode.ShareAvailabilityChange) {
532 // FMT: NNN Share method <method> now <available|unavailable>
533 boolean avail = false;
534 if (cooked[5].equals("available")) {
535 avail = true;
536 }
537 notifyShareAvailabilityChange(cooked[3], avail);
538 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
539 (code == VoldResponseCode.VolumeDiskRemoved) ||
540 (code == VoldResponseCode.VolumeBadRemoval)) {
541 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
542 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
543 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
544 final String label = cooked[2];
545 final String path = cooked[3];
546 int major = -1;
547 int minor = -1;
548
549 try {
550 String devComp = cooked[6].substring(1, cooked[6].length() -1);
551 String[] devTok = devComp.split(":");
552 major = Integer.parseInt(devTok[0]);
553 minor = Integer.parseInt(devTok[1]);
554 } catch (Exception ex) {
555 Log.e(TAG, "Failed to parse major/minor", ex);
556 }
557
San Mehat4270e1e2010-01-29 05:32:19 -0800558 if (code == VoldResponseCode.VolumeDiskInserted) {
559 new Thread() {
560 public void run() {
561 try {
562 int rc;
San Mehatb1043402010-02-05 08:26:50 -0800563 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800564 Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
565 }
566 } catch (Exception ex) {
567 Log.w(TAG, "Failed to mount media on insertion", ex);
568 }
569 }
570 }.start();
571 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
572 /*
573 * This event gets trumped if we're already in BAD_REMOVAL state
574 */
575 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
576 return true;
577 }
578 /* Send the media unmounted event first */
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800579 if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
San Mehat4270e1e2010-01-29 05:32:19 -0800580 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
581 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
582 mContext.sendBroadcast(in);
583
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800584 if (DEBUG_EVENTS) Log.i(TAG, "Sending media removed");
San Mehat4270e1e2010-01-29 05:32:19 -0800585 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
586 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
587 } else if (code == VoldResponseCode.VolumeBadRemoval) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800588 if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
San Mehat4270e1e2010-01-29 05:32:19 -0800589 /* Send the media unmounted event first */
590 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
591 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
592 mContext.sendBroadcast(in);
593
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800594 if (DEBUG_EVENTS) Log.i(TAG, "Sending media bad removal");
San Mehat4270e1e2010-01-29 05:32:19 -0800595 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
596 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
597 } else {
598 Log.e(TAG, String.format("Unknown code {%d}", code));
599 }
600 } else {
601 return false;
602 }
603
604 if (in != null) {
605 mContext.sendBroadcast(in);
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400606 }
607 return true;
San Mehat4270e1e2010-01-29 05:32:19 -0800608 }
609
San Mehat207e5382010-02-04 20:46:54 -0800610 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
San Mehat4270e1e2010-01-29 05:32:19 -0800611 String vs = getVolumeState(path);
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800612 if (DEBUG_EVENTS) Log.i(TAG, "notifyVolumeStateChanged::" + vs);
San Mehat4270e1e2010-01-29 05:32:19 -0800613
614 Intent in = null;
615
Mike Lockwoodbf2dd442010-03-03 06:16:52 -0500616 if (oldState == VolumeState.Shared && newState != oldState) {
San Mehat2fe718a2010-03-11 12:01:49 -0800617 if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
Mike Lockwoodbf2dd442010-03-03 06:16:52 -0500618 mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED,
619 Uri.parse("file://" + path)));
620 }
621
San Mehat4270e1e2010-01-29 05:32:19 -0800622 if (newState == VolumeState.Init) {
623 } else if (newState == VolumeState.NoMedia) {
624 // NoMedia is handled via Disk Remove events
625 } else if (newState == VolumeState.Idle) {
626 /*
627 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
628 * if we're in the process of enabling UMS
629 */
630 if (!vs.equals(
631 Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
632 Environment.MEDIA_NOFS) && !vs.equals(
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800633 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800634 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state for media bad removal nofs and unmountable");
San Mehat4270e1e2010-01-29 05:32:19 -0800635 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
636 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
637 }
638 } else if (newState == VolumeState.Pending) {
639 } else if (newState == VolumeState.Checking) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800640 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state checking");
San Mehat4270e1e2010-01-29 05:32:19 -0800641 updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
642 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
643 } else if (newState == VolumeState.Mounted) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800644 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state mounted");
San Mehat4270e1e2010-01-29 05:32:19 -0800645 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
San Mehat4270e1e2010-01-29 05:32:19 -0800646 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
647 in.putExtra("read-only", false);
648 } else if (newState == VolumeState.Unmounting) {
San Mehat4270e1e2010-01-29 05:32:19 -0800649 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
650 } else if (newState == VolumeState.Formatting) {
651 } else if (newState == VolumeState.Shared) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800652 if (DEBUG_EVENTS) Log.i(TAG, "Updating volume state media mounted");
San Mehat4270e1e2010-01-29 05:32:19 -0800653 /* Send the media unmounted event first */
654 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
655 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
656 mContext.sendBroadcast(in);
657
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800658 if (DEBUG_EVENTS) Log.i(TAG, "Updating media shared");
San Mehat4270e1e2010-01-29 05:32:19 -0800659 updatePublicVolumeState(path, Environment.MEDIA_SHARED);
660 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
San Mehat2fe718a2010-03-11 12:01:49 -0800661 if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
San Mehat4270e1e2010-01-29 05:32:19 -0800662 } else if (newState == VolumeState.SharedMnt) {
663 Log.e(TAG, "Live shared mounts not supported yet!");
664 return;
665 } else {
666 Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
667 }
668
669 if (in != null) {
670 mContext.sendBroadcast(in);
671 }
672 }
673
San Mehat207e5382010-02-04 20:46:54 -0800674 private boolean doGetShareMethodAvailable(String method) {
675 ArrayList<String> rsp = mConnector.doCommand("share status " + method);
676
677 for (String line : rsp) {
678 String []tok = line.split(" ");
679 int code;
680 try {
681 code = Integer.parseInt(tok[0]);
682 } catch (NumberFormatException nfe) {
683 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
684 return false;
685 }
686 if (code == VoldResponseCode.ShareStatusResult) {
687 if (tok[2].equals("available"))
688 return true;
689 return false;
690 } else {
691 Log.e(TAG, String.format("Unexpected response code %d", code));
692 return false;
693 }
694 }
695 Log.e(TAG, "Got an empty response");
696 return false;
697 }
698
699 private int doMountVolume(String path) {
San Mehatb1043402010-02-05 08:26:50 -0800700 int rc = StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800701
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800702 if (DEBUG_EVENTS) Log.i(TAG, "doMountVolume: Mouting " + path);
San Mehat207e5382010-02-04 20:46:54 -0800703 try {
704 mConnector.doCommand(String.format("volume mount %s", path));
705 } catch (NativeDaemonConnectorException e) {
706 /*
707 * Mount failed for some reason
708 */
709 Intent in = null;
710 int code = e.getCode();
711 if (code == VoldResponseCode.OpFailedNoMedia) {
712 /*
713 * Attempt to mount but no media inserted
714 */
San Mehatb1043402010-02-05 08:26:50 -0800715 rc = StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800716 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800717 if (DEBUG_EVENTS) Log.i(TAG, " updating volume state :: media nofs");
San Mehat207e5382010-02-04 20:46:54 -0800718 /*
719 * Media is blank or does not contain a supported filesystem
720 */
721 updatePublicVolumeState(path, Environment.MEDIA_NOFS);
722 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800723 rc = StorageResultCode.OperationFailedMediaBlank;
San Mehat207e5382010-02-04 20:46:54 -0800724 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -0800725 if (DEBUG_EVENTS) Log.i(TAG, "updating volume state media corrupt");
San Mehat207e5382010-02-04 20:46:54 -0800726 /*
727 * Volume consistency check failed
728 */
729 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
730 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800731 rc = StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800732 } else {
San Mehatb1043402010-02-05 08:26:50 -0800733 rc = StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800734 }
735
736 /*
737 * Send broadcast intent (if required for the failure)
738 */
739 if (in != null) {
740 mContext.sendBroadcast(in);
741 }
742 }
743
744 return rc;
745 }
746
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800747 /*
748 * If force is not set, we do not unmount if there are
749 * processes holding references to the volume about to be unmounted.
750 * If force is set, all the processes holding references need to be
751 * killed via the ActivityManager before actually unmounting the volume.
752 * This might even take a while and might be retried after timed delays
753 * to make sure we dont end up in an instable state and kill some core
754 * processes.
755 */
San Mehatd9709982010-02-18 11:43:03 -0800756 private int doUnmountVolume(String path, boolean force) {
San Mehat59443a62010-02-09 13:28:45 -0800757 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
San Mehat207e5382010-02-04 20:46:54 -0800758 return VoldResponseCode.OpFailedVolNotMounted;
759 }
760
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800761 // We unmounted the volume. No of the asec containers are available now.
762 synchronized (mAsecMountSet) {
763 mAsecMountSet.clear();
764 }
San Mehat207e5382010-02-04 20:46:54 -0800765 // Notify PackageManager of potential media removal and deal with
766 // return code later on. The caller of this api should be aware or have been
767 // notified that the applications installed on the media will be killed.
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -0800768 // Redundant probably. But no harm in updating state again.
San Mehat207e5382010-02-04 20:46:54 -0800769 mPms.updateExternalMediaStatus(false);
770 try {
San Mehatd9709982010-02-18 11:43:03 -0800771 mConnector.doCommand(String.format(
772 "volume unmount %s%s", path, (force ? " force" : "")));
San Mehatb1043402010-02-05 08:26:50 -0800773 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800774 } catch (NativeDaemonConnectorException e) {
775 // Don't worry about mismatch in PackageManager since the
776 // call back will handle the status changes any way.
777 int code = e.getCode();
778 if (code == VoldResponseCode.OpFailedVolNotMounted) {
San Mehata181b212010-02-11 06:50:20 -0800779 return StorageResultCode.OperationFailedStorageNotMounted;
San Mehatd9709982010-02-18 11:43:03 -0800780 } else if (code == VoldResponseCode.OpFailedStorageBusy) {
781 return StorageResultCode.OperationFailedStorageBusy;
San Mehat207e5382010-02-04 20:46:54 -0800782 } else {
San Mehatb1043402010-02-05 08:26:50 -0800783 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800784 }
785 }
786 }
787
788 private int doFormatVolume(String path) {
789 try {
790 String cmd = String.format("volume format %s", path);
791 mConnector.doCommand(cmd);
San Mehatb1043402010-02-05 08:26:50 -0800792 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800793 } catch (NativeDaemonConnectorException e) {
794 int code = e.getCode();
795 if (code == VoldResponseCode.OpFailedNoMedia) {
San Mehatb1043402010-02-05 08:26:50 -0800796 return StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800797 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
San Mehatb1043402010-02-05 08:26:50 -0800798 return StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800799 } else {
San Mehatb1043402010-02-05 08:26:50 -0800800 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800801 }
802 }
803 }
804
San Mehatb1043402010-02-05 08:26:50 -0800805 private boolean doGetVolumeShared(String path, String method) {
806 String cmd = String.format("volume shared %s %s", path, method);
807 ArrayList<String> rsp = mConnector.doCommand(cmd);
808
809 for (String line : rsp) {
810 String []tok = line.split(" ");
811 int code;
812 try {
813 code = Integer.parseInt(tok[0]);
814 } catch (NumberFormatException nfe) {
815 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
816 return false;
817 }
818 if (code == VoldResponseCode.ShareEnabledResult) {
819 if (tok[2].equals("enabled"))
820 return true;
821 return false;
822 } else {
823 Log.e(TAG, String.format("Unexpected response code %d", code));
824 return false;
825 }
826 }
827 Log.e(TAG, "Got an empty response");
828 return false;
829 }
830
San Mehat207e5382010-02-04 20:46:54 -0800831 private void notifyShareAvailabilityChange(String method, final boolean avail) {
San Mehat4270e1e2010-01-29 05:32:19 -0800832 if (!method.equals("ums")) {
833 Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
834 return;
835 }
836
837 synchronized (mListeners) {
838 for (int i = mListeners.size() -1; i >= 0; i--) {
839 MountServiceBinderListener bl = mListeners.get(i);
840 try {
San Mehatb1043402010-02-05 08:26:50 -0800841 bl.mListener.onUsbMassStorageConnectionChanged(avail);
San Mehat4270e1e2010-01-29 05:32:19 -0800842 } catch (RemoteException rex) {
843 Log.e(TAG, "Listener dead");
844 mListeners.remove(i);
845 } catch (Exception ex) {
846 Log.e(TAG, "Listener failed", ex);
847 }
848 }
849 }
850
San Mehat207e5382010-02-04 20:46:54 -0800851 if (mBooted == true) {
San Mehat6a965af22010-02-24 17:47:30 -0800852 sendUmsIntent(avail);
853 } else {
854 mSendUmsConnectedOnBoot = avail;
San Mehat4270e1e2010-01-29 05:32:19 -0800855 }
San Mehat2fe718a2010-03-11 12:01:49 -0800856
857 final String path = Environment.getExternalStorageDirectory().getPath();
858 if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
859 /*
860 * USB mass storage disconnected while enabled
861 */
862 new Thread() {
863 public void run() {
864 try {
865 int rc;
866 Log.w(TAG, "Disabling UMS after cable disconnect");
867 doShareUnshareVolume(path, "ums", false);
868 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
869 Log.e(TAG, String.format(
870 "Failed to remount {%s} on UMS enabled-disconnect (%d)",
871 path, rc));
872 }
873 } catch (Exception ex) {
874 Log.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
875 }
876 }
877 }.start();
878 }
San Mehat4270e1e2010-01-29 05:32:19 -0800879 }
880
San Mehat6a965af22010-02-24 17:47:30 -0800881 private void sendUmsIntent(boolean c) {
882 mContext.sendBroadcast(
883 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
884 }
885
San Mehat207e5382010-02-04 20:46:54 -0800886 private void validatePermission(String perm) {
San Mehat4270e1e2010-01-29 05:32:19 -0800887 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
888 throw new SecurityException(String.format("Requires %s permission", perm));
889 }
890 }
891
892 /**
San Mehat207e5382010-02-04 20:46:54 -0800893 * Constructs a new MountService instance
894 *
895 * @param context Binder context for this service
896 */
897 public MountService(Context context) {
898 mContext = context;
899
San Mehat207e5382010-02-04 20:46:54 -0800900 // XXX: This will go away soon in favor of IMountServiceObserver
901 mPms = (PackageManagerService) ServiceManager.getService("package");
902
903 mContext.registerReceiver(mBroadcastReceiver,
904 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
905
Daniel Sandler5f27ef42010-03-16 15:42:02 -0400906 mHandlerThread = new HandlerThread("MountService");
907 mHandlerThread.start();
908 mHandler = new MountServiceHandler(mHandlerThread.getLooper());
909
Marco Nelissenc34ebce2010-02-18 13:39:41 -0800910 /*
911 * Vold does not run in the simulator, so pretend the connector thread
912 * ran and did its thing.
913 */
914 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
915 mReady = true;
916 mUmsEnabling = true;
917 return;
918 }
919
San Mehat207e5382010-02-04 20:46:54 -0800920 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
921 mReady = false;
922 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
923 thread.start();
924 }
925
926 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800927 * Exposed API calls below here
928 */
929
930 public void registerListener(IMountServiceListener listener) {
931 synchronized (mListeners) {
932 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
933 try {
934 listener.asBinder().linkToDeath(bl, 0);
935 mListeners.add(bl);
936 } catch (RemoteException rex) {
937 Log.e(TAG, "Failed to link to listener death");
938 }
939 }
940 }
941
942 public void unregisterListener(IMountServiceListener listener) {
943 synchronized (mListeners) {
944 for(MountServiceBinderListener bl : mListeners) {
945 if (bl.mListener == listener) {
946 mListeners.remove(mListeners.indexOf(bl));
947 return;
948 }
949 }
950 }
951 }
952
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800953 public void shutdown(final IMountShutdownObserver observer) {
San Mehat4270e1e2010-01-29 05:32:19 -0800954 validatePermission(android.Manifest.permission.SHUTDOWN);
955
956 Log.i(TAG, "Shutting down");
957
958 String path = Environment.getExternalStorageDirectory().getPath();
959 String state = getVolumeState(path);
San Mehat91c77612010-01-07 10:39:41 -0800960
961 if (state.equals(Environment.MEDIA_SHARED)) {
962 /*
963 * If the media is currently shared, unshare it.
964 * XXX: This is still dangerous!. We should not
965 * be rebooting at *all* if UMS is enabled, since
966 * the UMS host could have dirty FAT cache entries
967 * yet to flush.
968 */
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800969 setUsbMassStorageEnabled(false);
San Mehat91c77612010-01-07 10:39:41 -0800970 } else if (state.equals(Environment.MEDIA_CHECKING)) {
971 /*
972 * If the media is being checked, then we need to wait for
973 * it to complete before being able to proceed.
974 */
975 // XXX: @hackbod - Should we disable the ANR timer here?
976 int retries = 30;
977 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
978 try {
979 Thread.sleep(1000);
980 } catch (InterruptedException iex) {
981 Log.e(TAG, "Interrupted while waiting for media", iex);
982 break;
983 }
984 state = Environment.getExternalStorageState();
985 }
986 if (retries == 0) {
987 Log.e(TAG, "Timed out waiting for media to check");
988 }
989 }
990
991 if (state.equals(Environment.MEDIA_MOUNTED)) {
Suchi Amalapurapu6ffce2e2010-03-08 14:48:40 -0800992 // Post a unmount message.
993 ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
994 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
San Mehat4270e1e2010-01-29 05:32:19 -0800995 }
996 }
997
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -0800998 private boolean getUmsEnabling() {
999 synchronized (mListeners) {
1000 return mUmsEnabling;
1001 }
1002 }
1003
1004 private void setUmsEnabling(boolean enable) {
1005 synchronized (mListeners) {
1006 mUmsEnabling = true;
1007 }
1008 }
1009
San Mehatb1043402010-02-05 08:26:50 -08001010 public boolean isUsbMassStorageConnected() {
San Mehat207e5382010-02-04 20:46:54 -08001011 waitForReady();
San Mehat91c77612010-01-07 10:39:41 -08001012
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -08001013 if (getUmsEnabling()) {
San Mehatb1043402010-02-05 08:26:50 -08001014 return true;
San Mehat7fd0fee2009-12-17 07:12:23 -08001015 }
San Mehatb1043402010-02-05 08:26:50 -08001016 return doGetShareMethodAvailable("ums");
1017 }
1018
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -08001019 public void setUsbMassStorageEnabled(boolean enable) {
San Mehatb1043402010-02-05 08:26:50 -08001020 waitForReady();
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -08001021 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehatb1043402010-02-05 08:26:50 -08001022
Suchi Amalapurapu0eec21d2010-02-25 17:07:14 -08001023 // TODO: Add support for multiple share methods
1024
1025 /*
1026 * If the volume is mounted and we're enabling then unmount it
1027 */
1028 String path = Environment.getExternalStorageDirectory().getPath();
1029 String vs = getVolumeState(path);
1030 String method = "ums";
1031 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1032 // Override for isUsbMassStorageEnabled()
1033 setUmsEnabling(enable);
1034 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1035 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1036 // Clear override
1037 setUmsEnabling(false);
1038 }
1039 /*
1040 * If we disabled UMS then mount the volume
1041 */
1042 if (!enable) {
1043 doShareUnshareVolume(path, method, enable);
1044 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1045 Log.e(TAG, "Failed to remount " + path +
1046 " after disabling share method " + method);
1047 /*
1048 * Even though the mount failed, the unshare didn't so don't indicate an error.
1049 * The mountVolume() call will have set the storage state and sent the necessary
1050 * broadcasts.
1051 */
1052 }
1053 }
San Mehatb1043402010-02-05 08:26:50 -08001054 }
1055
1056 public boolean isUsbMassStorageEnabled() {
1057 waitForReady();
1058 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001059 }
San Mehat4270e1e2010-01-29 05:32:19 -08001060
San Mehat7fd0fee2009-12-17 07:12:23 -08001061 /**
1062 * @return state of the volume at the specified mount point
1063 */
San Mehat4270e1e2010-01-29 05:32:19 -08001064 public String getVolumeState(String mountPoint) {
San Mehat7fd0fee2009-12-17 07:12:23 -08001065 /*
1066 * XXX: Until we have multiple volume discovery, just hardwire
1067 * this to /sdcard
1068 */
1069 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
1070 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1071 throw new IllegalArgumentException();
1072 }
1073
1074 return mLegacyState;
1075 }
1076
San Mehat4270e1e2010-01-29 05:32:19 -08001077 public int mountVolume(String path) {
1078 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat4270e1e2010-01-29 05:32:19 -08001079
San Mehat207e5382010-02-04 20:46:54 -08001080 waitForReady();
1081 return doMountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 }
1083
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -08001084 public void unmountVolume(String path, boolean force) {
San Mehat4270e1e2010-01-29 05:32:19 -08001085 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -08001086 waitForReady();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087
Suchi Amalapurapu8a9ab242010-03-11 16:49:16 -08001088 String volState = getVolumeState(path);
1089 if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path + " force = " + force);
1090 if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1091 Environment.MEDIA_REMOVED.equals(volState) ||
1092 Environment.MEDIA_SHARED.equals(volState) ||
1093 Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1094 // Media already unmounted or cannot be unmounted.
1095 // TODO return valid return code when adding observer call back.
1096 return;
1097 }
Suchi Amalapurapuc42e29e2010-02-22 16:03:53 -08001098 UnmountCallBack ucb = new UnmountCallBack(path, force);
1099 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001100 }
1101
San Mehat4270e1e2010-01-29 05:32:19 -08001102 public int formatVolume(String path) {
1103 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -08001104 waitForReady();
San Mehat5b77dab2010-01-26 13:28:50 -08001105
San Mehat207e5382010-02-04 20:46:54 -08001106 return doFormatVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001107 }
1108
San Mehatc1b4ce92010-02-16 17:13:03 -08001109 public int []getStorageUsers(String path) {
1110 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1111 waitForReady();
1112 try {
1113 String[] r = mConnector.doListCommand(
1114 String.format("storage users %s", path),
1115 VoldResponseCode.StorageUsersListResult);
1116 // FMT: <pid> <process name>
1117 int[] data = new int[r.length];
1118 for (int i = 0; i < r.length; i++) {
1119 String []tok = r[i].split(" ");
1120 try {
1121 data[i] = Integer.parseInt(tok[0]);
1122 } catch (NumberFormatException nfe) {
1123 Log.e(TAG, String.format("Error parsing pid %s", tok[0]));
1124 return new int[0];
1125 }
1126 }
1127 return data;
1128 } catch (NativeDaemonConnectorException e) {
1129 Log.e(TAG, "Failed to retrieve storage users list", e);
1130 return new int[0];
1131 }
1132 }
1133
San Mehatb1043402010-02-05 08:26:50 -08001134 private void warnOnNotMounted() {
1135 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1136 Log.w(TAG, "getSecureContainerList() called when storage not mounted");
1137 }
1138 }
1139
San Mehat4270e1e2010-01-29 05:32:19 -08001140 public String[] getSecureContainerList() {
1141 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -08001142 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001143 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -08001144
San Mehat4270e1e2010-01-29 05:32:19 -08001145 try {
1146 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
1147 } catch (NativeDaemonConnectorException e) {
1148 return new String[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001149 }
1150 }
San Mehat36972292010-01-06 11:06:32 -08001151
San Mehat4270e1e2010-01-29 05:32:19 -08001152 public int createSecureContainer(String id, int sizeMb, String fstype,
1153 String key, int ownerUid) {
1154 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehat207e5382010-02-04 20:46:54 -08001155 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001156 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001157
San Mehatb1043402010-02-05 08:26:50 -08001158 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001159 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
1160 try {
1161 mConnector.doCommand(cmd);
1162 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001163 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001164 }
San Mehata181b212010-02-11 06:50:20 -08001165
1166 if (rc == StorageResultCode.OperationSucceeded) {
1167 synchronized (mAsecMountSet) {
1168 mAsecMountSet.add(id);
1169 }
1170 }
San Mehat4270e1e2010-01-29 05:32:19 -08001171 return rc;
San Mehat36972292010-01-06 11:06:32 -08001172 }
1173
San Mehat4270e1e2010-01-29 05:32:19 -08001174 public int finalizeSecureContainer(String id) {
1175 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehatb1043402010-02-05 08:26:50 -08001176 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001177
San Mehatb1043402010-02-05 08:26:50 -08001178 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001179 try {
1180 mConnector.doCommand(String.format("asec finalize %s", id));
San Mehata181b212010-02-11 06:50:20 -08001181 /*
1182 * Finalization does a remount, so no need
1183 * to update mAsecMountSet
1184 */
San Mehat4270e1e2010-01-29 05:32:19 -08001185 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001186 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001187 }
San Mehat4270e1e2010-01-29 05:32:19 -08001188 return rc;
San Mehat36972292010-01-06 11:06:32 -08001189 }
1190
San Mehatd9709982010-02-18 11:43:03 -08001191 public int destroySecureContainer(String id, boolean force) {
San Mehat4270e1e2010-01-29 05:32:19 -08001192 validatePermission(android.Manifest.permission.ASEC_DESTROY);
San Mehat207e5382010-02-04 20:46:54 -08001193 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001194 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -08001195
San Mehatb1043402010-02-05 08:26:50 -08001196 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001197 try {
San Mehatd9709982010-02-18 11:43:03 -08001198 mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
San Mehat4270e1e2010-01-29 05:32:19 -08001199 } catch (NativeDaemonConnectorException e) {
San Mehatd9709982010-02-18 11:43:03 -08001200 int code = e.getCode();
1201 if (code == VoldResponseCode.OpFailedStorageBusy) {
1202 rc = StorageResultCode.OperationFailedStorageBusy;
1203 } else {
1204 rc = StorageResultCode.OperationFailedInternalError;
1205 }
San Mehat02735bc2010-01-26 15:18:08 -08001206 }
San Mehata181b212010-02-11 06:50:20 -08001207
1208 if (rc == StorageResultCode.OperationSucceeded) {
1209 synchronized (mAsecMountSet) {
1210 if (mAsecMountSet.contains(id)) {
1211 mAsecMountSet.remove(id);
1212 }
1213 }
1214 }
1215
San Mehat4270e1e2010-01-29 05:32:19 -08001216 return rc;
San Mehat36972292010-01-06 11:06:32 -08001217 }
1218
San Mehat4270e1e2010-01-29 05:32:19 -08001219 public int mountSecureContainer(String id, String key, int ownerUid) {
1220 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -08001221 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001222 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001223
San Mehata181b212010-02-11 06:50:20 -08001224 synchronized (mAsecMountSet) {
1225 if (mAsecMountSet.contains(id)) {
1226 return StorageResultCode.OperationFailedStorageMounted;
1227 }
1228 }
1229
San Mehatb1043402010-02-05 08:26:50 -08001230 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001231 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
1232 try {
1233 mConnector.doCommand(cmd);
1234 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001235 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001236 }
San Mehat6cdd9c02010-02-09 14:45:20 -08001237
1238 if (rc == StorageResultCode.OperationSucceeded) {
1239 synchronized (mAsecMountSet) {
1240 mAsecMountSet.add(id);
1241 }
1242 }
San Mehat4270e1e2010-01-29 05:32:19 -08001243 return rc;
San Mehat36972292010-01-06 11:06:32 -08001244 }
1245
San Mehatd9709982010-02-18 11:43:03 -08001246 public int unmountSecureContainer(String id, boolean force) {
San Mehat4270e1e2010-01-29 05:32:19 -08001247 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -08001248 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001249 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001250
San Mehat6cdd9c02010-02-09 14:45:20 -08001251 synchronized (mAsecMountSet) {
1252 if (!mAsecMountSet.contains(id)) {
San Mehata181b212010-02-11 06:50:20 -08001253 return StorageResultCode.OperationFailedStorageNotMounted;
San Mehat6cdd9c02010-02-09 14:45:20 -08001254 }
1255 }
1256
San Mehatb1043402010-02-05 08:26:50 -08001257 int rc = StorageResultCode.OperationSucceeded;
San Mehatd9709982010-02-18 11:43:03 -08001258 String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
San Mehat4270e1e2010-01-29 05:32:19 -08001259 try {
1260 mConnector.doCommand(cmd);
1261 } catch (NativeDaemonConnectorException e) {
San Mehatd9709982010-02-18 11:43:03 -08001262 int code = e.getCode();
1263 if (code == VoldResponseCode.OpFailedStorageBusy) {
1264 rc = StorageResultCode.OperationFailedStorageBusy;
1265 } else {
1266 rc = StorageResultCode.OperationFailedInternalError;
1267 }
San Mehat02735bc2010-01-26 15:18:08 -08001268 }
San Mehat6cdd9c02010-02-09 14:45:20 -08001269
1270 if (rc == StorageResultCode.OperationSucceeded) {
1271 synchronized (mAsecMountSet) {
1272 mAsecMountSet.remove(id);
1273 }
1274 }
San Mehat4270e1e2010-01-29 05:32:19 -08001275 return rc;
San Mehat9dba7092010-01-18 06:47:41 -08001276 }
1277
San Mehat6cdd9c02010-02-09 14:45:20 -08001278 public boolean isSecureContainerMounted(String id) {
1279 validatePermission(android.Manifest.permission.ASEC_ACCESS);
1280 waitForReady();
1281 warnOnNotMounted();
1282
1283 synchronized (mAsecMountSet) {
1284 return mAsecMountSet.contains(id);
1285 }
1286 }
1287
San Mehat4270e1e2010-01-29 05:32:19 -08001288 public int renameSecureContainer(String oldId, String newId) {
1289 validatePermission(android.Manifest.permission.ASEC_RENAME);
San Mehat207e5382010-02-04 20:46:54 -08001290 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001291 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -08001292
San Mehata181b212010-02-11 06:50:20 -08001293 synchronized (mAsecMountSet) {
San Mehat85451ee2010-02-24 08:54:18 -08001294 /*
1295 * Because a mounted container has active internal state which cannot be
1296 * changed while active, we must ensure both ids are not currently mounted.
1297 */
1298 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
San Mehata181b212010-02-11 06:50:20 -08001299 return StorageResultCode.OperationFailedStorageMounted;
1300 }
1301 }
1302
San Mehatb1043402010-02-05 08:26:50 -08001303 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -08001304 String cmd = String.format("asec rename %s %s", oldId, newId);
1305 try {
1306 mConnector.doCommand(cmd);
1307 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -08001308 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -08001309 }
San Mehata181b212010-02-11 06:50:20 -08001310
San Mehat4270e1e2010-01-29 05:32:19 -08001311 return rc;
San Mehat45f61042010-01-23 08:12:43 -08001312 }
1313
San Mehat4270e1e2010-01-29 05:32:19 -08001314 public String getSecureContainerPath(String id) {
1315 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -08001316 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -08001317 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -08001318
San Mehat4270e1e2010-01-29 05:32:19 -08001319 ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
San Mehat36972292010-01-06 11:06:32 -08001320
San Mehat22dd86e2010-01-12 12:21:18 -08001321 for (String line : rsp) {
1322 String []tok = line.split(" ");
1323 int code = Integer.parseInt(tok[0]);
1324 if (code == VoldResponseCode.AsecPathResult) {
1325 return tok[1];
1326 } else {
San Mehat4270e1e2010-01-29 05:32:19 -08001327 Log.e(TAG, String.format("Unexpected response code %d", code));
1328 return "";
San Mehat22dd86e2010-01-12 12:21:18 -08001329 }
1330 }
San Mehat4270e1e2010-01-29 05:32:19 -08001331
1332 Log.e(TAG, "Got an empty response");
1333 return "";
San Mehat22dd86e2010-01-12 12:21:18 -08001334 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335}
1336