am 946863b6: Merge "Fix USB audio disconnect logic" into lmp-mr1-dev
automerge: 61cf7e0

* commit '61cf7e0fe7e79df53ff00a25241041ec99096105':
  Fix USB audio disconnect logic
diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java
new file mode 100644
index 0000000..bb45ee8
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbAudioManager.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.alsa.AlsaCardsParser;
+import android.alsa.AlsaDevicesParser;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.media.AudioManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * UsbAudioManager manages USB audio devices.
+ */
+public class UsbAudioManager {
+    private static final String TAG = UsbAudioManager.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+
+    private final class AudioDevice {
+        public int mCard;
+        public int mDevice;
+        public boolean mHasPlayback;
+        public boolean mHasCapture;
+        public boolean mHasMIDI;
+
+        public AudioDevice(int card, int device,
+                boolean hasPlayback, boolean hasCapture, boolean hasMidi) {
+            mCard = card;
+            mDevice = device;
+            mHasPlayback = hasPlayback;
+            mHasCapture = hasCapture;
+            mHasMIDI = hasMidi;
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("AudioDevice: [card: " + mCard);
+            sb.append(", device: " + mDevice);
+            sb.append(", hasPlayback: " + mHasPlayback);
+            sb.append(", hasCapture: " + mHasCapture);
+            sb.append(", hasMidi: " + mHasMIDI);
+            sb.append("]");
+            return sb.toString();
+        }
+    }
+
+    private final HashMap<UsbDevice,AudioDevice> mAudioDevices
+            = new HashMap<UsbDevice,AudioDevice>();
+
+    /* package */ UsbAudioManager(Context context) {
+        mContext = context;
+    }
+
+    // Broadcasts the arrival/departure of a USB audio interface
+    // audioDevice - the AudioDevice that was added or removed
+    // enabled - if true, we're connecting a device (it's arrived), else disconnecting
+    private void sendDeviceNotification(AudioDevice audioDevice, boolean enabled) {
+        // send a sticky broadcast containing current USB state
+        Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        intent.putExtra("state", enabled ? 1 : 0);
+        intent.putExtra("card", audioDevice.mCard);
+        intent.putExtra("device", audioDevice.mDevice);
+        intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
+        intent.putExtra("hasCapture", audioDevice.mHasCapture);
+        intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private boolean waitForAlsaFile(int card, int device, boolean capture) {
+        // These values were empirically determined.
+        final int kNumRetries = 5;
+        final int kSleepTime = 500; // ms
+        String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
+        File alsaDevFile = new File(alsaDevPath);
+        boolean exists = false;
+        for (int retry = 0; !exists && retry < kNumRetries; retry++) {
+            exists = alsaDevFile.exists();
+            if (!exists) {
+                try {
+                    Thread.sleep(kSleepTime);
+                } catch (IllegalThreadStateException ex) {
+                    Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
+                } catch (java.lang.InterruptedException ex) {
+                    Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
+                }
+            }
+        }
+
+        return exists;
+    }
+
+    /* package */ void deviceAdded(UsbDevice usbDevice) {
+        // Is there an audio interface in there?
+        boolean isAudioDevice = false;
+
+        // FIXME - handle multiple configurations?
+        int interfaceCount = usbDevice.getInterfaceCount();
+        for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
+                ntrfaceIndex++) {
+            UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
+            if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
+                isAudioDevice = true;
+            }
+        }
+        if (!isAudioDevice) {
+            return;
+        }
+
+        //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
+        // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not
+        // clear why this works, or that it can be relied on going forward.  Needs further
+        // research.
+        AlsaCardsParser cardsParser = new AlsaCardsParser();
+        cardsParser.scan();
+        // cardsParser.Log();
+
+        // But we need to parse the device to determine its capabilities.
+        AlsaDevicesParser devicesParser = new AlsaDevicesParser();
+        devicesParser.scan();
+        // devicesParser.Log();
+
+        // The protocol for now will be to select the last-connected (highest-numbered)
+        // Alsa Card.
+        int card = cardsParser.getNumCardRecords() - 1;
+        int device = 0;
+
+        boolean hasPlayback = devicesParser.hasPlaybackDevices(card);
+        boolean hasCapture = devicesParser.hasCaptureDevices(card);
+        boolean hasMidi = devicesParser.hasMIDIDevices(card);
+
+        // Playback device file needed/present?
+        if (hasPlayback &&
+            !waitForAlsaFile(card, device, false)) {
+            return;
+        }
+
+        // Capture device file needed/present?
+        if (hasCapture &&
+            !waitForAlsaFile(card, device, true)) {
+            return;
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
+        }
+
+        AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
+        mAudioDevices.put(usbDevice, audioDevice);
+        sendDeviceNotification(audioDevice, true);
+    }
+
+    /* package */ void deviceRemoved(UsbDevice device) {
+       if (DEBUG) {
+          Slog.d(TAG, "deviceRemoved(): " + device);
+        }
+
+        AudioDevice audioDevice = mAudioDevices.remove(device);
+        if (audioDevice != null) {
+            sendDeviceNotification(audioDevice, false);
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw) {
+        pw.println("  USB AudioDevices:");
+        for (UsbDevice device : mAudioDevices.keySet()) {
+            pw.println("    " + device.getDeviceName() + ": " + mAudioDevices.get(device));
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 06febb3..e769bda 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -16,8 +16,6 @@
 
 package com.android.server.usb;
 
-import android.alsa.AlsaCardsParser;
-import android.alsa.AlsaDevicesParser;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.usb.UsbConfiguration;
@@ -25,16 +23,13 @@
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbEndpoint;
 import android.hardware.usb.UsbInterface;
-import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
-import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.PrintWriter;
@@ -46,11 +41,12 @@
  */
 public class UsbHostManager {
     private static final String TAG = UsbHostManager.class.getSimpleName();
-    private static final boolean DEBUG_AUDIO = false;
+    private static final boolean DEBUG = false;
 
     // contains all connected USB devices
     private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>();
 
+
     // USB busses to exclude from USB host support
     private final String[] mHostBlacklist;
 
@@ -64,14 +60,7 @@
     private ArrayList<UsbInterface> mNewInterfaces;
     private ArrayList<UsbEndpoint> mNewEndpoints;
 
-    // Attributes of any connected USB audio device.
-    //TODO(pmclean) When we extend to multiple, USB Audio devices, we will need to get
-    // more clever about this.
-    private int mConnectedUsbCard = -1;
-    private int mConnectedUsbDeviceNum = -1;
-    private boolean mConnectedHasPlayback = false;
-    private boolean mConnectedHasCapture = false;
-    private boolean mConnectedHasMIDI = false;
+    private UsbAudioManager mUsbAudioManager;
 
     @GuardedBy("mLock")
     private UsbSettingsManager mCurrentSettings;
@@ -80,6 +69,7 @@
         mContext = context;
         mHostBlacklist = context.getResources().getStringArray(
                 com.android.internal.R.array.config_usbHostBlacklist);
+        mUsbAudioManager = new UsbAudioManager(context);
     }
 
     public void setCurrentSettings(UsbSettingsManager settings) {
@@ -118,48 +108,6 @@
         return false;
     }
 
-    // Broadcasts the arrival/departure of a USB audio interface
-    // card - the ALSA card number of the physical interface
-    // device - the ALSA device number of the physical interface
-    // enabled - if true, we're connecting a device (it's arrived), else disconnecting
-    private void sendDeviceNotification(int card, int device, boolean enabled,
-            boolean hasPlayback, boolean hasCapture, boolean hasMIDI) {
-        // send a sticky broadcast containing current USB state
-        Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        intent.putExtra("state", enabled ? 1 : 0);
-        intent.putExtra("card", card);
-        intent.putExtra("device", device);
-        intent.putExtra("hasPlayback", hasPlayback);
-        intent.putExtra("hasCapture", hasCapture);
-        intent.putExtra("hasMIDI", hasMIDI);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private boolean waitForAlsaFile(int card, int device, boolean capture) {
-        // These values were empirically determined.
-        final int kNumRetries = 5;
-        final int kSleepTime = 500; // ms
-        String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
-        File alsaDevFile = new File(alsaDevPath);
-        boolean exists = false;
-        for (int retry = 0; !exists && retry < kNumRetries; retry++) {
-            exists = alsaDevFile.exists();
-            if (!exists) {
-                try {
-                    Thread.sleep(kSleepTime);
-                } catch (IllegalThreadStateException ex) {
-                    Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
-                } catch (java.lang.InterruptedException ex) {
-                    Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
-                }
-            }
-        }
-
-        return exists;
-    }
-
     /* Called from JNI in monitorUsbHostBus() to report new USB devices
        Returns true if successful, in which case the JNI code will continue adding configurations,
        interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
@@ -169,7 +117,7 @@
             int deviceClass, int deviceSubclass, int deviceProtocol,
             String manufacturerName, String productName, String serialNumber) {
 
-        if (DEBUG_AUDIO) {
+        if (DEBUG) {
             Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
             // Audio Class Codes:
             // Audio: 0x01
@@ -254,7 +202,7 @@
 
     /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
     private void endUsbDeviceAdded() {
-        if (DEBUG_AUDIO) {
+        if (DEBUG) {
             Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
         }
         if (mNewInterface != null) {
@@ -266,16 +214,6 @@
                     mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
         }
 
-        // Is there an audio interface in there?
-        final int kUsbClassId_Audio = 0x01;
-        boolean isAudioDevice = false;
-        for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < mNewInterfaces.size();
-                ntrfaceIndex++) {
-            UsbInterface ntrface = mNewInterfaces.get(ntrfaceIndex);
-            if (ntrface.getInterfaceClass() == kUsbClassId_Audio) {
-                isAudioDevice = true;
-            }
-        }
 
         synchronized (mLock) {
             if (mNewDevice != null) {
@@ -284,6 +222,7 @@
                 mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
                 Slog.d(TAG, "Added device " + mNewDevice);
                 getCurrentSettings().deviceAttached(mNewDevice);
+                mUsbAudioManager.deviceAdded(mNewDevice);
             } else {
                 Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
             }
@@ -292,81 +231,14 @@
             mNewInterfaces = null;
             mNewEndpoints = null;
         }
-
-        if (!isAudioDevice) {
-            return; // bail
-        }
-
-        //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
-        // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not
-        // clear why this works, or that it can be relied on going forward.  Needs further
-        // research.
-        AlsaCardsParser cardsParser = new AlsaCardsParser();
-        cardsParser.scan();
-        // cardsParser.Log();
-
-        // But we need to parse the device to determine its capabilities.
-        AlsaDevicesParser devicesParser = new AlsaDevicesParser();
-        devicesParser.scan();
-        // devicesParser.Log();
-
-        // The protocol for now will be to select the last-connected (highest-numbered)
-        // Alsa Card.
-        mConnectedUsbCard = cardsParser.getNumCardRecords() - 1;
-        mConnectedUsbDeviceNum = 0;
-
-        mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard);
-        mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard);
-        mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard);
-
-        // Playback device file needed/present?
-        if (mConnectedHasPlayback &&
-            !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) {
-            return;
-        }
-
-        // Capture device file needed/present?
-        if (mConnectedHasCapture &&
-            !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, true)) {
-            return;
-        }
-
-        if (DEBUG_AUDIO) {
-            Slog.d(TAG,
-                    "usb: hasPlayback:" + mConnectedHasPlayback + " hasCapture:" + mConnectedHasCapture);
-        }
-
-        sendDeviceNotification(mConnectedUsbCard,
-                mConnectedUsbDeviceNum,
-                true,
-                mConnectedHasPlayback,
-                mConnectedHasCapture,
-                mConnectedHasMIDI);
     }
 
     /* Called from JNI in monitorUsbHostBus to report USB device removal */
     private void usbDeviceRemoved(String deviceName) {
-        if (DEBUG_AUDIO) {
-          Slog.d(TAG, "usb:UsbHostManager.usbDeviceRemoved() nm:" + deviceName);
-        }
-
-        if (mConnectedUsbCard != -1 && mConnectedUsbDeviceNum != -1) {
-            sendDeviceNotification(mConnectedUsbCard,
-                    mConnectedUsbDeviceNum,
-                    false,
-                    mConnectedHasPlayback,
-                    mConnectedHasCapture,
-                    mConnectedHasMIDI);
-            mConnectedUsbCard = -1;
-            mConnectedUsbDeviceNum = -1;
-            mConnectedHasPlayback = false;
-            mConnectedHasCapture = false;
-            mConnectedHasMIDI = false;
-        }
-
         synchronized (mLock) {
             UsbDevice device = mDevices.remove(deviceName);
             if (device != null) {
+                mUsbAudioManager.deviceRemoved(device);
                 getCurrentSettings().deviceDetached(device);
             }
         }
@@ -418,6 +290,7 @@
                 pw.println("    " + name + ": " + mDevices.get(name));
             }
         }
+        mUsbAudioManager.dump(fd, pw);
     }
 
     private native void monitorUsbHostBus();