blob: 8d450337a13a3a2c864b50602ffed918e21037bc [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 Mehat59443a62010-02-09 13:28:45 -0800180 int rc = doUnmountVolume(path);
San Mehatb1043402010-02-05 08:26:50 -0800181 mUmsEnabling = false; // Clear override
San Mehat59443a62010-02-09 13:28:45 -0800182 if (rc != StorageResultCode.OperationSucceeded) {
183 Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc));
184 return rc;
185 }
San Mehat4270e1e2010-01-29 05:32:19 -0800186 }
187
188 try {
189 mConnector.doCommand(String.format(
190 "volume %sshare %s %s", (enable ? "" : "un"), path, method));
191 } catch (NativeDaemonConnectorException e) {
192 Log.e(TAG, "Failed to share/unshare", e);
San Mehatb1043402010-02-05 08:26:50 -0800193 return StorageResultCode.OperationFailedInternalError;
San Mehat4270e1e2010-01-29 05:32:19 -0800194 }
195
196 /*
197 * If we disabled UMS then mount the volume
198 */
199 if (!enable) {
San Mehatb1043402010-02-05 08:26:50 -0800200 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800201 Log.e(TAG, String.format(
202 "Failed to remount %s after disabling share method %s", path, method));
203 /*
204 * Even though the mount failed, the unshare didn't so don't indicate an error.
205 * The mountVolume() call will have set the storage state and sent the necessary
206 * broadcasts.
207 */
208 }
209 }
210
San Mehatb1043402010-02-05 08:26:50 -0800211 return StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800212 }
213
San Mehat207e5382010-02-04 20:46:54 -0800214 private void updatePublicVolumeState(String path, String state) {
San Mehat4270e1e2010-01-29 05:32:19 -0800215 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
216 Log.w(TAG, "Multiple volumes not currently supported");
217 return;
218 }
San Mehatb1043402010-02-05 08:26:50 -0800219
220 if (mLegacyState.equals(state)) {
221 Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
222 return;
223 }
San Mehat4270e1e2010-01-29 05:32:19 -0800224
225 String oldState = mLegacyState;
226 mLegacyState = state;
227
228 synchronized (mListeners) {
229 for (int i = mListeners.size() -1; i >= 0; i--) {
230 MountServiceBinderListener bl = mListeners.get(i);
231 try {
San Mehatb1043402010-02-05 08:26:50 -0800232 bl.mListener.onStorageStateChanged(path, oldState, state);
San Mehat4270e1e2010-01-29 05:32:19 -0800233 } catch (RemoteException rex) {
234 Log.e(TAG, "Listener dead");
235 mListeners.remove(i);
236 } catch (Exception ex) {
237 Log.e(TAG, "Listener failed", ex);
238 }
239 }
240 }
241 }
242
243 /**
244 *
245 * Callback from NativeDaemonConnector
246 */
247 public void onDaemonConnected() {
248 /*
249 * Since we'll be calling back into the NativeDaemonConnector,
250 * we need to do our work in a new thread.
251 */
252 new Thread() {
253 public void run() {
254 /**
255 * Determine media state and UMS detection status
256 */
257 String path = Environment.getExternalStorageDirectory().getPath();
258 String state = Environment.MEDIA_REMOVED;
259
260 try {
261 String[] vols = mConnector.doListCommand(
262 "volume list", VoldResponseCode.VolumeListResult);
263 for (String volstr : vols) {
264 String[] tok = volstr.split(" ");
265 // FMT: <label> <mountpoint> <state>
266 if (!tok[1].equals(path)) {
267 Log.w(TAG, String.format(
268 "Skipping unknown volume '%s'",tok[1]));
269 continue;
270 }
271 int st = Integer.parseInt(tok[2]);
272 if (st == VolumeState.NoMedia) {
273 state = Environment.MEDIA_REMOVED;
274 } else if (st == VolumeState.Idle) {
San Mehat207e5382010-02-04 20:46:54 -0800275 state = Environment.MEDIA_UNMOUNTED;
San Mehat4270e1e2010-01-29 05:32:19 -0800276 } else if (st == VolumeState.Mounted) {
277 state = Environment.MEDIA_MOUNTED;
278 Log.i(TAG, "Media already mounted on daemon connection");
279 } else if (st == VolumeState.Shared) {
280 state = Environment.MEDIA_SHARED;
281 Log.i(TAG, "Media shared on daemon connection");
282 } else {
283 throw new Exception(String.format("Unexpected state %d", st));
284 }
285 }
286 if (state != null) {
287 updatePublicVolumeState(path, state);
288 }
289 } catch (Exception e) {
290 Log.e(TAG, "Error processing initial volume state", e);
291 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
292 }
293
294 try {
San Mehat207e5382010-02-04 20:46:54 -0800295 boolean avail = doGetShareMethodAvailable("ums");
San Mehat4270e1e2010-01-29 05:32:19 -0800296 notifyShareAvailabilityChange("ums", avail);
297 } catch (Exception ex) {
298 Log.w(TAG, "Failed to get share availability");
299 }
San Mehat207e5382010-02-04 20:46:54 -0800300 /*
301 * Now that we've done our initialization, release
302 * the hounds!
303 */
304 mReady = true;
San Mehat4270e1e2010-01-29 05:32:19 -0800305 }
306 }.start();
307 }
308
309 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800310 * Callback from NativeDaemonConnector
311 */
312 public boolean onEvent(int code, String raw, String[] cooked) {
313 Intent in = null;
314
San Mehat4270e1e2010-01-29 05:32:19 -0800315 if (code == VoldResponseCode.VolumeStateChange) {
316 /*
317 * One of the volumes we're managing has changed state.
318 * Format: "NNN Volume <label> <path> state changed
319 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
320 */
321 notifyVolumeStateChange(
322 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
323 Integer.parseInt(cooked[10]));
324 } else if (code == VoldResponseCode.ShareAvailabilityChange) {
325 // FMT: NNN Share method <method> now <available|unavailable>
326 boolean avail = false;
327 if (cooked[5].equals("available")) {
328 avail = true;
329 }
330 notifyShareAvailabilityChange(cooked[3], avail);
331 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
332 (code == VoldResponseCode.VolumeDiskRemoved) ||
333 (code == VoldResponseCode.VolumeBadRemoval)) {
334 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
335 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
336 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
337 final String label = cooked[2];
338 final String path = cooked[3];
339 int major = -1;
340 int minor = -1;
341
342 try {
343 String devComp = cooked[6].substring(1, cooked[6].length() -1);
344 String[] devTok = devComp.split(":");
345 major = Integer.parseInt(devTok[0]);
346 minor = Integer.parseInt(devTok[1]);
347 } catch (Exception ex) {
348 Log.e(TAG, "Failed to parse major/minor", ex);
349 }
350
San Mehat4270e1e2010-01-29 05:32:19 -0800351 if (code == VoldResponseCode.VolumeDiskInserted) {
352 new Thread() {
353 public void run() {
354 try {
355 int rc;
San Mehatb1043402010-02-05 08:26:50 -0800356 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800357 Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
358 }
359 } catch (Exception ex) {
360 Log.w(TAG, "Failed to mount media on insertion", ex);
361 }
362 }
363 }.start();
364 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
365 /*
366 * This event gets trumped if we're already in BAD_REMOVAL state
367 */
368 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
369 return true;
370 }
371 /* Send the media unmounted event first */
372 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
373 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
374 mContext.sendBroadcast(in);
375
376 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
377 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
378 } else if (code == VoldResponseCode.VolumeBadRemoval) {
379 /* Send the media unmounted event first */
380 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
381 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
382 mContext.sendBroadcast(in);
383
384 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
385 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
386 } else {
387 Log.e(TAG, String.format("Unknown code {%d}", code));
388 }
389 } else {
390 return false;
391 }
392
393 if (in != null) {
394 mContext.sendBroadcast(in);
395 }
396 return true;
397 }
398
San Mehat207e5382010-02-04 20:46:54 -0800399 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
San Mehat4270e1e2010-01-29 05:32:19 -0800400 String vs = getVolumeState(path);
401
402 Intent in = null;
403
404 if (newState == VolumeState.Init) {
405 } else if (newState == VolumeState.NoMedia) {
406 // NoMedia is handled via Disk Remove events
407 } else if (newState == VolumeState.Idle) {
408 /*
409 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
410 * if we're in the process of enabling UMS
411 */
412 if (!vs.equals(
413 Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
414 Environment.MEDIA_NOFS) && !vs.equals(
415 Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
416 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
417 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
418 }
419 } else if (newState == VolumeState.Pending) {
420 } else if (newState == VolumeState.Checking) {
421 updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
422 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
423 } else if (newState == VolumeState.Mounted) {
424 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
425 // Update media status on PackageManagerService to mount packages on sdcard
426 mPms.updateExternalMediaStatus(true);
427 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
428 in.putExtra("read-only", false);
429 } else if (newState == VolumeState.Unmounting) {
430 mPms.updateExternalMediaStatus(false);
431 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
432 } else if (newState == VolumeState.Formatting) {
433 } else if (newState == VolumeState.Shared) {
434 /* Send the media unmounted event first */
435 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
436 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
437 mContext.sendBroadcast(in);
438
439 updatePublicVolumeState(path, Environment.MEDIA_SHARED);
440 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
441 } else if (newState == VolumeState.SharedMnt) {
442 Log.e(TAG, "Live shared mounts not supported yet!");
443 return;
444 } else {
445 Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
446 }
447
448 if (in != null) {
449 mContext.sendBroadcast(in);
450 }
451 }
452
San Mehat207e5382010-02-04 20:46:54 -0800453 private boolean doGetShareMethodAvailable(String method) {
454 ArrayList<String> rsp = mConnector.doCommand("share status " + method);
455
456 for (String line : rsp) {
457 String []tok = line.split(" ");
458 int code;
459 try {
460 code = Integer.parseInt(tok[0]);
461 } catch (NumberFormatException nfe) {
462 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
463 return false;
464 }
465 if (code == VoldResponseCode.ShareStatusResult) {
466 if (tok[2].equals("available"))
467 return true;
468 return false;
469 } else {
470 Log.e(TAG, String.format("Unexpected response code %d", code));
471 return false;
472 }
473 }
474 Log.e(TAG, "Got an empty response");
475 return false;
476 }
477
478 private int doMountVolume(String path) {
San Mehatb1043402010-02-05 08:26:50 -0800479 int rc = StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800480
481 try {
482 mConnector.doCommand(String.format("volume mount %s", path));
483 } catch (NativeDaemonConnectorException e) {
484 /*
485 * Mount failed for some reason
486 */
487 Intent in = null;
488 int code = e.getCode();
489 if (code == VoldResponseCode.OpFailedNoMedia) {
490 /*
491 * Attempt to mount but no media inserted
492 */
San Mehatb1043402010-02-05 08:26:50 -0800493 rc = StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800494 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
495 /*
496 * Media is blank or does not contain a supported filesystem
497 */
498 updatePublicVolumeState(path, Environment.MEDIA_NOFS);
499 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800500 rc = StorageResultCode.OperationFailedMediaBlank;
San Mehat207e5382010-02-04 20:46:54 -0800501 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
502 /*
503 * Volume consistency check failed
504 */
505 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
506 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800507 rc = StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800508 } else {
San Mehatb1043402010-02-05 08:26:50 -0800509 rc = StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800510 }
511
512 /*
513 * Send broadcast intent (if required for the failure)
514 */
515 if (in != null) {
516 mContext.sendBroadcast(in);
517 }
518 }
519
520 return rc;
521 }
522
523 private int doUnmountVolume(String path) {
San Mehat59443a62010-02-09 13:28:45 -0800524 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
San Mehat207e5382010-02-04 20:46:54 -0800525 return VoldResponseCode.OpFailedVolNotMounted;
526 }
527
528 // Notify PackageManager of potential media removal and deal with
529 // return code later on. The caller of this api should be aware or have been
530 // notified that the applications installed on the media will be killed.
531 mPms.updateExternalMediaStatus(false);
532 try {
533 mConnector.doCommand(String.format("volume unmount %s", path));
San Mehatb1043402010-02-05 08:26:50 -0800534 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800535 } catch (NativeDaemonConnectorException e) {
536 // Don't worry about mismatch in PackageManager since the
537 // call back will handle the status changes any way.
538 int code = e.getCode();
539 if (code == VoldResponseCode.OpFailedVolNotMounted) {
San Mehatb1043402010-02-05 08:26:50 -0800540 return StorageResultCode.OperationFailedVolumeNotMounted;
San Mehat207e5382010-02-04 20:46:54 -0800541 } else {
San Mehatb1043402010-02-05 08:26:50 -0800542 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800543 }
544 }
545 }
546
547 private int doFormatVolume(String path) {
548 try {
549 String cmd = String.format("volume format %s", path);
550 mConnector.doCommand(cmd);
San Mehatb1043402010-02-05 08:26:50 -0800551 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800552 } catch (NativeDaemonConnectorException e) {
553 int code = e.getCode();
554 if (code == VoldResponseCode.OpFailedNoMedia) {
San Mehatb1043402010-02-05 08:26:50 -0800555 return StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800556 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
San Mehatb1043402010-02-05 08:26:50 -0800557 return StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800558 } else {
San Mehatb1043402010-02-05 08:26:50 -0800559 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800560 }
561 }
562 }
563
San Mehatb1043402010-02-05 08:26:50 -0800564 private boolean doGetVolumeShared(String path, String method) {
565 String cmd = String.format("volume shared %s %s", path, method);
566 ArrayList<String> rsp = mConnector.doCommand(cmd);
567
568 for (String line : rsp) {
569 String []tok = line.split(" ");
570 int code;
571 try {
572 code = Integer.parseInt(tok[0]);
573 } catch (NumberFormatException nfe) {
574 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
575 return false;
576 }
577 if (code == VoldResponseCode.ShareEnabledResult) {
578 if (tok[2].equals("enabled"))
579 return true;
580 return false;
581 } else {
582 Log.e(TAG, String.format("Unexpected response code %d", code));
583 return false;
584 }
585 }
586 Log.e(TAG, "Got an empty response");
587 return false;
588 }
589
San Mehat207e5382010-02-04 20:46:54 -0800590 private void notifyShareAvailabilityChange(String method, final boolean avail) {
San Mehat4270e1e2010-01-29 05:32:19 -0800591 if (!method.equals("ums")) {
592 Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
593 return;
594 }
595
596 synchronized (mListeners) {
597 for (int i = mListeners.size() -1; i >= 0; i--) {
598 MountServiceBinderListener bl = mListeners.get(i);
599 try {
San Mehatb1043402010-02-05 08:26:50 -0800600 bl.mListener.onUsbMassStorageConnectionChanged(avail);
San Mehat4270e1e2010-01-29 05:32:19 -0800601 } catch (RemoteException rex) {
602 Log.e(TAG, "Listener dead");
603 mListeners.remove(i);
604 } catch (Exception ex) {
605 Log.e(TAG, "Listener failed", ex);
606 }
607 }
608 }
609
San Mehat207e5382010-02-04 20:46:54 -0800610 if (mBooted == true) {
611 Intent intent;
612 if (avail) {
613 intent = new Intent(Intent.ACTION_UMS_CONNECTED);
614 } else {
615 intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
616 }
617 mContext.sendBroadcast(intent);
San Mehat4270e1e2010-01-29 05:32:19 -0800618 }
San Mehat4270e1e2010-01-29 05:32:19 -0800619 }
620
San Mehat207e5382010-02-04 20:46:54 -0800621 private void validatePermission(String perm) {
San Mehat4270e1e2010-01-29 05:32:19 -0800622 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
623 throw new SecurityException(String.format("Requires %s permission", perm));
624 }
625 }
626
627 /**
San Mehat207e5382010-02-04 20:46:54 -0800628 * Constructs a new MountService instance
629 *
630 * @param context Binder context for this service
631 */
632 public MountService(Context context) {
633 mContext = context;
634
635 /*
636 * Vold does not run in the simulator, so fake out a mounted
637 * event to trigger MediaScanner
638 */
639 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
640 updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
641 return;
642 }
643
644 // XXX: This will go away soon in favor of IMountServiceObserver
645 mPms = (PackageManagerService) ServiceManager.getService("package");
646
647 mContext.registerReceiver(mBroadcastReceiver,
648 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
649
650 mListeners = new ArrayList<MountServiceBinderListener>();
651
652 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
653 mReady = false;
654 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
655 thread.start();
656 }
657
658 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800659 * Exposed API calls below here
660 */
661
662 public void registerListener(IMountServiceListener listener) {
663 synchronized (mListeners) {
664 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
665 try {
666 listener.asBinder().linkToDeath(bl, 0);
667 mListeners.add(bl);
668 } catch (RemoteException rex) {
669 Log.e(TAG, "Failed to link to listener death");
670 }
671 }
672 }
673
674 public void unregisterListener(IMountServiceListener listener) {
675 synchronized (mListeners) {
676 for(MountServiceBinderListener bl : mListeners) {
677 if (bl.mListener == listener) {
678 mListeners.remove(mListeners.indexOf(bl));
679 return;
680 }
681 }
682 }
683 }
684
685 public void shutdown() {
686 validatePermission(android.Manifest.permission.SHUTDOWN);
687
688 Log.i(TAG, "Shutting down");
689
690 String path = Environment.getExternalStorageDirectory().getPath();
691 String state = getVolumeState(path);
San Mehat91c77612010-01-07 10:39:41 -0800692
693 if (state.equals(Environment.MEDIA_SHARED)) {
694 /*
695 * If the media is currently shared, unshare it.
696 * XXX: This is still dangerous!. We should not
697 * be rebooting at *all* if UMS is enabled, since
698 * the UMS host could have dirty FAT cache entries
699 * yet to flush.
700 */
San Mehatb1043402010-02-05 08:26:50 -0800701 if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800702 Log.e(TAG, "UMS disable on shutdown failed");
San Mehat91c77612010-01-07 10:39:41 -0800703 }
704 } else if (state.equals(Environment.MEDIA_CHECKING)) {
705 /*
706 * If the media is being checked, then we need to wait for
707 * it to complete before being able to proceed.
708 */
709 // XXX: @hackbod - Should we disable the ANR timer here?
710 int retries = 30;
711 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
712 try {
713 Thread.sleep(1000);
714 } catch (InterruptedException iex) {
715 Log.e(TAG, "Interrupted while waiting for media", iex);
716 break;
717 }
718 state = Environment.getExternalStorageState();
719 }
720 if (retries == 0) {
721 Log.e(TAG, "Timed out waiting for media to check");
722 }
723 }
724
725 if (state.equals(Environment.MEDIA_MOUNTED)) {
726 /*
727 * If the media is mounted, then gracefully unmount it.
728 */
San Mehatb1043402010-02-05 08:26:50 -0800729 if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800730 Log.e(TAG, "Failed to unmount media for shutdown");
731 }
732 }
733 }
734
San Mehatb1043402010-02-05 08:26:50 -0800735 public boolean isUsbMassStorageConnected() {
San Mehat207e5382010-02-04 20:46:54 -0800736 waitForReady();
San Mehat91c77612010-01-07 10:39:41 -0800737
San Mehatb1043402010-02-05 08:26:50 -0800738 if (mUmsEnabling) {
739 return true;
San Mehat7fd0fee2009-12-17 07:12:23 -0800740 }
San Mehatb1043402010-02-05 08:26:50 -0800741 return doGetShareMethodAvailable("ums");
742 }
743
744 public int setUsbMassStorageEnabled(boolean enable) {
745 waitForReady();
746
747 return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable);
748 }
749
750 public boolean isUsbMassStorageEnabled() {
751 waitForReady();
752 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 }
San Mehat4270e1e2010-01-29 05:32:19 -0800754
San Mehat7fd0fee2009-12-17 07:12:23 -0800755 /**
756 * @return state of the volume at the specified mount point
757 */
San Mehat4270e1e2010-01-29 05:32:19 -0800758 public String getVolumeState(String mountPoint) {
San Mehat7fd0fee2009-12-17 07:12:23 -0800759 /*
760 * XXX: Until we have multiple volume discovery, just hardwire
761 * this to /sdcard
762 */
763 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
764 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
765 throw new IllegalArgumentException();
766 }
767
768 return mLegacyState;
769 }
770
San Mehat4270e1e2010-01-29 05:32:19 -0800771 public int mountVolume(String path) {
772 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat4270e1e2010-01-29 05:32:19 -0800773
San Mehat207e5382010-02-04 20:46:54 -0800774 waitForReady();
775 return doMountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800776 }
777
San Mehat4270e1e2010-01-29 05:32:19 -0800778 public int unmountVolume(String path) {
779 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800780 waitForReady();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781
San Mehat207e5382010-02-04 20:46:54 -0800782 return doUnmountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784
San Mehat4270e1e2010-01-29 05:32:19 -0800785 public int formatVolume(String path) {
786 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800787 waitForReady();
San Mehat5b77dab2010-01-26 13:28:50 -0800788
San Mehat207e5382010-02-04 20:46:54 -0800789 return doFormatVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 }
791
San Mehatb1043402010-02-05 08:26:50 -0800792 private void warnOnNotMounted() {
793 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
794 Log.w(TAG, "getSecureContainerList() called when storage not mounted");
795 }
796 }
797
San Mehat4270e1e2010-01-29 05:32:19 -0800798 public String[] getSecureContainerList() {
799 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800800 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800801 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800802
San Mehat4270e1e2010-01-29 05:32:19 -0800803 try {
804 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
805 } catch (NativeDaemonConnectorException e) {
806 return new String[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 }
808 }
San Mehat36972292010-01-06 11:06:32 -0800809
San Mehat4270e1e2010-01-29 05:32:19 -0800810 public int createSecureContainer(String id, int sizeMb, String fstype,
811 String key, int ownerUid) {
812 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehat207e5382010-02-04 20:46:54 -0800813 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800814 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800815
San Mehatb1043402010-02-05 08:26:50 -0800816 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800817 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
818 try {
819 mConnector.doCommand(cmd);
820 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800821 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800822 }
San Mehat4270e1e2010-01-29 05:32:19 -0800823 return rc;
San Mehat36972292010-01-06 11:06:32 -0800824 }
825
San Mehat4270e1e2010-01-29 05:32:19 -0800826 public int finalizeSecureContainer(String id) {
827 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehatb1043402010-02-05 08:26:50 -0800828 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800829
San Mehatb1043402010-02-05 08:26:50 -0800830 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800831 try {
832 mConnector.doCommand(String.format("asec finalize %s", id));
833 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800834 rc = StorageResultCode.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 destroySecureContainer(String id) {
840 validatePermission(android.Manifest.permission.ASEC_DESTROY);
San Mehat207e5382010-02-04 20:46:54 -0800841 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800842 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800843
San Mehatb1043402010-02-05 08:26:50 -0800844 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800845 try {
846 mConnector.doCommand(String.format("asec destroy %s", id));
847 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800848 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800849 }
San Mehat4270e1e2010-01-29 05:32:19 -0800850 return rc;
San Mehat36972292010-01-06 11:06:32 -0800851 }
852
San Mehat4270e1e2010-01-29 05:32:19 -0800853 public int mountSecureContainer(String id, String key, int ownerUid) {
854 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800855 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800856 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800857
San Mehatb1043402010-02-05 08:26:50 -0800858 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800859 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
860 try {
861 mConnector.doCommand(cmd);
862 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800863 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800864 }
San Mehat4270e1e2010-01-29 05:32:19 -0800865 return rc;
San Mehat36972292010-01-06 11:06:32 -0800866 }
867
San Mehat4270e1e2010-01-29 05:32:19 -0800868 public int unmountSecureContainer(String id) {
869 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800870 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800871 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800872
San Mehatb1043402010-02-05 08:26:50 -0800873 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800874 String cmd = String.format("asec unmount %s", id);
875 try {
876 mConnector.doCommand(cmd);
877 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800878 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800879 }
San Mehat4270e1e2010-01-29 05:32:19 -0800880 return rc;
San Mehat9dba7092010-01-18 06:47:41 -0800881 }
882
San Mehat4270e1e2010-01-29 05:32:19 -0800883 public int renameSecureContainer(String oldId, String newId) {
884 validatePermission(android.Manifest.permission.ASEC_RENAME);
San Mehat207e5382010-02-04 20:46:54 -0800885 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800886 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800887
San Mehatb1043402010-02-05 08:26:50 -0800888 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800889 String cmd = String.format("asec rename %s %s", oldId, newId);
890 try {
891 mConnector.doCommand(cmd);
892 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800893 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800894 }
San Mehat4270e1e2010-01-29 05:32:19 -0800895 return rc;
San Mehat45f61042010-01-23 08:12:43 -0800896 }
897
San Mehat4270e1e2010-01-29 05:32:19 -0800898 public String getSecureContainerPath(String id) {
899 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800900 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800901 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800902
San Mehat4270e1e2010-01-29 05:32:19 -0800903 ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
San Mehat36972292010-01-06 11:06:32 -0800904
San Mehat22dd86e2010-01-12 12:21:18 -0800905 for (String line : rsp) {
906 String []tok = line.split(" ");
907 int code = Integer.parseInt(tok[0]);
908 if (code == VoldResponseCode.AsecPathResult) {
909 return tok[1];
910 } else {
San Mehat4270e1e2010-01-29 05:32:19 -0800911 Log.e(TAG, String.format("Unexpected response code %d", code));
912 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800913 }
914 }
San Mehat4270e1e2010-01-29 05:32:19 -0800915
916 Log.e(TAG, "Got an empty response");
917 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800918 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919}
920