blob: e764bbbe7af0e614e4c946cbb4d0c88c2338a7e3 [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;
San Mehat6cdd9c02010-02-09 14:45:20 -080040import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
42import java.io.File;
43import java.io.FileReader;
44
45/**
San Mehatb1043402010-02-05 08:26:50 -080046 * MountService implements back-end services for platform storage
47 * management.
48 * @hide - Applications should use android.os.storage.StorageManager
49 * to access the MountService.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 */
San Mehat22dd86e2010-01-12 12:21:18 -080051class MountService extends IMountService.Stub
52 implements INativeDaemonConnectorCallbacks {
San Mehatb1043402010-02-05 08:26:50 -080053 private static final boolean LOCAL_LOGD = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054
55 private static final String TAG = "MountService";
56
San Mehat4270e1e2010-01-29 05:32:19 -080057 /*
58 * Internal vold volume state constants
59 */
San Mehat7fd0fee2009-12-17 07:12:23 -080060 class VolumeState {
61 public static final int Init = -1;
62 public static final int NoMedia = 0;
63 public static final int Idle = 1;
64 public static final int Pending = 2;
65 public static final int Checking = 3;
66 public static final int Mounted = 4;
67 public static final int Unmounting = 5;
68 public static final int Formatting = 6;
69 public static final int Shared = 7;
70 public static final int SharedMnt = 8;
71 }
72
San Mehat4270e1e2010-01-29 05:32:19 -080073 /*
74 * Internal vold response code constants
75 */
San Mehat22dd86e2010-01-12 12:21:18 -080076 class VoldResponseCode {
San Mehat4270e1e2010-01-29 05:32:19 -080077 /*
78 * 100 series - Requestion action was initiated; expect another reply
79 * before proceeding with a new command.
80 */
San Mehat22dd86e2010-01-12 12:21:18 -080081 public static final int VolumeListResult = 110;
82 public static final int AsecListResult = 111;
83
San Mehat4270e1e2010-01-29 05:32:19 -080084 /*
85 * 200 series - Requestion action has been successfully completed.
86 */
87 public static final int ShareStatusResult = 210;
San Mehat22dd86e2010-01-12 12:21:18 -080088 public static final int AsecPathResult = 211;
San Mehat4270e1e2010-01-29 05:32:19 -080089 public static final int ShareEnabledResult = 212;
San Mehat22dd86e2010-01-12 12:21:18 -080090
San Mehat4270e1e2010-01-29 05:32:19 -080091 /*
92 * 400 series - Command was accepted, but the requested action
93 * did not take place.
94 */
95 public static final int OpFailedNoMedia = 401;
96 public static final int OpFailedMediaBlank = 402;
97 public static final int OpFailedMediaCorrupt = 403;
98 public static final int OpFailedVolNotMounted = 404;
99 public static final int OpFailedVolBusy = 405;
100
101 /*
102 * 600 series - Unsolicited broadcasts.
103 */
San Mehat22dd86e2010-01-12 12:21:18 -0800104 public static final int VolumeStateChange = 605;
San Mehat22dd86e2010-01-12 12:21:18 -0800105 public static final int ShareAvailabilityChange = 620;
106 public static final int VolumeDiskInserted = 630;
107 public static final int VolumeDiskRemoved = 631;
108 public static final int VolumeBadRemoval = 632;
109 }
110
San Mehat4270e1e2010-01-29 05:32:19 -0800111 private Context mContext;
112 private NativeDaemonConnector mConnector;
113 private String mLegacyState = Environment.MEDIA_REMOVED;
114 private PackageManagerService mPms;
115 private boolean mUmsEnabling;
116 private ArrayList<MountServiceBinderListener> mListeners;
San Mehat207e5382010-02-04 20:46:54 -0800117 private boolean mBooted;
118 private boolean mReady;
Suchi Amalapurapufd3530f2010-01-18 00:15:59 -0800119
San Mehat6cdd9c02010-02-09 14:45:20 -0800120 /**
121 * Private hash of currently mounted secure containers.
122 */
123 private HashSet<String> mAsecMountSet = new HashSet<String>();
124
San Mehat207e5382010-02-04 20:46:54 -0800125 private void waitForReady() {
126 while (mReady == false) {
127 for (int retries = 5; retries > 0; retries--) {
128 if (mReady) {
129 return;
130 }
131 SystemClock.sleep(1000);
132 }
133 Log.w(TAG, "Waiting too long for mReady!");
134 }
San Mehat1f6301e2010-01-07 22:40:27 -0800135 }
136
San Mehat207e5382010-02-04 20:46:54 -0800137 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 public void onReceive(Context context, Intent intent) {
San Mehat91c77612010-01-07 10:39:41 -0800139 String action = intent.getAction();
140
141 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
San Mehat207e5382010-02-04 20:46:54 -0800142 mBooted = true;
San Mehat22dd86e2010-01-12 12:21:18 -0800143
San Mehat207e5382010-02-04 20:46:54 -0800144 String path = Environment.getExternalStorageDirectory().getPath();
145 if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) {
146 int rc = doMountVolume(path);
San Mehatb1043402010-02-05 08:26:50 -0800147 if (rc != StorageResultCode.OperationSucceeded) {
San Mehat207e5382010-02-04 20:46:54 -0800148 Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
149 }
150 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 }
152 }
153 };
154
San Mehat4270e1e2010-01-29 05:32:19 -0800155 private final class MountServiceBinderListener implements IBinder.DeathRecipient {
156 final IMountServiceListener mListener;
157
158 MountServiceBinderListener(IMountServiceListener listener) {
159 mListener = listener;
160
San Mehat91c77612010-01-07 10:39:41 -0800161 }
162
San Mehat4270e1e2010-01-29 05:32:19 -0800163 public void binderDied() {
San Mehatb1043402010-02-05 08:26:50 -0800164 if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
San Mehat4270e1e2010-01-29 05:32:19 -0800165 synchronized(mListeners) {
166 mListeners.remove(this);
167 mListener.asBinder().unlinkToDeath(this, 0);
168 }
169 }
170 }
171
San Mehat207e5382010-02-04 20:46:54 -0800172 private int doShareUnshareVolume(String path, String method, boolean enable) {
San Mehat4270e1e2010-01-29 05:32:19 -0800173 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
174
175 // TODO: Add support for multiple share methods
176 if (!method.equals("ums")) {
177 throw new IllegalArgumentException(String.format("Method %s not supported", method));
178 }
179
180 /*
181 * If the volume is mounted and we're enabling then unmount it
182 */
183 String vs = getVolumeState(path);
184 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
San Mehatb1043402010-02-05 08:26:50 -0800185 mUmsEnabling = enable; // Override for isUsbMassStorageEnabled()
San Mehat59443a62010-02-09 13:28:45 -0800186 int rc = doUnmountVolume(path);
San Mehatb1043402010-02-05 08:26:50 -0800187 mUmsEnabling = false; // Clear override
San Mehat59443a62010-02-09 13:28:45 -0800188 if (rc != StorageResultCode.OperationSucceeded) {
189 Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc));
190 return rc;
191 }
San Mehat4270e1e2010-01-29 05:32:19 -0800192 }
193
194 try {
195 mConnector.doCommand(String.format(
196 "volume %sshare %s %s", (enable ? "" : "un"), path, method));
197 } catch (NativeDaemonConnectorException e) {
198 Log.e(TAG, "Failed to share/unshare", e);
San Mehatb1043402010-02-05 08:26:50 -0800199 return StorageResultCode.OperationFailedInternalError;
San Mehat4270e1e2010-01-29 05:32:19 -0800200 }
201
202 /*
203 * If we disabled UMS then mount the volume
204 */
205 if (!enable) {
San Mehatb1043402010-02-05 08:26:50 -0800206 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800207 Log.e(TAG, String.format(
208 "Failed to remount %s after disabling share method %s", path, method));
209 /*
210 * Even though the mount failed, the unshare didn't so don't indicate an error.
211 * The mountVolume() call will have set the storage state and sent the necessary
212 * broadcasts.
213 */
214 }
215 }
216
San Mehatb1043402010-02-05 08:26:50 -0800217 return StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800218 }
219
San Mehat207e5382010-02-04 20:46:54 -0800220 private void updatePublicVolumeState(String path, String state) {
San Mehat4270e1e2010-01-29 05:32:19 -0800221 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
222 Log.w(TAG, "Multiple volumes not currently supported");
223 return;
224 }
San Mehatb1043402010-02-05 08:26:50 -0800225
226 if (mLegacyState.equals(state)) {
227 Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
228 return;
229 }
San Mehat4270e1e2010-01-29 05:32:19 -0800230
231 String oldState = mLegacyState;
232 mLegacyState = state;
233
234 synchronized (mListeners) {
235 for (int i = mListeners.size() -1; i >= 0; i--) {
236 MountServiceBinderListener bl = mListeners.get(i);
237 try {
San Mehatb1043402010-02-05 08:26:50 -0800238 bl.mListener.onStorageStateChanged(path, oldState, state);
San Mehat4270e1e2010-01-29 05:32:19 -0800239 } catch (RemoteException rex) {
240 Log.e(TAG, "Listener dead");
241 mListeners.remove(i);
242 } catch (Exception ex) {
243 Log.e(TAG, "Listener failed", ex);
244 }
245 }
246 }
247 }
248
249 /**
250 *
251 * Callback from NativeDaemonConnector
252 */
253 public void onDaemonConnected() {
254 /*
255 * Since we'll be calling back into the NativeDaemonConnector,
256 * we need to do our work in a new thread.
257 */
258 new Thread() {
259 public void run() {
260 /**
261 * Determine media state and UMS detection status
262 */
263 String path = Environment.getExternalStorageDirectory().getPath();
264 String state = Environment.MEDIA_REMOVED;
265
266 try {
267 String[] vols = mConnector.doListCommand(
268 "volume list", VoldResponseCode.VolumeListResult);
269 for (String volstr : vols) {
270 String[] tok = volstr.split(" ");
271 // FMT: <label> <mountpoint> <state>
272 if (!tok[1].equals(path)) {
273 Log.w(TAG, String.format(
274 "Skipping unknown volume '%s'",tok[1]));
275 continue;
276 }
277 int st = Integer.parseInt(tok[2]);
278 if (st == VolumeState.NoMedia) {
279 state = Environment.MEDIA_REMOVED;
280 } else if (st == VolumeState.Idle) {
San Mehat207e5382010-02-04 20:46:54 -0800281 state = Environment.MEDIA_UNMOUNTED;
San Mehat4270e1e2010-01-29 05:32:19 -0800282 } else if (st == VolumeState.Mounted) {
283 state = Environment.MEDIA_MOUNTED;
284 Log.i(TAG, "Media already mounted on daemon connection");
285 } else if (st == VolumeState.Shared) {
286 state = Environment.MEDIA_SHARED;
287 Log.i(TAG, "Media shared on daemon connection");
288 } else {
289 throw new Exception(String.format("Unexpected state %d", st));
290 }
291 }
292 if (state != null) {
293 updatePublicVolumeState(path, state);
294 }
295 } catch (Exception e) {
296 Log.e(TAG, "Error processing initial volume state", e);
297 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
298 }
299
300 try {
San Mehat207e5382010-02-04 20:46:54 -0800301 boolean avail = doGetShareMethodAvailable("ums");
San Mehat4270e1e2010-01-29 05:32:19 -0800302 notifyShareAvailabilityChange("ums", avail);
303 } catch (Exception ex) {
304 Log.w(TAG, "Failed to get share availability");
305 }
San Mehat207e5382010-02-04 20:46:54 -0800306 /*
307 * Now that we've done our initialization, release
308 * the hounds!
309 */
310 mReady = true;
San Mehat4270e1e2010-01-29 05:32:19 -0800311 }
312 }.start();
313 }
314
315 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800316 * Callback from NativeDaemonConnector
317 */
318 public boolean onEvent(int code, String raw, String[] cooked) {
319 Intent in = null;
320
San Mehat4270e1e2010-01-29 05:32:19 -0800321 if (code == VoldResponseCode.VolumeStateChange) {
322 /*
323 * One of the volumes we're managing has changed state.
324 * Format: "NNN Volume <label> <path> state changed
325 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
326 */
327 notifyVolumeStateChange(
328 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
329 Integer.parseInt(cooked[10]));
330 } else if (code == VoldResponseCode.ShareAvailabilityChange) {
331 // FMT: NNN Share method <method> now <available|unavailable>
332 boolean avail = false;
333 if (cooked[5].equals("available")) {
334 avail = true;
335 }
336 notifyShareAvailabilityChange(cooked[3], avail);
337 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
338 (code == VoldResponseCode.VolumeDiskRemoved) ||
339 (code == VoldResponseCode.VolumeBadRemoval)) {
340 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
341 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
342 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
343 final String label = cooked[2];
344 final String path = cooked[3];
345 int major = -1;
346 int minor = -1;
347
348 try {
349 String devComp = cooked[6].substring(1, cooked[6].length() -1);
350 String[] devTok = devComp.split(":");
351 major = Integer.parseInt(devTok[0]);
352 minor = Integer.parseInt(devTok[1]);
353 } catch (Exception ex) {
354 Log.e(TAG, "Failed to parse major/minor", ex);
355 }
356
San Mehat4270e1e2010-01-29 05:32:19 -0800357 if (code == VoldResponseCode.VolumeDiskInserted) {
358 new Thread() {
359 public void run() {
360 try {
361 int rc;
San Mehatb1043402010-02-05 08:26:50 -0800362 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800363 Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
364 }
365 } catch (Exception ex) {
366 Log.w(TAG, "Failed to mount media on insertion", ex);
367 }
368 }
369 }.start();
370 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
371 /*
372 * This event gets trumped if we're already in BAD_REMOVAL state
373 */
374 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
375 return true;
376 }
377 /* Send the media unmounted event first */
378 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
379 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
380 mContext.sendBroadcast(in);
381
382 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
383 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
384 } else if (code == VoldResponseCode.VolumeBadRemoval) {
385 /* Send the media unmounted event first */
386 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
387 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
388 mContext.sendBroadcast(in);
389
390 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
391 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
392 } else {
393 Log.e(TAG, String.format("Unknown code {%d}", code));
394 }
395 } else {
396 return false;
397 }
398
399 if (in != null) {
400 mContext.sendBroadcast(in);
401 }
402 return true;
403 }
404
San Mehat207e5382010-02-04 20:46:54 -0800405 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
San Mehat4270e1e2010-01-29 05:32:19 -0800406 String vs = getVolumeState(path);
407
408 Intent in = null;
409
410 if (newState == VolumeState.Init) {
411 } else if (newState == VolumeState.NoMedia) {
412 // NoMedia is handled via Disk Remove events
413 } else if (newState == VolumeState.Idle) {
414 /*
415 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
416 * if we're in the process of enabling UMS
417 */
418 if (!vs.equals(
419 Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
420 Environment.MEDIA_NOFS) && !vs.equals(
421 Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
422 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
423 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
424 }
425 } else if (newState == VolumeState.Pending) {
426 } else if (newState == VolumeState.Checking) {
427 updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
428 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
429 } else if (newState == VolumeState.Mounted) {
430 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
431 // Update media status on PackageManagerService to mount packages on sdcard
432 mPms.updateExternalMediaStatus(true);
433 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
434 in.putExtra("read-only", false);
435 } else if (newState == VolumeState.Unmounting) {
436 mPms.updateExternalMediaStatus(false);
437 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
438 } else if (newState == VolumeState.Formatting) {
439 } else if (newState == VolumeState.Shared) {
440 /* Send the media unmounted event first */
441 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
442 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
443 mContext.sendBroadcast(in);
444
445 updatePublicVolumeState(path, Environment.MEDIA_SHARED);
446 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
447 } else if (newState == VolumeState.SharedMnt) {
448 Log.e(TAG, "Live shared mounts not supported yet!");
449 return;
450 } else {
451 Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
452 }
453
454 if (in != null) {
455 mContext.sendBroadcast(in);
456 }
457 }
458
San Mehat207e5382010-02-04 20:46:54 -0800459 private boolean doGetShareMethodAvailable(String method) {
460 ArrayList<String> rsp = mConnector.doCommand("share status " + method);
461
462 for (String line : rsp) {
463 String []tok = line.split(" ");
464 int code;
465 try {
466 code = Integer.parseInt(tok[0]);
467 } catch (NumberFormatException nfe) {
468 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
469 return false;
470 }
471 if (code == VoldResponseCode.ShareStatusResult) {
472 if (tok[2].equals("available"))
473 return true;
474 return false;
475 } else {
476 Log.e(TAG, String.format("Unexpected response code %d", code));
477 return false;
478 }
479 }
480 Log.e(TAG, "Got an empty response");
481 return false;
482 }
483
484 private int doMountVolume(String path) {
San Mehatb1043402010-02-05 08:26:50 -0800485 int rc = StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800486
487 try {
488 mConnector.doCommand(String.format("volume mount %s", path));
489 } catch (NativeDaemonConnectorException e) {
490 /*
491 * Mount failed for some reason
492 */
493 Intent in = null;
494 int code = e.getCode();
495 if (code == VoldResponseCode.OpFailedNoMedia) {
496 /*
497 * Attempt to mount but no media inserted
498 */
San Mehatb1043402010-02-05 08:26:50 -0800499 rc = StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800500 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
501 /*
502 * Media is blank or does not contain a supported filesystem
503 */
504 updatePublicVolumeState(path, Environment.MEDIA_NOFS);
505 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800506 rc = StorageResultCode.OperationFailedMediaBlank;
San Mehat207e5382010-02-04 20:46:54 -0800507 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
508 /*
509 * Volume consistency check failed
510 */
511 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
512 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
San Mehatb1043402010-02-05 08:26:50 -0800513 rc = StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800514 } else {
San Mehatb1043402010-02-05 08:26:50 -0800515 rc = StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800516 }
517
518 /*
519 * Send broadcast intent (if required for the failure)
520 */
521 if (in != null) {
522 mContext.sendBroadcast(in);
523 }
524 }
525
526 return rc;
527 }
528
529 private int doUnmountVolume(String path) {
San Mehat59443a62010-02-09 13:28:45 -0800530 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
San Mehat207e5382010-02-04 20:46:54 -0800531 return VoldResponseCode.OpFailedVolNotMounted;
532 }
533
534 // Notify PackageManager of potential media removal and deal with
535 // return code later on. The caller of this api should be aware or have been
536 // notified that the applications installed on the media will be killed.
537 mPms.updateExternalMediaStatus(false);
538 try {
539 mConnector.doCommand(String.format("volume unmount %s", path));
San Mehatb1043402010-02-05 08:26:50 -0800540 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800541 } catch (NativeDaemonConnectorException e) {
542 // Don't worry about mismatch in PackageManager since the
543 // call back will handle the status changes any way.
544 int code = e.getCode();
545 if (code == VoldResponseCode.OpFailedVolNotMounted) {
San Mehata181b212010-02-11 06:50:20 -0800546 return StorageResultCode.OperationFailedStorageNotMounted;
San Mehat207e5382010-02-04 20:46:54 -0800547 } else {
San Mehatb1043402010-02-05 08:26:50 -0800548 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800549 }
550 }
551 }
552
553 private int doFormatVolume(String path) {
554 try {
555 String cmd = String.format("volume format %s", path);
556 mConnector.doCommand(cmd);
San Mehatb1043402010-02-05 08:26:50 -0800557 return StorageResultCode.OperationSucceeded;
San Mehat207e5382010-02-04 20:46:54 -0800558 } catch (NativeDaemonConnectorException e) {
559 int code = e.getCode();
560 if (code == VoldResponseCode.OpFailedNoMedia) {
San Mehatb1043402010-02-05 08:26:50 -0800561 return StorageResultCode.OperationFailedNoMedia;
San Mehat207e5382010-02-04 20:46:54 -0800562 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
San Mehatb1043402010-02-05 08:26:50 -0800563 return StorageResultCode.OperationFailedMediaCorrupt;
San Mehat207e5382010-02-04 20:46:54 -0800564 } else {
San Mehatb1043402010-02-05 08:26:50 -0800565 return StorageResultCode.OperationFailedInternalError;
San Mehat207e5382010-02-04 20:46:54 -0800566 }
567 }
568 }
569
San Mehatb1043402010-02-05 08:26:50 -0800570 private boolean doGetVolumeShared(String path, String method) {
571 String cmd = String.format("volume shared %s %s", path, method);
572 ArrayList<String> rsp = mConnector.doCommand(cmd);
573
574 for (String line : rsp) {
575 String []tok = line.split(" ");
576 int code;
577 try {
578 code = Integer.parseInt(tok[0]);
579 } catch (NumberFormatException nfe) {
580 Log.e(TAG, String.format("Error parsing code %s", tok[0]));
581 return false;
582 }
583 if (code == VoldResponseCode.ShareEnabledResult) {
584 if (tok[2].equals("enabled"))
585 return true;
586 return false;
587 } else {
588 Log.e(TAG, String.format("Unexpected response code %d", code));
589 return false;
590 }
591 }
592 Log.e(TAG, "Got an empty response");
593 return false;
594 }
595
San Mehat207e5382010-02-04 20:46:54 -0800596 private void notifyShareAvailabilityChange(String method, final boolean avail) {
San Mehat4270e1e2010-01-29 05:32:19 -0800597 if (!method.equals("ums")) {
598 Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
599 return;
600 }
601
602 synchronized (mListeners) {
603 for (int i = mListeners.size() -1; i >= 0; i--) {
604 MountServiceBinderListener bl = mListeners.get(i);
605 try {
San Mehatb1043402010-02-05 08:26:50 -0800606 bl.mListener.onUsbMassStorageConnectionChanged(avail);
San Mehat4270e1e2010-01-29 05:32:19 -0800607 } catch (RemoteException rex) {
608 Log.e(TAG, "Listener dead");
609 mListeners.remove(i);
610 } catch (Exception ex) {
611 Log.e(TAG, "Listener failed", ex);
612 }
613 }
614 }
615
San Mehat207e5382010-02-04 20:46:54 -0800616 if (mBooted == true) {
617 Intent intent;
618 if (avail) {
619 intent = new Intent(Intent.ACTION_UMS_CONNECTED);
620 } else {
621 intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
622 }
623 mContext.sendBroadcast(intent);
San Mehat4270e1e2010-01-29 05:32:19 -0800624 }
San Mehat4270e1e2010-01-29 05:32:19 -0800625 }
626
San Mehat207e5382010-02-04 20:46:54 -0800627 private void validatePermission(String perm) {
San Mehat4270e1e2010-01-29 05:32:19 -0800628 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
629 throw new SecurityException(String.format("Requires %s permission", perm));
630 }
631 }
632
633 /**
San Mehat207e5382010-02-04 20:46:54 -0800634 * Constructs a new MountService instance
635 *
636 * @param context Binder context for this service
637 */
638 public MountService(Context context) {
639 mContext = context;
640
641 /*
642 * Vold does not run in the simulator, so fake out a mounted
643 * event to trigger MediaScanner
644 */
645 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
646 updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
647 return;
648 }
649
650 // XXX: This will go away soon in favor of IMountServiceObserver
651 mPms = (PackageManagerService) ServiceManager.getService("package");
652
653 mContext.registerReceiver(mBroadcastReceiver,
654 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
655
656 mListeners = new ArrayList<MountServiceBinderListener>();
657
658 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
659 mReady = false;
660 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
661 thread.start();
662 }
663
664 /**
San Mehat4270e1e2010-01-29 05:32:19 -0800665 * Exposed API calls below here
666 */
667
668 public void registerListener(IMountServiceListener listener) {
669 synchronized (mListeners) {
670 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
671 try {
672 listener.asBinder().linkToDeath(bl, 0);
673 mListeners.add(bl);
674 } catch (RemoteException rex) {
675 Log.e(TAG, "Failed to link to listener death");
676 }
677 }
678 }
679
680 public void unregisterListener(IMountServiceListener listener) {
681 synchronized (mListeners) {
682 for(MountServiceBinderListener bl : mListeners) {
683 if (bl.mListener == listener) {
684 mListeners.remove(mListeners.indexOf(bl));
685 return;
686 }
687 }
688 }
689 }
690
691 public void shutdown() {
692 validatePermission(android.Manifest.permission.SHUTDOWN);
693
694 Log.i(TAG, "Shutting down");
695
696 String path = Environment.getExternalStorageDirectory().getPath();
697 String state = getVolumeState(path);
San Mehat91c77612010-01-07 10:39:41 -0800698
699 if (state.equals(Environment.MEDIA_SHARED)) {
700 /*
701 * If the media is currently shared, unshare it.
702 * XXX: This is still dangerous!. We should not
703 * be rebooting at *all* if UMS is enabled, since
704 * the UMS host could have dirty FAT cache entries
705 * yet to flush.
706 */
San Mehatb1043402010-02-05 08:26:50 -0800707 if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800708 Log.e(TAG, "UMS disable on shutdown failed");
San Mehat91c77612010-01-07 10:39:41 -0800709 }
710 } else if (state.equals(Environment.MEDIA_CHECKING)) {
711 /*
712 * If the media is being checked, then we need to wait for
713 * it to complete before being able to proceed.
714 */
715 // XXX: @hackbod - Should we disable the ANR timer here?
716 int retries = 30;
717 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
718 try {
719 Thread.sleep(1000);
720 } catch (InterruptedException iex) {
721 Log.e(TAG, "Interrupted while waiting for media", iex);
722 break;
723 }
724 state = Environment.getExternalStorageState();
725 }
726 if (retries == 0) {
727 Log.e(TAG, "Timed out waiting for media to check");
728 }
729 }
730
731 if (state.equals(Environment.MEDIA_MOUNTED)) {
732 /*
733 * If the media is mounted, then gracefully unmount it.
734 */
San Mehatb1043402010-02-05 08:26:50 -0800735 if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) {
San Mehat4270e1e2010-01-29 05:32:19 -0800736 Log.e(TAG, "Failed to unmount media for shutdown");
737 }
738 }
739 }
740
San Mehatb1043402010-02-05 08:26:50 -0800741 public boolean isUsbMassStorageConnected() {
San Mehat207e5382010-02-04 20:46:54 -0800742 waitForReady();
San Mehat91c77612010-01-07 10:39:41 -0800743
San Mehatb1043402010-02-05 08:26:50 -0800744 if (mUmsEnabling) {
745 return true;
San Mehat7fd0fee2009-12-17 07:12:23 -0800746 }
San Mehatb1043402010-02-05 08:26:50 -0800747 return doGetShareMethodAvailable("ums");
748 }
749
750 public int setUsbMassStorageEnabled(boolean enable) {
751 waitForReady();
752
753 return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable);
754 }
755
756 public boolean isUsbMassStorageEnabled() {
757 waitForReady();
758 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800759 }
San Mehat4270e1e2010-01-29 05:32:19 -0800760
San Mehat7fd0fee2009-12-17 07:12:23 -0800761 /**
762 * @return state of the volume at the specified mount point
763 */
San Mehat4270e1e2010-01-29 05:32:19 -0800764 public String getVolumeState(String mountPoint) {
San Mehat7fd0fee2009-12-17 07:12:23 -0800765 /*
766 * XXX: Until we have multiple volume discovery, just hardwire
767 * this to /sdcard
768 */
769 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
770 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
771 throw new IllegalArgumentException();
772 }
773
774 return mLegacyState;
775 }
776
San Mehat4270e1e2010-01-29 05:32:19 -0800777 public int mountVolume(String path) {
778 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat4270e1e2010-01-29 05:32:19 -0800779
San Mehat207e5382010-02-04 20:46:54 -0800780 waitForReady();
781 return doMountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 }
783
San Mehat4270e1e2010-01-29 05:32:19 -0800784 public int unmountVolume(String path) {
785 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800786 waitForReady();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787
San Mehat207e5382010-02-04 20:46:54 -0800788 return doUnmountVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789 }
790
San Mehat4270e1e2010-01-29 05:32:19 -0800791 public int formatVolume(String path) {
792 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
San Mehat207e5382010-02-04 20:46:54 -0800793 waitForReady();
San Mehat5b77dab2010-01-26 13:28:50 -0800794
San Mehat207e5382010-02-04 20:46:54 -0800795 return doFormatVolume(path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 }
797
San Mehatb1043402010-02-05 08:26:50 -0800798 private void warnOnNotMounted() {
799 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
800 Log.w(TAG, "getSecureContainerList() called when storage not mounted");
801 }
802 }
803
San Mehat4270e1e2010-01-29 05:32:19 -0800804 public String[] getSecureContainerList() {
805 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800806 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800807 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800808
San Mehat4270e1e2010-01-29 05:32:19 -0800809 try {
810 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
811 } catch (NativeDaemonConnectorException e) {
812 return new String[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813 }
814 }
San Mehat36972292010-01-06 11:06:32 -0800815
San Mehat4270e1e2010-01-29 05:32:19 -0800816 public int createSecureContainer(String id, int sizeMb, String fstype,
817 String key, int ownerUid) {
818 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehat207e5382010-02-04 20:46:54 -0800819 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800820 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800821
San Mehatb1043402010-02-05 08:26:50 -0800822 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800823 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
824 try {
825 mConnector.doCommand(cmd);
826 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800827 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800828 }
San Mehata181b212010-02-11 06:50:20 -0800829
830 if (rc == StorageResultCode.OperationSucceeded) {
831 synchronized (mAsecMountSet) {
832 mAsecMountSet.add(id);
833 }
834 }
San Mehat4270e1e2010-01-29 05:32:19 -0800835 return rc;
San Mehat36972292010-01-06 11:06:32 -0800836 }
837
San Mehat4270e1e2010-01-29 05:32:19 -0800838 public int finalizeSecureContainer(String id) {
839 validatePermission(android.Manifest.permission.ASEC_CREATE);
San Mehatb1043402010-02-05 08:26:50 -0800840 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800841
San Mehatb1043402010-02-05 08:26:50 -0800842 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800843 try {
844 mConnector.doCommand(String.format("asec finalize %s", id));
San Mehata181b212010-02-11 06:50:20 -0800845 /*
846 * Finalization does a remount, so no need
847 * to update mAsecMountSet
848 */
San Mehat4270e1e2010-01-29 05:32:19 -0800849 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800850 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800851 }
San Mehat4270e1e2010-01-29 05:32:19 -0800852 return rc;
San Mehat36972292010-01-06 11:06:32 -0800853 }
854
San Mehat4270e1e2010-01-29 05:32:19 -0800855 public int destroySecureContainer(String id) {
856 validatePermission(android.Manifest.permission.ASEC_DESTROY);
San Mehat207e5382010-02-04 20:46:54 -0800857 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800858 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800859
San Mehatb1043402010-02-05 08:26:50 -0800860 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800861 try {
862 mConnector.doCommand(String.format("asec destroy %s", id));
863 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800864 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800865 }
San Mehata181b212010-02-11 06:50:20 -0800866
867 if (rc == StorageResultCode.OperationSucceeded) {
868 synchronized (mAsecMountSet) {
869 if (mAsecMountSet.contains(id)) {
870 mAsecMountSet.remove(id);
871 }
872 }
873 }
874
San Mehat4270e1e2010-01-29 05:32:19 -0800875 return rc;
San Mehat36972292010-01-06 11:06:32 -0800876 }
877
San Mehat4270e1e2010-01-29 05:32:19 -0800878 public int mountSecureContainer(String id, String key, int ownerUid) {
879 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800880 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800881 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800882
San Mehata181b212010-02-11 06:50:20 -0800883 synchronized (mAsecMountSet) {
884 if (mAsecMountSet.contains(id)) {
885 return StorageResultCode.OperationFailedStorageMounted;
886 }
887 }
888
San Mehatb1043402010-02-05 08:26:50 -0800889 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800890 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
891 try {
892 mConnector.doCommand(cmd);
893 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800894 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800895 }
San Mehat6cdd9c02010-02-09 14:45:20 -0800896
897 if (rc == StorageResultCode.OperationSucceeded) {
898 synchronized (mAsecMountSet) {
899 mAsecMountSet.add(id);
900 }
901 }
San Mehat4270e1e2010-01-29 05:32:19 -0800902 return rc;
San Mehat36972292010-01-06 11:06:32 -0800903 }
904
San Mehat4270e1e2010-01-29 05:32:19 -0800905 public int unmountSecureContainer(String id) {
906 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
San Mehat207e5382010-02-04 20:46:54 -0800907 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800908 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800909
San Mehat6cdd9c02010-02-09 14:45:20 -0800910 synchronized (mAsecMountSet) {
911 if (!mAsecMountSet.contains(id)) {
San Mehata181b212010-02-11 06:50:20 -0800912 return StorageResultCode.OperationFailedStorageNotMounted;
San Mehat6cdd9c02010-02-09 14:45:20 -0800913 }
914 }
915
San Mehatb1043402010-02-05 08:26:50 -0800916 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800917 String cmd = String.format("asec unmount %s", id);
918 try {
919 mConnector.doCommand(cmd);
920 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800921 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800922 }
San Mehat6cdd9c02010-02-09 14:45:20 -0800923
924 if (rc == StorageResultCode.OperationSucceeded) {
925 synchronized (mAsecMountSet) {
926 mAsecMountSet.remove(id);
927 }
928 }
San Mehat4270e1e2010-01-29 05:32:19 -0800929 return rc;
San Mehat9dba7092010-01-18 06:47:41 -0800930 }
931
San Mehat6cdd9c02010-02-09 14:45:20 -0800932 public boolean isSecureContainerMounted(String id) {
933 validatePermission(android.Manifest.permission.ASEC_ACCESS);
934 waitForReady();
935 warnOnNotMounted();
936
937 synchronized (mAsecMountSet) {
938 return mAsecMountSet.contains(id);
939 }
940 }
941
San Mehat4270e1e2010-01-29 05:32:19 -0800942 public int renameSecureContainer(String oldId, String newId) {
943 validatePermission(android.Manifest.permission.ASEC_RENAME);
San Mehat207e5382010-02-04 20:46:54 -0800944 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800945 warnOnNotMounted();
San Mehat4270e1e2010-01-29 05:32:19 -0800946
San Mehata181b212010-02-11 06:50:20 -0800947 synchronized (mAsecMountSet) {
948 if (mAsecMountSet.contains(oldId)) {
949 return StorageResultCode.OperationFailedStorageMounted;
950 }
951 }
952
San Mehatb1043402010-02-05 08:26:50 -0800953 int rc = StorageResultCode.OperationSucceeded;
San Mehat4270e1e2010-01-29 05:32:19 -0800954 String cmd = String.format("asec rename %s %s", oldId, newId);
955 try {
956 mConnector.doCommand(cmd);
957 } catch (NativeDaemonConnectorException e) {
San Mehatb1043402010-02-05 08:26:50 -0800958 rc = StorageResultCode.OperationFailedInternalError;
San Mehat02735bc2010-01-26 15:18:08 -0800959 }
San Mehata181b212010-02-11 06:50:20 -0800960
San Mehat4270e1e2010-01-29 05:32:19 -0800961 return rc;
San Mehat45f61042010-01-23 08:12:43 -0800962 }
963
San Mehat4270e1e2010-01-29 05:32:19 -0800964 public String getSecureContainerPath(String id) {
965 validatePermission(android.Manifest.permission.ASEC_ACCESS);
San Mehat207e5382010-02-04 20:46:54 -0800966 waitForReady();
San Mehatb1043402010-02-05 08:26:50 -0800967 warnOnNotMounted();
San Mehatf919cd022010-02-04 15:10:38 -0800968
San Mehat4270e1e2010-01-29 05:32:19 -0800969 ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
San Mehat36972292010-01-06 11:06:32 -0800970
San Mehat22dd86e2010-01-12 12:21:18 -0800971 for (String line : rsp) {
972 String []tok = line.split(" ");
973 int code = Integer.parseInt(tok[0]);
974 if (code == VoldResponseCode.AsecPathResult) {
975 return tok[1];
976 } else {
San Mehat4270e1e2010-01-29 05:32:19 -0800977 Log.e(TAG, String.format("Unexpected response code %d", code));
978 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800979 }
980 }
San Mehat4270e1e2010-01-29 05:32:19 -0800981
982 Log.e(TAG, "Got an empty response");
983 return "";
San Mehat22dd86e2010-01-12 12:21:18 -0800984 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985}
986