blob: 0e44858f299559602dbe51cfc6c3b4e218b12774 [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;
26import android.os.IMountService;
San Mehat4270e1e2010-01-29 05:32:19 -080027import android.os.IMountServiceListener;
28import android.os.MountServiceResultCode;
29import 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/**
45 * MountService implements an to the mount service daemon
46 * @hide
47 */
San Mehat22dd86e2010-01-12 12:21:18 -080048class MountService extends IMountService.Stub
49 implements INativeDaemonConnectorCallbacks {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
51 private static final String TAG = "MountService";
52
San Mehat4270e1e2010-01-29 05:32:19 -080053 /*
54 * Internal vold volume state constants
55 */
San Mehat7fd0fee2009-12-17 07:12:23 -080056 class VolumeState {
57 public static final int Init = -1;
58 public static final int NoMedia = 0;
59 public static final int Idle = 1;
60 public static final int Pending = 2;
61 public static final int Checking = 3;
62 public static final int Mounted = 4;
63 public static final int Unmounting = 5;
64 public static final int Formatting = 6;
65 public static final int Shared = 7;
66 public static final int SharedMnt = 8;
67 }
68
San Mehat4270e1e2010-01-29 05:32:19 -080069 /*
70 * Internal vold response code constants
71 */
San Mehat22dd86e2010-01-12 12:21:18 -080072 class VoldResponseCode {
San Mehat4270e1e2010-01-29 05:32:19 -080073 /*
74 * 100 series - Requestion action was initiated; expect another reply
75 * before proceeding with a new command.
76 */
San Mehat22dd86e2010-01-12 12:21:18 -080077 public static final int VolumeListResult = 110;
78 public static final int AsecListResult = 111;
79
San Mehat4270e1e2010-01-29 05:32:19 -080080 /*
81 * 200 series - Requestion action has been successfully completed.
82 */
83 public static final int ShareStatusResult = 210;
San Mehat22dd86e2010-01-12 12:21:18 -080084 public static final int AsecPathResult = 211;
San Mehat4270e1e2010-01-29 05:32:19 -080085 public static final int ShareEnabledResult = 212;
San Mehat22dd86e2010-01-12 12:21:18 -080086
San Mehat4270e1e2010-01-29 05:32:19 -080087 /*
88 * 400 series - Command was accepted, but the requested action
89 * did not take place.
90 */
91 public static final int OpFailedNoMedia = 401;
92 public static final int OpFailedMediaBlank = 402;
93 public static final int OpFailedMediaCorrupt = 403;
94 public static final int OpFailedVolNotMounted = 404;
95 public static final int OpFailedVolBusy = 405;
96
97 /*
98 * 600 series - Unsolicited broadcasts.
99 */
San Mehat22dd86e2010-01-12 12:21:18 -0800100 public static final int VolumeStateChange = 605;
San Mehat22dd86e2010-01-12 12:21:18 -0800101 public static final int ShareAvailabilityChange = 620;
102 public static final int VolumeDiskInserted = 630;
103 public static final int VolumeDiskRemoved = 631;
104 public static final int VolumeBadRemoval = 632;
105 }
106
San Mehat4270e1e2010-01-29 05:32:19 -0800107 private Context mContext;
108 private NativeDaemonConnector mConnector;
109 private String mLegacyState = Environment.MEDIA_REMOVED;
110 private PackageManagerService mPms;
111 private boolean mUmsEnabling;
112 private ArrayList<MountServiceBinderListener> mListeners;
San Mehat207e5382010-02-04 20:46:54 -0800113 private boolean mBooted;
114 private boolean mReady;
Suchi Amalapurapufd3530f2010-01-18 00:15:59 -0800115
San Mehat207e5382010-02-04 20:46:54 -0800116 private void waitForReady() {
117 while (mReady == false) {
118 for (int retries = 5; retries > 0; retries--) {
119 if (mReady) {
120 return;
121 }
122 SystemClock.sleep(1000);
123 }
124 Log.w(TAG, "Waiting too long for mReady!");
125 }
San Mehat1f6301e2010-01-07 22:40:27 -0800126 }
127
San Mehat207e5382010-02-04 20:46:54 -0800128 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 public void onReceive(Context context, Intent intent) {
San Mehat91c77612010-01-07 10:39:41 -0800130 String action = intent.getAction();
131
132 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
San Mehat207e5382010-02-04 20:46:54 -0800133 mBooted = true;
San Mehat22dd86e2010-01-12 12:21:18 -0800134
San Mehat207e5382010-02-04 20:46:54 -0800135 String path = Environment.getExternalStorageDirectory().getPath();
136 if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) {
137 int rc = doMountVolume(path);
138 if (rc != MountServiceResultCode.OperationSucceeded) {
139 Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
140 }
141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 }
143 }
144 };
145
San Mehat4270e1e2010-01-29 05:32:19 -0800146 private final class MountServiceBinderListener implements IBinder.DeathRecipient {
147 final IMountServiceListener mListener;
148
149 MountServiceBinderListener(IMountServiceListener listener) {
150 mListener = listener;
151
San Mehat91c77612010-01-07 10:39:41 -0800152 }
153
San Mehat4270e1e2010-01-29 05:32:19 -0800154 public void binderDied() {
155 Log.d(TAG, "An IMountServiceListener has died!");
156 synchronized(mListeners) {
157 mListeners.remove(this);
158 mListener.asBinder().unlinkToDeath(this, 0);
159 }
160 }
161 }
162
San Mehat207e5382010-02-04 20:46:54 -0800163 private int doShareUnshareVolume(String path, String method, boolean enable) {
San Mehat4270e1e2010-01-29 05:32:19 -0800164 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
165
166 // TODO: Add support for multiple share methods
167 if (!method.equals("ums")) {
168 throw new IllegalArgumentException(String.format("Method %s not supported", method));
169 }
170
171 /*
172 * If the volume is mounted and we're enabling then unmount it
173 */
174 String vs = getVolumeState(path);
175 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
176 mUmsEnabling = enable; // Supress unmounted events
San Mehat207e5382010-02-04 20:46:54 -0800177 doUnmountVolume(path);
San Mehat4270e1e2010-01-29 05:32:19 -0800178 mUmsEnabling = false; // Unsupress unmounted events
179 }
180
181 try {
182 mConnector.doCommand(String.format(
183 "volume %sshare %s %s", (enable ? "" : "un"), path, method));
184 } catch (NativeDaemonConnectorException e) {
185 Log.e(TAG, "Failed to share/unshare", e);
186 return MountServiceResultCode.OperationFailedInternalError;
187 }
188
189 /*
190 * If we disabled UMS then mount the volume
191 */
192 if (!enable) {
San Mehat207e5382010-02-04 20:46:54 -0800193 if (doMountVolume(path) != MountServiceResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800194 Log.e(TAG, String.format(
195 "Failed to remount %s after disabling share method %s", path, method));
196 /*
197 * Even though the mount failed, the unshare didn't so don't indicate an error.
198 * The mountVolume() call will have set the storage state and sent the necessary
199 * broadcasts.
200 */
201 }
202 }
203
204 return MountServiceResultCode.OperationSucceeded;
205 }
206
San Mehat207e5382010-02-04 20:46:54 -0800207 private void updatePublicVolumeState(String path, String state) {
San Mehat4270e1e2010-01-29 05:32:19 -0800208 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
209 Log.w(TAG, "Multiple volumes not currently supported");
210 return;
211 }
212 Log.i(TAG, "State for {" + path + "} = {" + state + "}");
213
214 String oldState = mLegacyState;
215 mLegacyState = state;
216
217 synchronized (mListeners) {
218 for (int i = mListeners.size() -1; i >= 0; i--) {
219 MountServiceBinderListener bl = mListeners.get(i);
220 try {
221 bl.mListener.onVolumeStateChanged("", path, oldState, state);
222 } catch (RemoteException rex) {
223 Log.e(TAG, "Listener dead");
224 mListeners.remove(i);
225 } catch (Exception ex) {
226 Log.e(TAG, "Listener failed", ex);
227 }
228 }
229 }
230 }
231
232 /**
233 *
234 * Callback from NativeDaemonConnector
235 */
236 public void onDaemonConnected() {
237 /*
238 * Since we'll be calling back into the NativeDaemonConnector,
239 * we need to do our work in a new thread.
240 */
241 new Thread() {
242 public void run() {
243 /**
244 * Determine media state and UMS detection status
245 */
246 String path = Environment.getExternalStorageDirectory().getPath();
247 String state = Environment.MEDIA_REMOVED;
248
249 try {
250 String[] vols = mConnector.doListCommand(
251 "volume list", VoldResponseCode.VolumeListResult);
252 for (String volstr : vols) {
253 String[] tok = volstr.split(" ");
254 // FMT: <label> <mountpoint> <state>
255 if (!tok[1].equals(path)) {
256 Log.w(TAG, String.format(
257 "Skipping unknown volume '%s'",tok[1]));
258 continue;
259 }
260 int st = Integer.parseInt(tok[2]);
261 if (st == VolumeState.NoMedia) {
262 state = Environment.MEDIA_REMOVED;
263 } else if (st == VolumeState.Idle) {
San Mehat207e5382010-02-04 20:46:54 -0800264 state = Environment.MEDIA_UNMOUNTED;
San Mehat4270e1e2010-01-29 05:32:19 -0800265 } else if (st == VolumeState.Mounted) {
266 state = Environment.MEDIA_MOUNTED;
267 Log.i(TAG, "Media already mounted on daemon connection");
268 } else if (st == VolumeState.Shared) {
269 state = Environment.MEDIA_SHARED;
270 Log.i(TAG, "Media shared on daemon connection");
271 } else {
272 throw new Exception(String.format("Unexpected state %d", st));
273 }
274 }
275 if (state != null) {
276 updatePublicVolumeState(path, state);
277 }
278 } catch (Exception e) {
279 Log.e(TAG, "Error processing initial volume state", e);
280 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
281 }
282
283 try {
San Mehat207e5382010-02-04 20:46:54 -0800284 boolean avail = doGetShareMethodAvailable("ums");
San Mehat4270e1e2010-01-29 05:32:19 -0800285 notifyShareAvailabilityChange("ums", avail);
286 } catch (Exception ex) {
287 Log.w(TAG, "Failed to get share availability");
288 }
San Mehat207e5382010-02-04 20:46:54 -0800289 /*
290 * Now that we've done our initialization, release
291 * the hounds!
292 */
293 mReady = true;
San Mehat4270e1e2010-01-29 05:32:19 -0800294 }
295 }.start();
296 }
297
298 /**
299 *
300 * Callback from NativeDaemonConnector
301 */
302 public boolean onEvent(int code, String raw, String[] cooked) {
303 Intent in = null;
304
305 // Log.d(TAG, "event {" + raw + "}");
306 if (code == VoldResponseCode.VolumeStateChange) {
307 /*
308 * One of the volumes we're managing has changed state.
309 * Format: "NNN Volume <label> <path> state changed
310 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
311 */
312 notifyVolumeStateChange(
313 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
314 Integer.parseInt(cooked[10]));
315 } else if (code == VoldResponseCode.ShareAvailabilityChange) {
316 // FMT: NNN Share method <method> now <available|unavailable>
317 boolean avail = false;
318 if (cooked[5].equals("available")) {
319 avail = true;
320 }
321 notifyShareAvailabilityChange(cooked[3], avail);
322 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
323 (code == VoldResponseCode.VolumeDiskRemoved) ||
324 (code == VoldResponseCode.VolumeBadRemoval)) {
325 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
326 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
327 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
328 final String label = cooked[2];
329 final String path = cooked[3];
330 int major = -1;
331 int minor = -1;
332
333 try {
334 String devComp = cooked[6].substring(1, cooked[6].length() -1);
335 String[] devTok = devComp.split(":");
336 major = Integer.parseInt(devTok[0]);
337 minor = Integer.parseInt(devTok[1]);
338 } catch (Exception ex) {
339 Log.e(TAG, "Failed to parse major/minor", ex);
340 }
341
342 synchronized (mListeners) {
343 for (int i = mListeners.size() -1; i >= 0; i--) {
344 MountServiceBinderListener bl = mListeners.get(i);
345 try {
346 if (code == VoldResponseCode.VolumeDiskInserted) {
347 bl.mListener.onMediaInserted(label, path, major, minor);
348 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
349 bl.mListener.onMediaRemoved(label, path, major, minor, true);
350 } else if (code == VoldResponseCode.VolumeBadRemoval) {
351 bl.mListener.onMediaRemoved(label, path, major, minor, false);
352 } else {
353 Log.e(TAG, String.format("Unknown code {%d}", code));
354 }
355 } catch (RemoteException rex) {
356 Log.e(TAG, "Listener dead");
357 mListeners.remove(i);
358 } catch (Exception ex) {
359 Log.e(TAG, "Listener failed", ex);
360 }
361 }
362 }
363
364 if (code == VoldResponseCode.VolumeDiskInserted) {
365 new Thread() {
366 public void run() {
367 try {
368 int rc;
San Mehat207e5382010-02-04 20:46:54 -0800369 if ((rc = doMountVolume(path)) != MountServiceResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800370 Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
371 }
372 } catch (Exception ex) {
373 Log.w(TAG, "Failed to mount media on insertion", ex);
374 }
375 }
376 }.start();
377 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
378 /*
379 * This event gets trumped if we're already in BAD_REMOVAL state
380 */
381 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
382 return true;
383 }
384 /* Send the media unmounted event first */
385 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
386 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
387 mContext.sendBroadcast(in);
388
389 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
390 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
391 } else if (code == VoldResponseCode.VolumeBadRemoval) {
392 /* Send the media unmounted event first */
393 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
394 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
395 mContext.sendBroadcast(in);
396
397 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
398 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
399 } else {
400 Log.e(TAG, String.format("Unknown code {%d}", code));
401 }
402 } else {
403 return false;
404 }
405
406 if (in != null) {
407 mContext.sendBroadcast(in);
408 }
409 return true;
410 }
411
San Mehat207e5382010-02-04 20:46:54 -0800412 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
San Mehat4270e1e2010-01-29 05:32:19 -0800413 String vs = getVolumeState(path);
414
415 Intent in = null;
416
417 if (newState == VolumeState.Init) {
418 } else if (newState == VolumeState.NoMedia) {
419 // NoMedia is handled via Disk Remove events
420 } else if (newState == VolumeState.Idle) {
421 /*
422 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
423 * if we're in the process of enabling UMS
424 */
425 if (!vs.equals(
426 Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
427 Environment.MEDIA_NOFS) && !vs.equals(
428 Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
429 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
430 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
431 }
432 } else if (newState == VolumeState.Pending) {
433 } else if (newState == VolumeState.Checking) {
434 updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
435 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
436 } else if (newState == VolumeState.Mounted) {
437 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
438 // Update media status on PackageManagerService to mount packages on sdcard
439 mPms.updateExternalMediaStatus(true);
440 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
441 in.putExtra("read-only", false);
442 } else if (newState == VolumeState.Unmounting) {
443 mPms.updateExternalMediaStatus(false);
444 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
445 } else if (newState == VolumeState.Formatting) {
446 } else if (newState == VolumeState.Shared) {
447 /* Send the media unmounted event first */
448 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
449 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
450 mContext.sendBroadcast(in);
451
452 updatePublicVolumeState(path, Environment.MEDIA_SHARED);
453 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
454 } else if (newState == VolumeState.SharedMnt) {
455 Log.e(TAG, "Live shared mounts not supported yet!");
456 return;
457 } else {
458 Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
459 }
460
461 if (in != null) {
462 mContext.sendBroadcast(in);
463 }
464 }
465
San Mehat207e5382010-02-04 20:46:54 -0800466 private boolean doGetShareMethodAvailable(String method) {
467 ArrayList<String> rsp = mConnector.doCommand("share status " + method);
468
469 for (String line : rsp) {
470 String []tok = line.split(" ");
471 int code;
472 try {
473 code = Integer.parseInt(tok[0]);
474 } catch (NumberFormatException nfe) {
475 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
476 return false;
477 }
478 if (code == VoldResponseCode.ShareStatusResult) {
479 if (tok[2].equals("available"))
480 return true;
481 return false;
482 } else {
483 Log.e(TAG, String.format("Unexpected response code %d", code));
484 return false;
485 }
486 }
487 Log.e(TAG, "Got an empty response");
488 return false;
489 }
490
491 private int doMountVolume(String path) {
492 int rc = MountServiceResultCode.OperationSucceeded;
493
494 try {
495 mConnector.doCommand(String.format("volume mount %s", path));
496 } catch (NativeDaemonConnectorException e) {
497 /*
498 * Mount failed for some reason
499 */
500 Intent in = null;
501 int code = e.getCode();
502 if (code == VoldResponseCode.OpFailedNoMedia) {
503 /*
504 * Attempt to mount but no media inserted
505 */
506 rc = MountServiceResultCode.OperationFailedNoMedia;
507 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
508 /*
509 * Media is blank or does not contain a supported filesystem
510 */
511 updatePublicVolumeState(path, Environment.MEDIA_NOFS);
512 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
513 rc = MountServiceResultCode.OperationFailedMediaBlank;
514 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
515 /*
516 * Volume consistency check failed
517 */
518 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
519 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
520 rc = MountServiceResultCode.OperationFailedMediaCorrupt;
521 } else {
522 rc = MountServiceResultCode.OperationFailedInternalError;
523 }
524
525 /*
526 * Send broadcast intent (if required for the failure)
527 */
528 if (in != null) {
529 mContext.sendBroadcast(in);
530 }
531 }
532
533 return rc;
534 }
535
536 private int doUnmountVolume(String path) {
537 if (getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
538 return VoldResponseCode.OpFailedVolNotMounted;
539 }
540
541 // Notify PackageManager of potential media removal and deal with
542 // return code later on. The caller of this api should be aware or have been
543 // notified that the applications installed on the media will be killed.
544 mPms.updateExternalMediaStatus(false);
545 try {
546 mConnector.doCommand(String.format("volume unmount %s", path));
547 return MountServiceResultCode.OperationSucceeded;
548 } catch (NativeDaemonConnectorException e) {
549 // Don't worry about mismatch in PackageManager since the
550 // call back will handle the status changes any way.
551 int code = e.getCode();
552 if (code == VoldResponseCode.OpFailedVolNotMounted) {
553 return MountServiceResultCode.OperationFailedVolumeNotMounted;
554 } else {
555 return MountServiceResultCode.OperationFailedInternalError;
556 }
557 }
558 }
559
560 private int doFormatVolume(String path) {
561 try {
562 String cmd = String.format("volume format %s", path);
563 mConnector.doCommand(cmd);
564 return MountServiceResultCode.OperationSucceeded;
565 } catch (NativeDaemonConnectorException e) {
566 int code = e.getCode();
567 if (code == VoldResponseCode.OpFailedNoMedia) {
568 return MountServiceResultCode.OperationFailedNoMedia;
569 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
570 return MountServiceResultCode.OperationFailedMediaCorrupt;
571 } else {
572 return MountServiceResultCode.OperationFailedInternalError;
573 }
574 }
575 }
576
577 private void notifyShareAvailabilityChange(String method, final boolean avail) {
San Mehat4270e1e2010-01-29 05:32:19 -0800578 if (!method.equals("ums")) {
579 Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
580 return;
581 }
582
583 synchronized (mListeners) {
584 for (int i = mListeners.size() -1; i >= 0; i--) {
585 MountServiceBinderListener bl = mListeners.get(i);
586 try {
587 bl.mListener.onShareAvailabilityChanged(method, avail);
588 } catch (RemoteException rex) {
589 Log.e(TAG, "Listener dead");
590 mListeners.remove(i);
591 } catch (Exception ex) {
592 Log.e(TAG, "Listener failed", ex);
593 }
594 }
595 }
596
San Mehat207e5382010-02-04 20:46:54 -0800597 if (mBooted == true) {
598 Intent intent;
599 if (avail) {
600 intent = new Intent(Intent.ACTION_UMS_CONNECTED);
601 } else {
602 intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
603 }
604 mContext.sendBroadcast(intent);
San Mehat4270e1e2010-01-29 05:32:19 -0800605 }
San Mehat4270e1e2010-01-29 05:32:19 -0800606 }
607
San Mehat207e5382010-02-04 20:46:54 -0800608 private void validatePermission(String perm) {
San Mehat4270e1e2010-01-29 05:32:19 -0800609 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
610 throw new SecurityException(String.format("Requires %s permission", perm));
611 }
612 }
613
614 /**
San Mehat207e5382010-02-04 20:46:54 -0800615 * Constructs a new MountService instance
616 *
617 * @param context Binder context for this service
618 */
619 public MountService(Context context) {
620 mContext = context;
621
622 /*
623 * Vold does not run in the simulator, so fake out a mounted
624 * event to trigger MediaScanner
625 */
626 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
627 updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
628 return;
629 }
630
631 // XXX: This will go away soon in favor of IMountServiceObserver
632 mPms = (PackageManagerService) ServiceManager.getService("package");
633
634 mContext.registerReceiver(mBroadcastReceiver,
635 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
636
637 mListeners = new ArrayList<MountServiceBinderListener>();
638
639 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
640 mReady = false;
641 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
642 thread.start();
643 }
644
645 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800646 * Exposed API calls below here
647 */
648
649 public void registerListener(IMountServiceListener listener) {
650 synchronized (mListeners) {
651 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
652 try {
653 listener.asBinder().linkToDeath(bl, 0);
654 mListeners.add(bl);
655 } catch (RemoteException rex) {
656 Log.e(TAG, "Failed to link to listener death");
657 }
658 }
659 }
660
661 public void unregisterListener(IMountServiceListener listener) {
662 synchronized (mListeners) {
663 for(MountServiceBinderListener bl : mListeners) {
664 if (bl.mListener == listener) {
665 mListeners.remove(mListeners.indexOf(bl));
666 return;
667 }
668 }
669 }
670 }
671
672 public void shutdown() {
673 validatePermission(android.Manifest.permission.SHUTDOWN);
674
675 Log.i(TAG, "Shutting down");
676
677 String path = Environment.getExternalStorageDirectory().getPath();
678 String state = getVolumeState(path);
San Mehat91c77612010-01-07 10:39:41 -0800679
680 if (state.equals(Environment.MEDIA_SHARED)) {
681 /*
682 * If the media is currently shared, unshare it.
683 * XXX: This is still dangerous!. We should not
684 * be rebooting at *all* if UMS is enabled, since
685 * the UMS host could have dirty FAT cache entries
686 * yet to flush.
687 */
San Mehat4270e1e2010-01-29 05:32:19 -0800688 if (unshareVolume(path, "ums") != MountServiceResultCode.OperationSucceeded) {
689 Log.e(TAG, "UMS disable on shutdown failed");
San Mehat91c77612010-01-07 10:39:41 -0800690 }
691 } else if (state.equals(Environment.MEDIA_CHECKING)) {
692 /*
693 * If the media is being checked, then we need to wait for
694 * it to complete before being able to proceed.
695 */
696 // XXX: @hackbod - Should we disable the ANR timer here?
697 int retries = 30;
698 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
699 try {
700 Thread.sleep(1000);
701 } catch (InterruptedException iex) {
702 Log.e(TAG, "Interrupted while waiting for media", iex);
703 break;
704 }
705 state = Environment.getExternalStorageState();
706 }
707 if (retries == 0) {
708 Log.e(TAG, "Timed out waiting for media to check");
709 }
710 }
711
712 if (state.equals(Environment.MEDIA_MOUNTED)) {
713 /*
714 * If the media is mounted, then gracefully unmount it.
715 */
San Mehat207e5382010-02-04 20:46:54 -0800716 if (doUnmountVolume(path) != MountServiceResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800717 Log.e(TAG, "Failed to unmount media for shutdown");
718 }
719 }
720 }
721
722 public String[] getShareMethodList() {
723 String[] rdata = new String[1];
724 rdata[0] = "ums";
725 return rdata;
726 }
727
728 public boolean getShareMethodAvailable(String method) {
San Mehat207e5382010-02-04 20:46:54 -0800729 waitForReady();
730 return doGetShareMethodAvailable(method);
San Mehat91c77612010-01-07 10:39:41 -0800731 }
732
San Mehat4270e1e2010-01-29 05:32:19 -0800733 public int shareVolume(String path, String method) {
San Mehat207e5382010-02-04 20:46:54 -0800734 waitForReady();
San Mehat4270e1e2010-01-29 05:32:19 -0800735 return doShareUnshareVolume(path, method, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 }
737
San Mehat4270e1e2010-01-29 05:32:19 -0800738 public int unshareVolume(String path, String method) {
San Mehat207e5382010-02-04 20:46:54 -0800739 waitForReady();
San Mehat4270e1e2010-01-29 05:32:19 -0800740 return doShareUnshareVolume(path, method, false);
741 }
San Mehat7fd0fee2009-12-17 07:12:23 -0800742
San Mehat4270e1e2010-01-29 05:32:19 -0800743 public boolean getVolumeShared(String path, String method) {
San Mehat207e5382010-02-04 20:46:54 -0800744 waitForReady();
San Mehat4270e1e2010-01-29 05:32:19 -0800745 String cmd = String.format("volume shared %s %s", path, method);
746 ArrayList<String> rsp = mConnector.doCommand(cmd);
747
748 for (String line : rsp) {
749 String []tok = line.split(" ");
750 int code;
751 try {
752 code = Integer.parseInt(tok[0]);
753 } catch (NumberFormatException nfe) {
754 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
755 return false;
San Mehat7fd0fee2009-12-17 07:12:23 -0800756 }
San Mehat4270e1e2010-01-29 05:32:19 -0800757 if (code == VoldResponseCode.ShareEnabledResult) {
758 if (tok[2].equals("enabled"))
759 return true;
760 return false;
761 } else {
762 Log.e(TAG, String.format("Unexpected response code %d", code));
763 return false;
San Mehat7fd0fee2009-12-17 07:12:23 -0800764 }
San Mehat7fd0fee2009-12-17 07:12:23 -0800765 }
San Mehat4270e1e2010-01-29 05:32:19 -0800766 Log.e(TAG, "Got an empty response");
767 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800768 }
San Mehat4270e1e2010-01-29 05:32:19 -0800769
San Mehat7fd0fee2009-12-17 07:12:23 -0800770 /**
771 * @return state of the volume at the specified mount point
772 */
San Mehat4270e1e2010-01-29 05:32:19 -0800773 public String getVolumeState(String mountPoint) {
San Mehat7fd0fee2009-12-17 07:12:23 -0800774 /*
775 * XXX: Until we have multiple volume discovery, just hardwire
776 * this to /sdcard
777 */
778 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
779 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
780 throw new IllegalArgumentException();
781 }
782
783 return mLegacyState;
784 }
785
San Mehat4270e1e2010-01-29 05:32:19 -0800786 public int mountVolume(String path) {
787 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat4270e1e2010-01-29 05:32:19 -0800788
San Mehat207e5382010-02-04 20:46:54 -0800789 waitForReady();
790 return doMountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 }
792
San Mehat4270e1e2010-01-29 05:32:19 -0800793 public int unmountVolume(String path) {
794 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800795 waitForReady();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796
San Mehat207e5382010-02-04 20:46:54 -0800797 return doUnmountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 }
799
San Mehat4270e1e2010-01-29 05:32:19 -0800800 public int formatVolume(String path) {
801 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800802 waitForReady();
San Mehat5b77dab2010-01-26 13:28:50 -0800803
San Mehat207e5382010-02-04 20:46:54 -0800804 return doFormatVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 }
806
San Mehat4270e1e2010-01-29 05:32:19 -0800807 public String[] getSecureContainerList() {
808 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800809 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800810 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
811 Log.w(TAG, "getSecureContainerList() called when storage not mounted");
812 }
813
San Mehat4270e1e2010-01-29 05:32:19 -0800814 try {
815 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
816 } catch (NativeDaemonConnectorException e) {
817 return new String[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 }
819 }
San Mehat36972292010-01-06 11:06:32 -0800820
San Mehat4270e1e2010-01-29 05:32:19 -0800821 public int createSecureContainer(String id, int sizeMb, String fstype,
822 String key, int ownerUid) {
823 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehat207e5382010-02-04 20:46:54 -0800824 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800825 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
826 Log.w(TAG, "createSecureContainer() called when storage not mounted");
827 }
San Mehat4270e1e2010-01-29 05:32:19 -0800828
829 int rc = MountServiceResultCode.OperationSucceeded;
830 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
831 try {
832 mConnector.doCommand(cmd);
833 } catch (NativeDaemonConnectorException e) {
834 rc = MountServiceResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800835 }
San Mehat4270e1e2010-01-29 05:32:19 -0800836 return rc;
San Mehat36972292010-01-06 11:06:32 -0800837 }
838
San Mehat4270e1e2010-01-29 05:32:19 -0800839 public int finalizeSecureContainer(String id) {
840 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehatf919cd022010-02-04 15:10:38 -0800841 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
842 Log.w(TAG, "finalizeSecureContainer() called when storage not mounted");
843 }
San Mehat4270e1e2010-01-29 05:32:19 -0800844
845 int rc = MountServiceResultCode.OperationSucceeded;
846 try {
847 mConnector.doCommand(String.format("asec finalize %s", id));
848 } catch (NativeDaemonConnectorException e) {
849 rc = MountServiceResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800850 }
San Mehat4270e1e2010-01-29 05:32:19 -0800851 return rc;
San Mehat36972292010-01-06 11:06:32 -0800852 }
853
San Mehat4270e1e2010-01-29 05:32:19 -0800854 public int destroySecureContainer(String id) {
855 validatePermission(android.Manifest.permission.ASEC_DESTROY);
San Mehat207e5382010-02-04 20:46:54 -0800856 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800857 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
858 Log.w(TAG, "destroySecureContainer() called when storage not mounted");
859 }
860
San Mehat4270e1e2010-01-29 05:32:19 -0800861 int rc = MountServiceResultCode.OperationSucceeded;
862 try {
863 mConnector.doCommand(String.format("asec destroy %s", id));
864 } catch (NativeDaemonConnectorException e) {
865 rc = MountServiceResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800866 }
San Mehat4270e1e2010-01-29 05:32:19 -0800867 return rc;
San Mehat36972292010-01-06 11:06:32 -0800868 }
869
San Mehat4270e1e2010-01-29 05:32:19 -0800870 public int mountSecureContainer(String id, String key, int ownerUid) {
871 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800872 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800873 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
874 Log.w(TAG, "mountSecureContainer() called when storage not mounted");
875 }
San Mehat4270e1e2010-01-29 05:32:19 -0800876
877 int rc = MountServiceResultCode.OperationSucceeded;
878 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
879 try {
880 mConnector.doCommand(cmd);
881 } catch (NativeDaemonConnectorException e) {
882 rc = MountServiceResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800883 }
San Mehat4270e1e2010-01-29 05:32:19 -0800884 return rc;
San Mehat36972292010-01-06 11:06:32 -0800885 }
886
San Mehat4270e1e2010-01-29 05:32:19 -0800887 public int unmountSecureContainer(String id) {
888 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800889 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800890 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
891 Log.w(TAG, "unmountSecureContainer() called when storage not mounted");
892 }
San Mehat4270e1e2010-01-29 05:32:19 -0800893
894 int rc = MountServiceResultCode.OperationSucceeded;
895 String cmd = String.format("asec unmount %s", id);
896 try {
897 mConnector.doCommand(cmd);
898 } catch (NativeDaemonConnectorException e) {
899 rc = MountServiceResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800900 }
San Mehat4270e1e2010-01-29 05:32:19 -0800901 return rc;
San Mehat9dba7092010-01-18 06:47:41 -0800902 }
903
San Mehat4270e1e2010-01-29 05:32:19 -0800904 public int renameSecureContainer(String oldId, String newId) {
905 validatePermission(android.Manifest.permission.ASEC_RENAME);
San Mehat207e5382010-02-04 20:46:54 -0800906 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800907 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
908 Log.w(TAG, "renameSecureContainer() called when storage not mounted");
909 }
San Mehat4270e1e2010-01-29 05:32:19 -0800910
911 int rc = MountServiceResultCode.OperationSucceeded;
912 String cmd = String.format("asec rename %s %s", oldId, newId);
913 try {
914 mConnector.doCommand(cmd);
915 } catch (NativeDaemonConnectorException e) {
916 rc = MountServiceResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800917 }
San Mehat4270e1e2010-01-29 05:32:19 -0800918 return rc;
San Mehat45f61042010-01-23 08:12:43 -0800919 }
920
San Mehat4270e1e2010-01-29 05:32:19 -0800921 public String getSecureContainerPath(String id) {
922 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800923 waitForReady();
San Mehatf919cd022010-02-04 15:10:38 -0800924 if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
925 Log.w(TAG, "getSecureContainerPath() called when storage not mounted");
926 }
927
San Mehat4270e1e2010-01-29 05:32:19 -0800928 ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
San Mehat36972292010-01-06 11:06:32 -0800929
San Mehat22dd86e2010-01-12 12:21:18 -0800930 for (String line : rsp) {
931 String []tok = line.split(" ");
932 int code = Integer.parseInt(tok[0]);
933 if (code == VoldResponseCode.AsecPathResult) {
934 return tok[1];
935 } else {
San Mehat4270e1e2010-01-29 05:32:19 -0800936 Log.e(TAG, String.format("Unexpected response code %d", code));
937 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800938 }
939 }
San Mehat4270e1e2010-01-29 05:32:19 -0800940
941 Log.e(TAG, "Got an empty response");
942 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800943 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944}
945