blob: 6de2effe388ed69d5740261b55366e4328ea4ee9 [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.PackageManager;
24import android.content.res.Resources;
25import android.net.Uri;
San Mehatb1043402010-02-05 08:26:50 -080026import android.os.storage.IMountService;
27import android.os.storage.IMountServiceListener;
28import android.os.storage.StorageResultCode;
San Mehat4270e1e2010-01-29 05:32:19 -080029import android.os.RemoteException;
30import android.os.IBinder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.os.Environment;
Suchi Amalapurapufd3530f2010-01-18 00:15:59 -080032import android.os.ServiceManager;
San Mehat207e5382010-02-04 20:46:54 -080033import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.os.SystemProperties;
35import android.os.UEventObserver;
San Mehat1f6301e2010-01-07 22:40:27 -080036import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.text.TextUtils;
38import android.util.Log;
San Mehat22dd86e2010-01-12 12:21:18 -080039import java.util.ArrayList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
41import java.io.File;
42import java.io.FileReader;
43
44/**
San Mehatb1043402010-02-05 08:26:50 -080045 * MountService implements back-end services for platform storage
46 * management.
47 * @hide - Applications should use android.os.storage.StorageManager
48 * to access the MountService.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 */
San Mehat22dd86e2010-01-12 12:21:18 -080050class MountService extends IMountService.Stub
51 implements INativeDaemonConnectorCallbacks {
San Mehatb1043402010-02-05 08:26:50 -080052 private static final boolean LOCAL_LOGD = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
54 private static final String TAG = "MountService";
55
San Mehat4270e1e2010-01-29 05:32:19 -080056 /*
57 * Internal vold volume state constants
58 */
San Mehat7fd0fee2009-12-17 07:12:23 -080059 class VolumeState {
60 public static final int Init = -1;
61 public static final int NoMedia = 0;
62 public static final int Idle = 1;
63 public static final int Pending = 2;
64 public static final int Checking = 3;
65 public static final int Mounted = 4;
66 public static final int Unmounting = 5;
67 public static final int Formatting = 6;
68 public static final int Shared = 7;
69 public static final int SharedMnt = 8;
70 }
71
San Mehat4270e1e2010-01-29 05:32:19 -080072 /*
73 * Internal vold response code constants
74 */
San Mehat22dd86e2010-01-12 12:21:18 -080075 class VoldResponseCode {
San Mehat4270e1e2010-01-29 05:32:19 -080076 /*
77 * 100 series - Requestion action was initiated; expect another reply
78 * before proceeding with a new command.
79 */
San Mehat22dd86e2010-01-12 12:21:18 -080080 public static final int VolumeListResult = 110;
81 public static final int AsecListResult = 111;
82
San Mehat4270e1e2010-01-29 05:32:19 -080083 /*
84 * 200 series - Requestion action has been successfully completed.
85 */
86 public static final int ShareStatusResult = 210;
San Mehat22dd86e2010-01-12 12:21:18 -080087 public static final int AsecPathResult = 211;
San Mehat4270e1e2010-01-29 05:32:19 -080088 public static final int ShareEnabledResult = 212;
San Mehat22dd86e2010-01-12 12:21:18 -080089
San Mehat4270e1e2010-01-29 05:32:19 -080090 /*
91 * 400 series - Command was accepted, but the requested action
92 * did not take place.
93 */
94 public static final int OpFailedNoMedia = 401;
95 public static final int OpFailedMediaBlank = 402;
96 public static final int OpFailedMediaCorrupt = 403;
97 public static final int OpFailedVolNotMounted = 404;
98 public static final int OpFailedVolBusy = 405;
99
100 /*
101 * 600 series - Unsolicited broadcasts.
102 */
San Mehat22dd86e2010-01-12 12:21:18 -0800103 public static final int VolumeStateChange = 605;
San Mehat22dd86e2010-01-12 12:21:18 -0800104 public static final int ShareAvailabilityChange = 620;
105 public static final int VolumeDiskInserted = 630;
106 public static final int VolumeDiskRemoved = 631;
107 public static final int VolumeBadRemoval = 632;
108 }
109
San Mehat4270e1e2010-01-29 05:32:19 -0800110 private Context mContext;
111 private NativeDaemonConnector mConnector;
112 private String mLegacyState = Environment.MEDIA_REMOVED;
113 private PackageManagerService mPms;
114 private boolean mUmsEnabling;
115 private ArrayList<MountServiceBinderListener> mListeners;
San Mehat207e5382010-02-04 20:46:54 -0800116 private boolean mBooted;
117 private boolean mReady;
Suchi Amalapurapufd3530f2010-01-18 00:15:59 -0800118
San Mehat207e5382010-02-04 20:46:54 -0800119 private void waitForReady() {
120 while (mReady == false) {
121 for (int retries = 5; retries > 0; retries--) {
122 if (mReady) {
123 return;
124 }
125 SystemClock.sleep(1000);
126 }
127 Log.w(TAG, "Waiting too long for mReady!");
128 }
San Mehat1f6301e2010-01-07 22:40:27 -0800129 }
130
San Mehat207e5382010-02-04 20:46:54 -0800131 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 public void onReceive(Context context, Intent intent) {
San Mehat91c77612010-01-07 10:39:41 -0800133 String action = intent.getAction();
134
135 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
San Mehat207e5382010-02-04 20:46:54 -0800136 mBooted = true;
San Mehat22dd86e2010-01-12 12:21:18 -0800137
San Mehat207e5382010-02-04 20:46:54 -0800138 String path = Environment.getExternalStorageDirectory().getPath();
139 if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) {
140 int rc = doMountVolume(path);
San Mehatb1043402010-02-05 08:26:50 -0800141 if (rc != StorageResultCode.OperationSucceeded) {
San Mehat207e5382010-02-04 20:46:54 -0800142 Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
143 }
144 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 }
146 }
147 };
148
San Mehat4270e1e2010-01-29 05:32:19 -0800149 private final class MountServiceBinderListener implements IBinder.DeathRecipient {
150 final IMountServiceListener mListener;
151
152 MountServiceBinderListener(IMountServiceListener listener) {
153 mListener = listener;
154
San Mehat91c77612010-01-07 10:39:41 -0800155 }
156
San Mehat4270e1e2010-01-29 05:32:19 -0800157 public void binderDied() {
San Mehatb1043402010-02-05 08:26:50 -0800158 if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
San Mehat4270e1e2010-01-29 05:32:19 -0800159 synchronized(mListeners) {
160 mListeners.remove(this);
161 mListener.asBinder().unlinkToDeath(this, 0);
162 }
163 }
164 }
165
San Mehat207e5382010-02-04 20:46:54 -0800166 private int doShareUnshareVolume(String path, String method, boolean enable) {
San Mehat4270e1e2010-01-29 05:32:19 -0800167 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
168
169 // TODO: Add support for multiple share methods
170 if (!method.equals("ums")) {
171 throw new IllegalArgumentException(String.format("Method %s not supported", method));
172 }
173
174 /*
175 * If the volume is mounted and we're enabling then unmount it
176 */
177 String vs = getVolumeState(path);
178 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
San Mehatb1043402010-02-05 08:26:50 -0800179 mUmsEnabling = enable; // Override for isUsbMassStorageEnabled()
San Mehat207e5382010-02-04 20:46:54 -0800180 doUnmountVolume(path);
San Mehatb1043402010-02-05 08:26:50 -0800181 mUmsEnabling = false; // Clear override
San Mehat4270e1e2010-01-29 05:32:19 -0800182 }
183
184 try {
185 mConnector.doCommand(String.format(
186 "volume %sshare %s %s", (enable ? "" : "un"), path, method));
187 } catch (NativeDaemonConnectorException e) {
188 Log.e(TAG, "Failed to share/unshare", e);
San Mehatb1043402010-02-05 08:26:50 -0800189 return StorageResultCode.OperationFailedInternalError;
San Mehat4270e1e2010-01-29 05:32:19 -0800190 }
191
192 /*
193 * If we disabled UMS then mount the volume
194 */
195 if (!enable) {
San Mehatb1043402010-02-05 08:26:50 -0800196 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800197 Log.e(TAG, String.format(
198 "Failed to remount %s after disabling share method %s", path, method));
199 /*
200 * Even though the mount failed, the unshare didn't so don't indicate an error.
201 * The mountVolume() call will have set the storage state and sent the necessary
202 * broadcasts.
203 */
204 }
205 }
206
San Mehatb1043402010-02-05 08:26:50 -0800207 return StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800208 }
209
San Mehat207e5382010-02-04 20:46:54 -0800210 private void updatePublicVolumeState(String path, String state) {
San Mehat4270e1e2010-01-29 05:32:19 -0800211 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
212 Log.w(TAG, "Multiple volumes not currently supported");
213 return;
214 }
San Mehatb1043402010-02-05 08:26:50 -0800215
216 if (mLegacyState.equals(state)) {
217 Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
218 return;
219 }
San Mehat4270e1e2010-01-29 05:32:19 -0800220
221 String oldState = mLegacyState;
222 mLegacyState = state;
223
224 synchronized (mListeners) {
225 for (int i = mListeners.size() -1; i >= 0; i--) {
226 MountServiceBinderListener bl = mListeners.get(i);
227 try {
San Mehatb1043402010-02-05 08:26:50 -0800228 bl.mListener.onStorageStateChanged(path, oldState, state);
San Mehat4270e1e2010-01-29 05:32:19 -0800229 } catch (RemoteException rex) {
230 Log.e(TAG, "Listener dead");
231 mListeners.remove(i);
232 } catch (Exception ex) {
233 Log.e(TAG, "Listener failed", ex);
234 }
235 }
236 }
237 }
238
239 /**
240 *
241 * Callback from NativeDaemonConnector
242 */
243 public void onDaemonConnected() {
244 /*
245 * Since we'll be calling back into the NativeDaemonConnector,
246 * we need to do our work in a new thread.
247 */
248 new Thread() {
249 public void run() {
250 /**
251 * Determine media state and UMS detection status
252 */
253 String path = Environment.getExternalStorageDirectory().getPath();
254 String state = Environment.MEDIA_REMOVED;
255
256 try {
257 String[] vols = mConnector.doListCommand(
258 "volume list", VoldResponseCode.VolumeListResult);
259 for (String volstr : vols) {
260 String[] tok = volstr.split(" ");
261 // FMT: <label> <mountpoint> <state>
262 if (!tok[1].equals(path)) {
263 Log.w(TAG, String.format(
264 "Skipping unknown volume '%s'",tok[1]));
265 continue;
266 }
267 int st = Integer.parseInt(tok[2]);
268 if (st == VolumeState.NoMedia) {
269 state = Environment.MEDIA_REMOVED;
270 } else if (st == VolumeState.Idle) {
San Mehat207e5382010-02-04 20:46:54 -0800271 state = Environment.MEDIA_UNMOUNTED;
San Mehat4270e1e2010-01-29 05:32:19 -0800272 } else if (st == VolumeState.Mounted) {
273 state = Environment.MEDIA_MOUNTED;
274 Log.i(TAG, "Media already mounted on daemon connection");
275 } else if (st == VolumeState.Shared) {
276 state = Environment.MEDIA_SHARED;
277 Log.i(TAG, "Media shared on daemon connection");
278 } else {
279 throw new Exception(String.format("Unexpected state %d", st));
280 }
281 }
282 if (state != null) {
283 updatePublicVolumeState(path, state);
284 }
285 } catch (Exception e) {
286 Log.e(TAG, "Error processing initial volume state", e);
287 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
288 }
289
290 try {
San Mehat207e5382010-02-04 20:46:54 -0800291 boolean avail = doGetShareMethodAvailable("ums");
San Mehat4270e1e2010-01-29 05:32:19 -0800292 notifyShareAvailabilityChange("ums", avail);
293 } catch (Exception ex) {
294 Log.w(TAG, "Failed to get share availability");
295 }
San Mehat207e5382010-02-04 20:46:54 -0800296 /*
297 * Now that we've done our initialization, release
298 * the hounds!
299 */
300 mReady = true;
San Mehat4270e1e2010-01-29 05:32:19 -0800301 }
302 }.start();
303 }
304
305 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800306 * Callback from NativeDaemonConnector
307 */
308 public boolean onEvent(int code, String raw, String[] cooked) {
309 Intent in = null;
310
San Mehat4270e1e2010-01-29 05:32:19 -0800311 if (code == VoldResponseCode.VolumeStateChange) {
312 /*
313 * One of the volumes we're managing has changed state.
314 * Format: "NNN Volume <label> <path> state changed
315 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
316 */
317 notifyVolumeStateChange(
318 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
319 Integer.parseInt(cooked[10]));
320 } else if (code == VoldResponseCode.ShareAvailabilityChange) {
321 // FMT: NNN Share method <method> now <available|unavailable>
322 boolean avail = false;
323 if (cooked[5].equals("available")) {
324 avail = true;
325 }
326 notifyShareAvailabilityChange(cooked[3], avail);
327 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
328 (code == VoldResponseCode.VolumeDiskRemoved) ||
329 (code == VoldResponseCode.VolumeBadRemoval)) {
330 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
331 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
332 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
333 final String label = cooked[2];
334 final String path = cooked[3];
335 int major = -1;
336 int minor = -1;
337
338 try {
339 String devComp = cooked[6].substring(1, cooked[6].length() -1);
340 String[] devTok = devComp.split(":");
341 major = Integer.parseInt(devTok[0]);
342 minor = Integer.parseInt(devTok[1]);
343 } catch (Exception ex) {
344 Log.e(TAG, "Failed to parse major/minor", ex);
345 }
346
San Mehat4270e1e2010-01-29 05:32:19 -0800347 if (code == VoldResponseCode.VolumeDiskInserted) {
348 new Thread() {
349 public void run() {
350 try {
351 int rc;
San Mehatb1043402010-02-05 08:26:50 -0800352 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800353 Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
354 }
355 } catch (Exception ex) {
356 Log.w(TAG, "Failed to mount media on insertion", ex);
357 }
358 }
359 }.start();
360 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
361 /*
362 * This event gets trumped if we're already in BAD_REMOVAL state
363 */
364 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
365 return true;
366 }
367 /* Send the media unmounted event first */
368 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
369 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
370 mContext.sendBroadcast(in);
371
372 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
373 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
374 } else if (code == VoldResponseCode.VolumeBadRemoval) {
375 /* Send the media unmounted event first */
376 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
377 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
378 mContext.sendBroadcast(in);
379
380 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
381 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
382 } else {
383 Log.e(TAG, String.format("Unknown code {%d}", code));
384 }
385 } else {
386 return false;
387 }
388
389 if (in != null) {
390 mContext.sendBroadcast(in);
391 }
392 return true;
393 }
394
San Mehat207e5382010-02-04 20:46:54 -0800395 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
San Mehat4270e1e2010-01-29 05:32:19 -0800396 String vs = getVolumeState(path);
397
398 Intent in = null;
399
400 if (newState == VolumeState.Init) {
401 } else if (newState == VolumeState.NoMedia) {
402 // NoMedia is handled via Disk Remove events
403 } else if (newState == VolumeState.Idle) {
404 /*
405 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
406 * if we're in the process of enabling UMS
407 */
408 if (!vs.equals(
409 Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
410 Environment.MEDIA_NOFS) && !vs.equals(
411 Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
412 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
413 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
414 }
415 } else if (newState == VolumeState.Pending) {
416 } else if (newState == VolumeState.Checking) {
417 updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
418 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
419 } else if (newState == VolumeState.Mounted) {
420 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
421 // Update media status on PackageManagerService to mount packages on sdcard
422 mPms.updateExternalMediaStatus(true);
423 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
424 in.putExtra("read-only", false);
425 } else if (newState == VolumeState.Unmounting) {
426 mPms.updateExternalMediaStatus(false);
427 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
428 } else if (newState == VolumeState.Formatting) {
429 } else if (newState == VolumeState.Shared) {
430 /* Send the media unmounted event first */
431 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
432 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
433 mContext.sendBroadcast(in);
434
435 updatePublicVolumeState(path, Environment.MEDIA_SHARED);
436 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
437 } else if (newState == VolumeState.SharedMnt) {
438 Log.e(TAG, "Live shared mounts not supported yet!");
439 return;
440 } else {
441 Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
442 }
443
444 if (in != null) {
445 mContext.sendBroadcast(in);
446 }
447 }
448
San Mehat207e5382010-02-04 20:46:54 -0800449 private boolean doGetShareMethodAvailable(String method) {
450 ArrayList<String> rsp = mConnector.doCommand("share status " + method);
451
452 for (String line : rsp) {
453 String []tok = line.split(" ");
454 int code;
455 try {
456 code = Integer.parseInt(tok[0]);
457 } catch (NumberFormatException nfe) {
458 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
459 return false;
460 }
461 if (code == VoldResponseCode.ShareStatusResult) {
462 if (tok[2].equals("available"))
463 return true;
464 return false;
465 } else {
466 Log.e(TAG, String.format("Unexpected response code %d", code));
467 return false;
468 }
469 }
470 Log.e(TAG, "Got an empty response");
471 return false;
472 }
473
474 private int doMountVolume(String path) {
San Mehatb1043402010-02-05 08:26:50 -0800475 int rc = StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800476
477 try {
478 mConnector.doCommand(String.format("volume mount %s", path));
479 } catch (NativeDaemonConnectorException e) {
480 /*
481 * Mount failed for some reason
482 */
483 Intent in = null;
484 int code = e.getCode();
485 if (code == VoldResponseCode.OpFailedNoMedia) {
486 /*
487 * Attempt to mount but no media inserted
488 */
San Mehatb1043402010-02-05 08:26:50 -0800489 rc = StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800490 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
491 /*
492 * Media is blank or does not contain a supported filesystem
493 */
494 updatePublicVolumeState(path, Environment.MEDIA_NOFS);
495 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800496 rc = StorageResultCode.OperationFailedMediaBlank;
San Mehat207e5382010-02-04 20:46:54 -0800497 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
498 /*
499 * Volume consistency check failed
500 */
501 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
502 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800503 rc = StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800504 } else {
San Mehatb1043402010-02-05 08:26:50 -0800505 rc = StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800506 }
507
508 /*
509 * Send broadcast intent (if required for the failure)
510 */
511 if (in != null) {
512 mContext.sendBroadcast(in);
513 }
514 }
515
516 return rc;
517 }
518
519 private int doUnmountVolume(String path) {
520 if (getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
521 return VoldResponseCode.OpFailedVolNotMounted;
522 }
523
524 // Notify PackageManager of potential media removal and deal with
525 // return code later on. The caller of this api should be aware or have been
526 // notified that the applications installed on the media will be killed.
527 mPms.updateExternalMediaStatus(false);
528 try {
529 mConnector.doCommand(String.format("volume unmount %s", path));
San Mehatb1043402010-02-05 08:26:50 -0800530 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800531 } catch (NativeDaemonConnectorException e) {
532 // Don't worry about mismatch in PackageManager since the
533 // call back will handle the status changes any way.
534 int code = e.getCode();
535 if (code == VoldResponseCode.OpFailedVolNotMounted) {
San Mehatb1043402010-02-05 08:26:50 -0800536 return StorageResultCode.OperationFailedVolumeNotMounted;
San Mehat207e5382010-02-04 20:46:54 -0800537 } else {
San Mehatb1043402010-02-05 08:26:50 -0800538 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800539 }
540 }
541 }
542
543 private int doFormatVolume(String path) {
544 try {
545 String cmd = String.format("volume format %s", path);
546 mConnector.doCommand(cmd);
San Mehatb1043402010-02-05 08:26:50 -0800547 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800548 } catch (NativeDaemonConnectorException e) {
549 int code = e.getCode();
550 if (code == VoldResponseCode.OpFailedNoMedia) {
San Mehatb1043402010-02-05 08:26:50 -0800551 return StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800552 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
San Mehatb1043402010-02-05 08:26:50 -0800553 return StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800554 } else {
San Mehatb1043402010-02-05 08:26:50 -0800555 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800556 }
557 }
558 }
559
San Mehatb1043402010-02-05 08:26:50 -0800560 private boolean doGetVolumeShared(String path, String method) {
561 String cmd = String.format("volume shared %s %s", path, method);
562 ArrayList<String> rsp = mConnector.doCommand(cmd);
563
564 for (String line : rsp) {
565 String []tok = line.split(" ");
566 int code;
567 try {
568 code = Integer.parseInt(tok[0]);
569 } catch (NumberFormatException nfe) {
570 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
571 return false;
572 }
573 if (code == VoldResponseCode.ShareEnabledResult) {
574 if (tok[2].equals("enabled"))
575 return true;
576 return false;
577 } else {
578 Log.e(TAG, String.format("Unexpected response code %d", code));
579 return false;
580 }
581 }
582 Log.e(TAG, "Got an empty response");
583 return false;
584 }
585
San Mehat207e5382010-02-04 20:46:54 -0800586 private void notifyShareAvailabilityChange(String method, final boolean avail) {
San Mehat4270e1e2010-01-29 05:32:19 -0800587 if (!method.equals("ums")) {
588 Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
589 return;
590 }
591
592 synchronized (mListeners) {
593 for (int i = mListeners.size() -1; i >= 0; i--) {
594 MountServiceBinderListener bl = mListeners.get(i);
595 try {
San Mehatb1043402010-02-05 08:26:50 -0800596 bl.mListener.onUsbMassStorageConnectionChanged(avail);
San Mehat4270e1e2010-01-29 05:32:19 -0800597 } catch (RemoteException rex) {
598 Log.e(TAG, "Listener dead");
599 mListeners.remove(i);
600 } catch (Exception ex) {
601 Log.e(TAG, "Listener failed", ex);
602 }
603 }
604 }
605
San Mehat207e5382010-02-04 20:46:54 -0800606 if (mBooted == true) {
607 Intent intent;
608 if (avail) {
609 intent = new Intent(Intent.ACTION_UMS_CONNECTED);
610 } else {
611 intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
612 }
613 mContext.sendBroadcast(intent);
San Mehat4270e1e2010-01-29 05:32:19 -0800614 }
San Mehat4270e1e2010-01-29 05:32:19 -0800615 }
616
San Mehat207e5382010-02-04 20:46:54 -0800617 private void validatePermission(String perm) {
San Mehat4270e1e2010-01-29 05:32:19 -0800618 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
619 throw new SecurityException(String.format("Requires %s permission", perm));
620 }
621 }
622
623 /**
San Mehat207e5382010-02-04 20:46:54 -0800624 * Constructs a new MountService instance
625 *
626 * @param context Binder context for this service
627 */
628 public MountService(Context context) {
629 mContext = context;
630
631 /*
632 * Vold does not run in the simulator, so fake out a mounted
633 * event to trigger MediaScanner
634 */
635 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
636 updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
637 return;
638 }
639
640 // XXX: This will go away soon in favor of IMountServiceObserver
641 mPms = (PackageManagerService) ServiceManager.getService("package");
642
643 mContext.registerReceiver(mBroadcastReceiver,
644 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
645
646 mListeners = new ArrayList<MountServiceBinderListener>();
647
648 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
649 mReady = false;
650 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
651 thread.start();
652 }
653
654 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800655 * Exposed API calls below here
656 */
657
658 public void registerListener(IMountServiceListener listener) {
659 synchronized (mListeners) {
660 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
661 try {
662 listener.asBinder().linkToDeath(bl, 0);
663 mListeners.add(bl);
664 } catch (RemoteException rex) {
665 Log.e(TAG, "Failed to link to listener death");
666 }
667 }
668 }
669
670 public void unregisterListener(IMountServiceListener listener) {
671 synchronized (mListeners) {
672 for(MountServiceBinderListener bl : mListeners) {
673 if (bl.mListener == listener) {
674 mListeners.remove(mListeners.indexOf(bl));
675 return;
676 }
677 }
678 }
679 }
680
681 public void shutdown() {
682 validatePermission(android.Manifest.permission.SHUTDOWN);
683
684 Log.i(TAG, "Shutting down");
685
686 String path = Environment.getExternalStorageDirectory().getPath();
687 String state = getVolumeState(path);
San Mehat91c77612010-01-07 10:39:41 -0800688
689 if (state.equals(Environment.MEDIA_SHARED)) {
690 /*
691 * If the media is currently shared, unshare it.
692 * XXX: This is still dangerous!. We should not
693 * be rebooting at *all* if UMS is enabled, since
694 * the UMS host could have dirty FAT cache entries
695 * yet to flush.
696 */
San Mehatb1043402010-02-05 08:26:50 -0800697 if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800698 Log.e(TAG, "UMS disable on shutdown failed");
San Mehat91c77612010-01-07 10:39:41 -0800699 }
700 } else if (state.equals(Environment.MEDIA_CHECKING)) {
701 /*
702 * If the media is being checked, then we need to wait for
703 * it to complete before being able to proceed.
704 */
705 // XXX: @hackbod - Should we disable the ANR timer here?
706 int retries = 30;
707 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
708 try {
709 Thread.sleep(1000);
710 } catch (InterruptedException iex) {
711 Log.e(TAG, "Interrupted while waiting for media", iex);
712 break;
713 }
714 state = Environment.getExternalStorageState();
715 }
716 if (retries == 0) {
717 Log.e(TAG, "Timed out waiting for media to check");
718 }
719 }
720
721 if (state.equals(Environment.MEDIA_MOUNTED)) {
722 /*
723 * If the media is mounted, then gracefully unmount it.
724 */
San Mehatb1043402010-02-05 08:26:50 -0800725 if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800726 Log.e(TAG, "Failed to unmount media for shutdown");
727 }
728 }
729 }
730
San Mehatb1043402010-02-05 08:26:50 -0800731 public boolean isUsbMassStorageConnected() {
San Mehat207e5382010-02-04 20:46:54 -0800732 waitForReady();
San Mehat91c77612010-01-07 10:39:41 -0800733
San Mehatb1043402010-02-05 08:26:50 -0800734 if (mUmsEnabling) {
735 return true;
San Mehat7fd0fee2009-12-17 07:12:23 -0800736 }
San Mehatb1043402010-02-05 08:26:50 -0800737 return doGetShareMethodAvailable("ums");
738 }
739
740 public int setUsbMassStorageEnabled(boolean enable) {
741 waitForReady();
742
743 return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable);
744 }
745
746 public boolean isUsbMassStorageEnabled() {
747 waitForReady();
748 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749 }
San Mehat4270e1e2010-01-29 05:32:19 -0800750
San Mehat7fd0fee2009-12-17 07:12:23 -0800751 /**
752 * @return state of the volume at the specified mount point
753 */
San Mehat4270e1e2010-01-29 05:32:19 -0800754 public String getVolumeState(String mountPoint) {
San Mehat7fd0fee2009-12-17 07:12:23 -0800755 /*
756 * XXX: Until we have multiple volume discovery, just hardwire
757 * this to /sdcard
758 */
759 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
760 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
761 throw new IllegalArgumentException();
762 }
763
764 return mLegacyState;
765 }
766
San Mehat4270e1e2010-01-29 05:32:19 -0800767 public int mountVolume(String path) {
768 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat4270e1e2010-01-29 05:32:19 -0800769
San Mehat207e5382010-02-04 20:46:54 -0800770 waitForReady();
771 return doMountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800772 }
773
San Mehat4270e1e2010-01-29 05:32:19 -0800774 public int unmountVolume(String path) {
775 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800776 waitForReady();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800777
San Mehat207e5382010-02-04 20:46:54 -0800778 return doUnmountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800779 }
780
San Mehat4270e1e2010-01-29 05:32:19 -0800781 public int formatVolume(String path) {
782 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800783 waitForReady();
San Mehat5b77dab2010-01-26 13:28:50 -0800784
San Mehat207e5382010-02-04 20:46:54 -0800785 return doFormatVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 }
787
San Mehatb1043402010-02-05 08:26:50 -0800788 private void warnOnNotMounted() {
789 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
790 Log.w(TAG, "getSecureContainerList() called when storage not mounted");
791 }
792 }
793
San Mehat4270e1e2010-01-29 05:32:19 -0800794 public String[] getSecureContainerList() {
795 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800796 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800797 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800798
San Mehat4270e1e2010-01-29 05:32:19 -0800799 try {
800 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
801 } catch (NativeDaemonConnectorException e) {
802 return new String[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800803 }
804 }
San Mehat36972292010-01-06 11:06:32 -0800805
San Mehat4270e1e2010-01-29 05:32:19 -0800806 public int createSecureContainer(String id, int sizeMb, String fstype,
807 String key, int ownerUid) {
808 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehat207e5382010-02-04 20:46:54 -0800809 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800810 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800811
San Mehatb1043402010-02-05 08:26:50 -0800812 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800813 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
814 try {
815 mConnector.doCommand(cmd);
816 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800817 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800818 }
San Mehat4270e1e2010-01-29 05:32:19 -0800819 return rc;
San Mehat36972292010-01-06 11:06:32 -0800820 }
821
San Mehat4270e1e2010-01-29 05:32:19 -0800822 public int finalizeSecureContainer(String id) {
823 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehatb1043402010-02-05 08:26:50 -0800824 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800825
San Mehatb1043402010-02-05 08:26:50 -0800826 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800827 try {
828 mConnector.doCommand(String.format("asec finalize %s", id));
829 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800830 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800831 }
San Mehat4270e1e2010-01-29 05:32:19 -0800832 return rc;
San Mehat36972292010-01-06 11:06:32 -0800833 }
834
San Mehat4270e1e2010-01-29 05:32:19 -0800835 public int destroySecureContainer(String id) {
836 validatePermission(android.Manifest.permission.ASEC_DESTROY);
San Mehat207e5382010-02-04 20:46:54 -0800837 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800838 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800839
San Mehatb1043402010-02-05 08:26:50 -0800840 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800841 try {
842 mConnector.doCommand(String.format("asec destroy %s", id));
843 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800844 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800845 }
San Mehat4270e1e2010-01-29 05:32:19 -0800846 return rc;
San Mehat36972292010-01-06 11:06:32 -0800847 }
848
San Mehat4270e1e2010-01-29 05:32:19 -0800849 public int mountSecureContainer(String id, String key, int ownerUid) {
850 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800851 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800852 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800853
San Mehatb1043402010-02-05 08:26:50 -0800854 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800855 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
856 try {
857 mConnector.doCommand(cmd);
858 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800859 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800860 }
San Mehat4270e1e2010-01-29 05:32:19 -0800861 return rc;
San Mehat36972292010-01-06 11:06:32 -0800862 }
863
San Mehat4270e1e2010-01-29 05:32:19 -0800864 public int unmountSecureContainer(String id) {
865 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800866 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800867 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800868
San Mehatb1043402010-02-05 08:26:50 -0800869 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800870 String cmd = String.format("asec unmount %s", id);
871 try {
872 mConnector.doCommand(cmd);
873 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800874 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800875 }
San Mehat4270e1e2010-01-29 05:32:19 -0800876 return rc;
San Mehat9dba7092010-01-18 06:47:41 -0800877 }
878
San Mehat4270e1e2010-01-29 05:32:19 -0800879 public int renameSecureContainer(String oldId, String newId) {
880 validatePermission(android.Manifest.permission.ASEC_RENAME);
San Mehat207e5382010-02-04 20:46:54 -0800881 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800882 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800883
San Mehatb1043402010-02-05 08:26:50 -0800884 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800885 String cmd = String.format("asec rename %s %s", oldId, newId);
886 try {
887 mConnector.doCommand(cmd);
888 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800889 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800890 }
San Mehat4270e1e2010-01-29 05:32:19 -0800891 return rc;
San Mehat45f61042010-01-23 08:12:43 -0800892 }
893
San Mehat4270e1e2010-01-29 05:32:19 -0800894 public String getSecureContainerPath(String id) {
895 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800896 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800897 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800898
San Mehat4270e1e2010-01-29 05:32:19 -0800899 ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
San Mehat36972292010-01-06 11:06:32 -0800900
San Mehat22dd86e2010-01-12 12:21:18 -0800901 for (String line : rsp) {
902 String []tok = line.split(" ");
903 int code = Integer.parseInt(tok[0]);
904 if (code == VoldResponseCode.AsecPathResult) {
905 return tok[1];
906 } else {
San Mehat4270e1e2010-01-29 05:32:19 -0800907 Log.e(TAG, String.format("Unexpected response code %d", code));
908 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800909 }
910 }
San Mehat4270e1e2010-01-29 05:32:19 -0800911
912 Log.e(TAG, "Got an empty response");
913 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800914 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915}
916