OSDN Git Service

UsbManager: New APIs for USB accessories
authorMike Lockwood <lockwood@android.com>
Tue, 15 Feb 2011 14:50:22 +0000 (09:50 -0500)
committerMike Lockwood <lockwood@android.com>
Wed, 16 Feb 2011 13:25:16 +0000 (08:25 -0500)
USB accessories are peripherals that connect to android devices as a USB host.

When connected, the accessory will first identify itself to the android device
by sending manufacturer, product, accessory type and version strings
to the device, and then request the device to enter USB accessory mode.
The device will then enable the USB accessory kernel driver and disable
all other USB functionality except possibly adb
(adb can be used while the android device is connected to the PC
and the PC is running software that emulates a USB accessory)

The class android.hardware.UsbAccessory is used to describe the
currently attached USB accessory.
UsbAccessory contains the manufacturer, product, accessory type
and version strings to identify the accessory.
The accessory can be opened as a ParcelFileDescriptor, which can be used
to communicate with the accessory over two bulk endpoints.

The Intents UsbManager.USB_ACCESSORY_ATTACHED and
UsbManager.USB_ACCESSORY_DETACHED are broadcast when accessories are
connected and disconnected to the device.  The USB_ACCESSORY_ATTACHED
contains a UsbAccessory object for the attached accessory as an extra.
The Intent also contains string extras for the manufacturer, product,
accessory type and version strings to allow filtering on these strings.

Change-Id: Ie77cbf51814a4aa44a6b1e62673bfe4c6aa81755
Signed-off-by: Mike Lockwood <lockwood@android.com>
api/current.xml
core/java/android/hardware/IUsbManager.aidl
core/java/android/hardware/UsbAccessory.aidl [new file with mode: 0644]
core/java/android/hardware/UsbAccessory.java [new file with mode: 0644]
core/java/android/hardware/UsbManager.java
core/res/AndroidManifest.xml
services/java/com/android/server/UsbService.java
services/jni/com_android_server_UsbService.cpp

index 7806c24..cb86aad 100644 (file)
 >
 </field>
 </class>
+<class name="UsbAccessory"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getManufacturer"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getModel"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getType"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVersion"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parcel" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="UsbConstants"
  extends="java.lang.Object"
  abstract="false"
  deprecated="not deprecated"
  visibility="public"
 >
+<method name="getAccessoryList"
+ return="android.hardware.UsbAccessory[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getDeviceList"
  return="java.util.HashMap&lt;java.lang.String, android.hardware.UsbDevice&gt;"
  abstract="false"
 <parameter name="function" type="java.lang.String">
 </parameter>
 </method>
+<method name="openAccessory"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="accessory" type="android.hardware.UsbAccessory">
+</parameter>
+</method>
 <method name="openDevice"
  return="boolean"
  abstract="false"
 <parameter name="device" type="android.hardware.UsbDevice">
 </parameter>
 </method>
+<field name="ACTION_USB_ACCESSORY_ATTACHED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.hardware.action.USB_ACCESSORY_ATTACHED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_USB_ACCESSORY_DETACHED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.hardware.action.USB_ACCESSORY_DETACHED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_USB_DEVICE_ATTACHED"
  type="java.lang.String"
  transient="false"
  visibility="public"
 >
 </field>
+<field name="EXTRA_ACCESSORY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accessory&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_ACCESSORY_MANUFACTURER"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accessory-manufacturer&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_ACCESSORY_PRODUCT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accessory-product&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_ACCESSORY_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accessory-type&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_ACCESSORY_VERSION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accessory-version&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_DEVICE"
  type="java.lang.String"
  transient="false"
  visibility="public"
 >
 </field>
+<field name="USB_FUNCTION_ACCESSORY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;accessory&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="USB_FUNCTION_ADB"
  type="java.lang.String"
  transient="false"
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="s" type="java.lang.String">
+<parameter name="key" type="java.lang.String">
 </parameter>
 </method>
 <method name="describeContents"
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="s" type="java.lang.String">
+<parameter name="key" type="java.lang.String">
 </parameter>
 </method>
 <method name="getIconResId"
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
 </parameter>
 </method>
 </interface>
index b50b6b9..6c99ab3 100644 (file)
@@ -16,6 +16,7 @@
 
 package android.hardware;
 
+import android.hardware.UsbAccessory;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
@@ -25,4 +26,6 @@ interface IUsbManager
     /* Returns a list of all currently attached USB devices */
     void getDeviceList(out Bundle devices);
     ParcelFileDescriptor openDevice(String deviceName);
+    UsbAccessory getCurrentAccessory();
+    ParcelFileDescriptor openAccessory();
 }
diff --git a/core/java/android/hardware/UsbAccessory.aidl b/core/java/android/hardware/UsbAccessory.aidl
new file mode 100644 (file)
index 0000000..97a777b
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011, 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 android.hardware;
+
+parcelable UsbAccessory;
diff --git a/core/java/android/hardware/UsbAccessory.java b/core/java/android/hardware/UsbAccessory.java
new file mode 100644 (file)
index 0000000..71672fa
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 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 android.hardware;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * A class representing a USB accessory.
+ */
+public final class UsbAccessory implements Parcelable {
+
+    private static final String TAG = "UsbAccessory";
+
+    private String mManufacturer;
+    private String mModel;
+    private String mType;
+    private String mVersion;
+
+    private UsbAccessory() {
+    }
+
+    /**
+     * UsbAccessory should only be instantiated by UsbService implementation
+     * @hide
+     */
+    public UsbAccessory(String manufacturer, String model, String type, String version) {
+        mManufacturer = manufacturer;
+        mModel = model;
+        mType = type;
+        mVersion = version;
+    }
+
+    /**
+     * UsbAccessory should only be instantiated by UsbService implementation
+     * @hide
+     */
+    public UsbAccessory(String[] strings) {
+        mManufacturer = strings[0];
+        mModel = strings[1];
+        mType = strings[2];
+        mVersion = strings[3];
+    }
+
+    /**
+     * Returns the manufacturer of the accessory.
+     *
+     * @return the accessory manufacturer
+     */
+    public String getManufacturer() {
+        return mManufacturer;
+    }
+
+    /**
+     * Returns the model name of the accessory.
+     *
+     * @return the accessory model
+     */
+    public String getModel() {
+        return mModel;
+    }
+
+    /**
+     * Returns the type of the accessory.
+     *
+     * @return the accessory type
+     */
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the version of the accessory.
+     *
+     * @return the accessory version
+     */
+    public String getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public String toString() {
+        return "UsbAccessory[mManufacturer=" + mManufacturer +
+                            ", mModel=" + mModel +
+                            ", mType=" + mType +
+                            ", mVersion=" + mVersion + "]";
+    }
+
+    public static final Parcelable.Creator<UsbAccessory> CREATOR =
+        new Parcelable.Creator<UsbAccessory>() {
+        public UsbAccessory createFromParcel(Parcel in) {
+            String manufacturer = in.readString();
+            String model = in.readString();
+            String type = in.readString();
+            String version = in.readString();
+            return new UsbAccessory(manufacturer, model, type, version);
+        }
+
+        public UsbAccessory[] newArray(int size) {
+            return new UsbAccessory[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mManufacturer);
+        parcel.writeString(mModel);
+        parcel.writeString(mType);
+        parcel.writeString(mVersion);
+   }
+}
index 8fad210..0f616ff 100644 (file)
@@ -24,6 +24,7 @@ import android.util.Log;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashMap;
 
@@ -44,11 +45,14 @@ public class UsbManager {
      * Broadcast Action:  A sticky broadcast for USB state change events when in device mode.
      *
      * This is a sticky broadcast for clients that includes USB connected/disconnected state,
-     * the USB configuration that is currently set and a bundle containing name/value pairs
-     * with the names of the functions and a value of either {@link #USB_FUNCTION_ENABLED}
-     * or {@link #USB_FUNCTION_DISABLED}.
-     * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE},
-     * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}.
+     * <ul>
+     * <li> {@link #USB_CONNECTED} boolean indicating whether USB is connected or disconnected.
+     * <li> {@link #USB_CONFIGURATION} a Bundle containing name/value pairs where the name
+     * is the name of a USB function and the value is either {@link #USB_FUNCTION_ENABLED}
+     * or {@link #USB_FUNCTION_DISABLED}.  The possible function names include
+     * {@link #USB_FUNCTION_MASS_STORAGE}, {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS},
+     * {@link #USB_FUNCTION_MTP} and {@link #USB_FUNCTION_ACCESSORY}.
+     * </ul>
      */
     public static final String ACTION_USB_STATE =
             "android.hardware.action.USB_STATE";
@@ -57,6 +61,16 @@ public class UsbManager {
      * Broadcast Action:  A broadcast for USB device attached event.
      *
      * This intent is sent when a USB device is attached to the USB bus when in host mode.
+     * <ul>
+     * <li> {@link #EXTRA_DEVICE_NAME} containing the device's name (String)
+     * <li> {@link #EXTRA_VENDOR_ID} containing the device's vendor ID (Integer)
+     * <li> {@link #EXTRA_PRODUCT_ID} containing the device's product ID (Integer)
+     * <li> {@link #EXTRA_DEVICE_CLASS} } containing the device class (Integer)
+     * <li> {@link #EXTRA_DEVICE_SUBCLASS} containing the device subclass (Integer)
+     * <li> {@link #EXTRA_DEVICE_PROTOCOL} containing the device protocol (Integer)
+     * <li> {@link #EXTRA_DEVICE} containing the {@link android.hardware.UsbDevice}
+     * for the attached device
+     * </ul>
      */
     public static final String ACTION_USB_DEVICE_ATTACHED =
             "android.hardware.action.USB_DEVICE_ATTACHED";
@@ -65,10 +79,41 @@ public class UsbManager {
      * Broadcast Action:  A broadcast for USB device detached event.
      *
      * This intent is sent when a USB device is detached from the USB bus when in host mode.
+     * <ul>
+     * <li> {@link #EXTRA_DEVICE_NAME} containing the device's name (String)
+     * </ul>
      */
     public static final String ACTION_USB_DEVICE_DETACHED =
             "android.hardware.action.USB_DEVICE_DETACHED";
 
+   /**
+     * Broadcast Action:  A broadcast for USB accessory attached event.
+     *
+     * This intent is sent when a USB accessory is attached.
+     * <ul>
+     * <li> {@link #EXTRA_ACCESSORY_MANUFACTURER} containing the accessory's manufacturer (String)
+     * <li> {@link #EXTRA_ACCESSORY_PRODUCT} containing the accessory's product name (String)
+     * <li> {@link #EXTRA_ACCESSORY_TYPE} containing the accessory's type (String)
+     * <li> {@link #EXTRA_ACCESSORY_VERSION} containing the accessory's version (String)
+     * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.UsbAccessory}
+     * for the attached accessory
+     * </ul>
+     */
+    public static final String ACTION_USB_ACCESSORY_ATTACHED =
+            "android.hardware.action.USB_ACCESSORY_ATTACHED";
+
+   /**
+     * Broadcast Action:  A broadcast for USB accessory detached event.
+     *
+     * This intent is sent when a USB accessory is detached.
+     * <ul>
+      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.UsbAccessory}
+     * for the attached accessory that was detached
+     * </ul>
+     */
+    public static final String ACTION_USB_ACCESSORY_DETACHED =
+            "android.hardware.action.USB_ACCESSORY_DETACHED";
+
     /**
      * Boolean extra indicating whether USB is connected or disconnected.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
@@ -106,14 +151,22 @@ public class UsbManager {
     public static final String USB_FUNCTION_MTP = "mtp";
 
     /**
-     * Value indicating that a USB function is enabled.
+     * Name of the Accessory USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      */
+    public static final String USB_FUNCTION_ACCESSORY = "accessory";
+
+    /**
+     * Value indicating that a USB function is enabled.
+     * Used in {@link #USB_CONFIGURATION} extras bundle for the
+     * {@link #ACTION_USB_STATE} broadcast
+     */
     public static final String USB_FUNCTION_ENABLED = "enabled";
 
     /**
      * Value indicating that a USB function is disabled.
-     * Used in extras for the {@link #ACTION_USB_STATE} broadcast
+     * Used in {@link #USB_CONFIGURATION} extras bundle for the
+     * {@link #ACTION_USB_STATE} broadcast
      */
     public static final String USB_FUNCTION_DISABLED = "disabled";
 
@@ -158,8 +211,39 @@ public class UsbManager {
      * Name of extra for {@link #ACTION_USB_DEVICE_ATTACHED} broadcast
      * containing the UsbDevice object for the device.
      */
+
     public static final String EXTRA_DEVICE = "device";
 
+    /**
+     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} broadcast
+     * containing the UsbAccessory object for the accessory.
+     */
+    public static final String EXTRA_ACCESSORY = "accessory";
+
+    /**
+     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} broadcast
+     * containing the accessory's manufacturer name.
+     */
+    public static final String EXTRA_ACCESSORY_MANUFACTURER = "accessory-manufacturer";
+
+    /**
+     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} broadcast
+     * containing the accessory's product name.
+     */
+    public static final String EXTRA_ACCESSORY_PRODUCT = "accessory-product";
+
+    /**
+     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} broadcast
+     * containing the accessory's type.
+     */
+    public static final String EXTRA_ACCESSORY_TYPE = "accessory-type";
+
+    /**
+     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} broadcast
+     * containing the accessory's version.
+     */
+    public static final String EXTRA_ACCESSORY_VERSION = "accessory-version";
+
     private IUsbManager mService;
 
     /**
@@ -214,6 +298,41 @@ public class UsbManager {
         }
     }
 
+    /**
+     * Returns a list of currently attached USB accessories.
+     * (in the current implementation there can be at most one)
+     *
+     * @return list of USB accessories, or null if none are attached.
+     */
+    public UsbAccessory[] getAccessoryList() {
+        try {
+            UsbAccessory accessory = mService.getCurrentAccessory();
+            if (accessory == null) {
+                return null;
+            } else {
+                return new UsbAccessory[] { accessory };
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in openAccessory" , e);
+            return null;
+        }
+    }
+
+    /**
+     * Opens a file descriptor for reading and writing data to the USB accessory.
+     *
+     * @param accessory the USB accessory to open
+     * @return file descriptor, or null if the accessor could not be opened.
+     */
+    public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+        try {
+            return mService.openAccessory();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in openAccessory" , e);
+            return null;
+        }
+    }
+
     private static File getFunctionEnableFile(String function) {
         return new File("/sys/class/usb_composite/" + function + "/enable");
     }
@@ -245,4 +364,20 @@ public class UsbManager {
             return false;
         }
     }
+
+    /**
+     * Enables or disables a USB function.
+     *
+     * @hide
+     */
+    public static boolean setFunctionEnabled(String function, boolean enable) {
+        try {
+            FileOutputStream stream = new FileOutputStream(getFunctionEnableFile(function));
+            stream.write(enable ? '1' : '0');
+            stream.close();
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
 }
index 2dc0d31..37983e8 100644 (file)
@@ -82,9 +82,9 @@
     <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
     <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_CANCEL" />
 
-    <protected-broadcast android:name="android.hardware.action.USB_CONNECTED" />
-    <protected-broadcast android:name="android.hardware.action.USB_DISCONNECTED" />
     <protected-broadcast android:name="android.hardware.action.USB_STATE" />
+    <protected-broadcast android:name="android.hardware.action.USB_ACCESSORY_ATTACHED" />
+    <protected-broadcast android:name="android.hardware.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.action.USB_DEVICE_ATTACHED" />
     <protected-broadcast android:name="android.hardware.action.USB_DEVICE_DETACHED" />
 
index 460fd4d..af4c425 100644 (file)
@@ -20,6 +20,7 @@ import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.IUsbManager;
+import android.hardware.UsbAccessory;
 import android.hardware.UsbConstants;
 import android.hardware.UsbDevice;
 import android.hardware.UsbEndpoint;
@@ -78,18 +79,63 @@ class UsbService extends IUsbManager.Stub {
     private int mLastConfiguration = -1;
 
     // lists of enabled and disabled USB functions (for USB device mode)
+    // synchronize on mEnabledFunctions when using either of these lists
     private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
     private final ArrayList<String> mDisabledFunctions = new ArrayList<String>();
 
+    // contains all connected USB devices (for USB host mode)
     private final HashMap<String,UsbDevice> mDevices = new HashMap<String,UsbDevice>();
 
     // USB busses to exclude from USB host support
     private final String[] mHostBlacklist;
 
     private boolean mSystemReady;
+    private UsbAccessory mCurrentAccessory;
 
     private final Context mContext;
 
+    private final void functionEnabled(String function, boolean enabled) {
+        synchronized (mEnabledFunctions) {
+            if (enabled) {
+                if (!mEnabledFunctions.contains(function)) {
+                    mEnabledFunctions.add(function);
+                }
+                mDisabledFunctions.remove(function);
+            } else {
+                if (!mDisabledFunctions.contains(function)) {
+                    mDisabledFunctions.add(function);
+                }
+                mEnabledFunctions.remove(function);
+            }
+        }
+
+        if (enabled && UsbManager.USB_FUNCTION_ACCESSORY.equals(function)) {
+            String[] strings = nativeGetAccessoryStrings();
+            if (strings != null) {
+                Log.d(TAG, "entering USB accessory mode");
+                mCurrentAccessory = new UsbAccessory(strings);
+                Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+                intent.putExtra(UsbManager.EXTRA_ACCESSORY, mCurrentAccessory);
+                // add strings as separate extras to allow filtering
+                if (strings[0] != null) {
+                    intent.putExtra(UsbManager.EXTRA_ACCESSORY_MANUFACTURER, strings[0]);
+                }
+                if (strings[1] != null) {
+                    intent.putExtra(UsbManager.EXTRA_ACCESSORY_PRODUCT, strings[1]);
+                }
+                if (strings[2] != null) {
+                    intent.putExtra(UsbManager.EXTRA_ACCESSORY_TYPE, strings[2]);
+                }
+                if (strings[3] != null) {
+                    intent.putExtra(UsbManager.EXTRA_ACCESSORY_VERSION, strings[3]);
+                }
+                mContext.sendBroadcast(intent);
+            } else {
+                Log.e(TAG, "nativeGetAccessoryStrings failed");
+            }
+        }
+    }
+
     private final UEventObserver mUEventObserver = new UEventObserver() {
         @Override
         public void onUEvent(UEventObserver.UEvent event) {
@@ -127,17 +173,7 @@ class UsbService extends IUsbManager.Stub {
                         // Note: we do not broadcast a change when a function is enabled or disabled.
                         // We just record the state change for the next broadcast.
                         boolean enabled = "1".equals(enabledStr);
-                        if (enabled) {
-                            if (!mEnabledFunctions.contains(function)) {
-                                mEnabledFunctions.add(function);
-                            }
-                            mDisabledFunctions.remove(function);
-                        } else {
-                            if (!mDisabledFunctions.contains(function)) {
-                                mDisabledFunctions.add(function);
-                            }
-                            mEnabledFunctions.remove(function);
-                        }
+                        functionEnabled(function, enabled);
                     }
                 }
             }
@@ -182,18 +218,20 @@ class UsbService extends IUsbManager.Stub {
             return;
 
         try {
-            File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
-            for (int i = 0; i < files.length; i++) {
-                File file = new File(files[i], "enable");
-                FileReader reader = new FileReader(file);
-                int len = reader.read(buffer, 0, 1024);
-                reader.close();
-                int value = Integer.valueOf((new String(buffer, 0, len)).trim());
-                String functionName = files[i].getName();
-                if (value == 1) {
-                    mEnabledFunctions.add(functionName);
-                } else {
-                    mDisabledFunctions.add(functionName);
+            synchronized (mEnabledFunctions) {
+                File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
+                for (int i = 0; i < files.length; i++) {
+                    File file = new File(files[i], "enable");
+                    FileReader reader = new FileReader(file);
+                    int len = reader.read(buffer, 0, 1024);
+                    reader.close();
+                    int value = Integer.valueOf((new String(buffer, 0, len)).trim());
+                    String functionName = files[i].getName();
+                    if (value == 1) {
+                        mEnabledFunctions.add(functionName);
+                    } else {
+                        mDisabledFunctions.add(functionName);
+                    }
                 }
             }
         } catch (FileNotFoundException e) {
@@ -359,19 +397,32 @@ class UsbService extends IUsbManager.Stub {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
         if (mDevices.get(deviceName) == null) {
             // if it is not in mDevices, it either does not exist or is blacklisted
-            throw new IllegalArgumentException("device " + deviceName + " does not exist or is restricted");
+            throw new IllegalArgumentException(
+                    "device " + deviceName + " does not exist or is restricted");
         }
         return nativeOpenDevice(deviceName);
     }
 
+    public UsbAccessory getCurrentAccessory() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
+        return mCurrentAccessory;
+    }
+
+    public ParcelFileDescriptor openAccessory() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null);
+        return nativeOpenAccessory();
+    }
+
     private final Handler mHandler = new Handler() {
         private void addEnabledFunctions(Intent intent) {
+            synchronized (mEnabledFunctions) {
             // include state of all USB functions in our extras
-            for (int i = 0; i < mEnabledFunctions.size(); i++) {
-                intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
-            }
-            for (int i = 0; i < mDisabledFunctions.size(); i++) {
-                intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
+                for (int i = 0; i < mEnabledFunctions.size(); i++) {
+                    intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
+                }
+                for (int i = 0; i < mDisabledFunctions.size(); i++) {
+                    intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
+                }
             }
         }
 
@@ -381,6 +432,26 @@ class UsbService extends IUsbManager.Stub {
                 case MSG_UPDATE:
                     synchronized (this) {
                         if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
+                            if (mConnected == 0 && mCurrentAccessory != null) {
+                                // turn off accessory mode when we are disconnected
+                                if (UsbManager.setFunctionEnabled(
+                                        UsbManager.USB_FUNCTION_ACCESSORY, false)) {
+                                    Log.d(TAG, "exited USB accessory mode");
+
+                                    Intent intent = new Intent(
+                                            UsbManager.ACTION_USB_ACCESSORY_DETACHED);
+                                    intent.putExtra(UsbManager.EXTRA_ACCESSORY, mCurrentAccessory);
+                                    mContext.sendBroadcast(intent);
+                                    mCurrentAccessory = null;
+
+                                    // this will cause an immediate reset of the USB bus,
+                                    // so there is no point in sending the
+                                    // function disabled broadcast.
+                                    return;
+                                } else {
+                                    Log.e(TAG, "could not disable USB_FUNCTION_ACCESSORY");
+                                }
+                            }
 
                             final ContentResolver cr = mContext.getContentResolver();
                             if (Settings.Secure.getInt(cr,
@@ -408,4 +479,6 @@ class UsbService extends IUsbManager.Stub {
 
     private native void monitorUsbHostBus();
     private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+    private native String[] nativeGetAccessoryStrings();
+    private native ParcelFileDescriptor nativeOpenAccessory();
 }
index ef22111..192daaf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 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.
 
 #include <stdio.h>
 #include <asm/byteorder.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/usb/f_accessory.h>
+
+#define DRIVER_NAME "/dev/usb_accessory"
 
 namespace android
 {
@@ -164,10 +171,67 @@ static jobject android_server_UsbService_openDevice(JNIEnv *env, jobject thiz, j
         gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
 }
 
+static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strArray, int index)
+{
+    char buffer[256];
+
+    buffer[0] = 0;
+    int length = ioctl(fd, cmd, buffer);
+    LOGD("ioctl returned %d", length);
+    if (buffer[0]) {
+        jstring obj = env->NewStringUTF(buffer);
+        env->SetObjectArrayElement(strArray, index, obj);
+        env->DeleteLocalRef(obj);
+    }
+}
+
+
+static jobjectArray android_server_UsbService_getAccessoryStrings(JNIEnv *env, jobject thiz)
+{
+    int fd = open(DRIVER_NAME, O_RDWR);
+    if (fd < 0) {
+        LOGE("could not open %s", DRIVER_NAME);
+        return NULL;
+    }
+    jclass stringClass = env->FindClass("java/lang/String");
+    jobjectArray strArray = env->NewObjectArray(4, stringClass, NULL);
+    if (!strArray) goto out;
+    set_accessory_string(env, fd, ACCESSORY_GET_STRING_MANUFACTURER, strArray, 0);
+    set_accessory_string(env, fd, ACCESSORY_GET_STRING_MODEL, strArray, 1);
+    set_accessory_string(env, fd, ACCESSORY_GET_STRING_TYPE, strArray, 2);
+    set_accessory_string(env, fd, ACCESSORY_GET_STRING_VERSION, strArray, 3);
+
+out:
+    close(fd);
+    return strArray;
+}
+
+static jobject android_server_UsbService_openAccessory(JNIEnv *env, jobject thiz)
+{
+    int fd = open(DRIVER_NAME, O_RDWR);
+    if (fd < 0) {
+        LOGE("could not open %s", DRIVER_NAME);
+        return NULL;
+    }
+    jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass,
+        gFileDescriptorOffsets.mConstructor);
+    if (fileDescriptor != NULL) {
+        env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, fd);
+    } else {
+        return NULL;
+    }
+    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
+}
+
 static JNINativeMethod method_table[] = {
     { "monitorUsbHostBus", "()V", (void*)android_server_UsbService_monitorUsbHostBus },
     { "nativeOpenDevice",  "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
                                   (void*)android_server_UsbService_openDevice },
+    { "nativeGetAccessoryStrings", "()[Ljava/lang/String;",
+                                  (void*)android_server_UsbService_getAccessoryStrings },
+    { "nativeOpenAccessory","()Landroid/os/ParcelFileDescriptor;",
+                                  (void*)android_server_UsbService_openAccessory },
 };
 
 int register_android_server_UsbService(JNIEnv *env)