/* * 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); } }