blob: 2e430c849d973639a9634131e91099d31fa7b016 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.net.LocalSocketAddress;
import android.net.LocalSocket;
import android.os.Environment;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* Thread for communicating with the vol service daemon via a local socket.
* Events received from the daemon are passed to the MountService instance,
* and the MountService instance calls MountListener to send commands to the daemon.
*/
final class MountListener implements Runnable {
private static final String TAG = "MountListener";
// ** THE FOLLOWING STRING CONSTANTS MUST MATCH VALUES IN system/vold/
// socket name for connecting to vold
private static final String VOLD_SOCKET = "vold";
// vold commands
private static final String VOLD_CMD_ENABLE_UMS = "enable_ums";
private static final String VOLD_CMD_DISABLE_UMS = "disable_ums";
private static final String VOLD_CMD_SEND_UMS_STATUS = "send_ums_status";
private static final String VOLD_CMD_MOUNT_VOLUME = "mount_volume:";
private static final String VOLD_CMD_EJECT_MEDIA = "eject_media:";
private static final String VOLD_CMD_FORMAT_MEDIA = "format_media:";
// vold events
private static final String VOLD_EVT_UMS_ENABLED = "ums_enabled";
private static final String VOLD_EVT_UMS_DISABLED = "ums_disabled";
private static final String VOLD_EVT_UMS_CONNECTED = "ums_connected";
private static final String VOLD_EVT_UMS_DISCONNECTED = "ums_disconnected";
private static final String VOLD_EVT_NOMEDIA = "volume_nomedia:";
private static final String VOLD_EVT_UNMOUNTED = "volume_unmounted:";
private static final String VOLD_EVT_MOUNTED = "volume_mounted:";
private static final String VOLD_EVT_MOUNTED_RO = "volume_mounted_ro:";
private static final String VOLD_EVT_UMS = "volume_ums";
private static final String VOLD_EVT_BAD_REMOVAL = "volume_badremoval:";
private static final String VOLD_EVT_DAMAGED = "volume_damaged:";
private static final String VOLD_EVT_CHECKING = "volume_checking:";
private static final String VOLD_EVT_NOFS = "volume_nofs:";
private static final String VOLD_EVT_EJECTING = "volume_ejecting:";
/**
* MountService that handles events received from the vol service daemon
*/
private MountService mService;
/**
* Stream for sending commands to the vol service daemon.
*/
private OutputStream mOutputStream;
/**
* Cached value indicating whether or not USB mass storage is enabled.
*/
private boolean mUmsEnabled;
/**
* Cached value indicating whether or not USB mass storage is connected.
*/
private boolean mUmsConnected;
/**
* Constructor for MountListener
*
* @param service The MountListener we are handling communication with USB
* daemon for.
*/
MountListener(MountService service) {
mService = service;
}
/**
* Process and dispatches events received from the vol service daemon
*
* @param event An event received from the vol service daemon
*/
private void handleEvent(String event) {
if (Config.LOGD) Log.d(TAG, "handleEvent " + event);
int colonIndex = event.indexOf(':');
String path = (colonIndex > 0 ? event.substring(colonIndex + 1) : null);
if (event.equals(VOLD_EVT_UMS_ENABLED)) {
mUmsEnabled = true;
} else if (event.equals(VOLD_EVT_UMS_DISABLED)) {
mUmsEnabled = false;
} else if (event.equals(VOLD_EVT_UMS_CONNECTED)) {
mUmsConnected = true;
mService.notifyUmsConnected();
} else if (event.equals(VOLD_EVT_UMS_DISCONNECTED)) {
mUmsConnected = false;
mService.notifyUmsDisconnected();
} else if (event.startsWith(VOLD_EVT_NOMEDIA)) {
mService.notifyMediaRemoved(path);
} else if (event.startsWith(VOLD_EVT_UNMOUNTED)) {
mService.notifyMediaUnmounted(path);
} else if (event.startsWith(VOLD_EVT_CHECKING)) {
mService.notifyMediaChecking(path);
} else if (event.startsWith(VOLD_EVT_NOFS)) {
mService.notifyMediaNoFs(path);
} else if (event.startsWith(VOLD_EVT_MOUNTED)) {
mService.notifyMediaMounted(path, false);
} else if (event.startsWith(VOLD_EVT_MOUNTED_RO)) {
mService.notifyMediaMounted(path, true);
} else if (event.startsWith(VOLD_EVT_UMS)) {
mService.notifyMediaShared(path);
} else if (event.startsWith(VOLD_EVT_BAD_REMOVAL)) {
mService.notifyMediaBadRemoval(path);
// also send media eject intent, to notify apps to close any open
// files on the media.
mService.notifyMediaEject(path);
} else if (event.startsWith(VOLD_EVT_DAMAGED)) {
mService.notifyMediaUnmountable(path);
} else if (event.startsWith(VOLD_EVT_EJECTING)) {
mService.notifyMediaEject(path);
}
}
/**
* Sends a command to the mount service daemon via a local socket
*
* @param command The command to send to the mount service daemon
*/
private void writeCommand(String command) {
writeCommand2(command, null);
}
/**
* Sends a command to the mount service daemon via a local socket
* with a single argument
*
* @param command The command to send to the mount service daemon
* @param argument The argument to send with the command (or null)
*/
private void writeCommand2(String command, String argument) {
synchronized (this) {
if (mOutputStream == null) {
Log.e(TAG, "No connection to vold", new IllegalStateException());
} else {
StringBuilder builder = new StringBuilder(command);
if (argument != null) {
builder.append(argument);
}
builder.append('\0');
try {
mOutputStream.write(builder.toString().getBytes());
} catch (IOException ex) {
Log.e(TAG, "IOException in writeCommand", ex);
}
}
}
}
/**
* Opens a socket to communicate with the mount service daemon and listens
* for events from the daemon.
*
*/
private void listenToSocket() {
LocalSocket socket = null;
try {
socket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress(VOLD_SOCKET,
LocalSocketAddress.Namespace.RESERVED);
socket.connect(address);
InputStream inputStream = socket.getInputStream();
mOutputStream = socket.getOutputStream();
byte[] buffer = new byte[100];
writeCommand(VOLD_CMD_SEND_UMS_STATUS);
while (true) {
int count = inputStream.read(buffer);
if (count < 0) break;
int start = 0;
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
String event = new String(buffer, start, i - start);
handleEvent(event);
start = i + 1;
}
}
}
} catch (IOException ex) {
// This exception is normal when running in desktop simulator
// where there is no mount daemon to talk to
// log("IOException in listenToSocket");
}
synchronized (this) {
if (mOutputStream != null) {
try {
mOutputStream.close();
} catch (IOException e) {
Log.w(TAG, "IOException closing output stream");
}
mOutputStream = null;
}
}
try {
if (socket != null) {
socket.close();
}
} catch (IOException ex) {
Log.w(TAG, "IOException closing socket");
}
/*
* Sleep before trying again.
* This should not happen except while debugging.
* Without this sleep, the emulator will spin and
* create tons of throwaway LocalSockets, making
* system_server GC constantly.
*/
Log.e(TAG, "Failed to connect to vold", new IllegalStateException());
SystemClock.sleep(2000);
}
/**
* Main loop for MountListener thread.
*/
public void run() {
// ugly hack for the simulator.
if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
SystemProperties.set("EXTERNAL_STORAGE_STATE", Environment.MEDIA_MOUNTED);
// usbd does not run in the simulator, so send a fake device mounted event to trigger the Media Scanner
mService.notifyMediaMounted(Environment.getExternalStorageDirectory().getPath(), false);
// no usbd in the simulator, so no point in hanging around.
return;
}
try {
while (true) {
listenToSocket();
}
} catch (Throwable t) {
// catch all Throwables so we don't bring down the system process
Log.e(TAG, "Fatal error " + t + " in MountListener thread!");
}
}
/**
* @return true if USB mass storage is enabled
*/
boolean getMassStorageEnabled() {
return mUmsEnabled;
}
/**
* Enables or disables USB mass storage support.
*
* @param enable true to enable USB mass storage support
*/
void setMassStorageEnabled(boolean enable) {
writeCommand(enable ? VOLD_CMD_ENABLE_UMS : VOLD_CMD_DISABLE_UMS);
}
/**
* @return true if USB mass storage is connected
*/
boolean getMassStorageConnected() {
return mUmsConnected;
}
/**
* Mount media at given mount point.
*/
public void mountMedia(String mountPoint) {
writeCommand2(VOLD_CMD_MOUNT_VOLUME, mountPoint);
}
/**
* Unmount media at given mount point.
*/
public void ejectMedia(String mountPoint) {
writeCommand2(VOLD_CMD_EJECT_MEDIA, mountPoint);
}
/**
* Format media at given mount point.
*/
public void formatMedia(String mountPoint) {
writeCommand2(VOLD_CMD_FORMAT_MEDIA, mountPoint);
}
}