Audio record notification: report audio device information

Support querying the AudioDeviceInfo in AudioRecordConfiguration.
When AudioService (through RecordingActivityMonitor) receives
  a recording event on an existing session, report it as an
  update if the recording configuration has changed.

Bug 22876530

Change-Id: I1b72c08aa0589077fe8ad254087965e6384ce50a
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 7d9e4a2..2fb7498 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -389,7 +389,8 @@
 
 static void
 android_media_AudioSystem_recording_callback(int event, int session, int source,
-        const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig)
+        const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig,
+        audio_patch_handle_t patchHandle)
 {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     if (env == NULL) {
@@ -401,12 +402,14 @@
     }
 
     // create an array for 2*3 integers to store the record configurations (client + device)
-    jintArray recParamArray = env->NewIntArray(6);
+    //                 plus 1 integer for the patch handle
+    const int REC_PARAM_SIZE = 7;
+    jintArray recParamArray = env->NewIntArray(REC_PARAM_SIZE);
     if (recParamArray == NULL) {
         ALOGE("recording callback: Couldn't allocate int array for configuration data");
         return;
     }
-    jint recParamData[6];
+    jint recParamData[REC_PARAM_SIZE];
     recParamData[0] = (jint) audioFormatFromNative(clientConfig->format);
     // FIXME this doesn't support index-based masks
     recParamData[1] = (jint) inChannelMaskFromNative(clientConfig->channel_mask);
@@ -415,7 +418,8 @@
     // FIXME this doesn't support index-based masks
     recParamData[4] = (jint) inChannelMaskFromNative(deviceConfig->channel_mask);
     recParamData[5] = (jint) deviceConfig->sample_rate;
-    env->SetIntArrayRegion(recParamArray, 0, 6, recParamData);
+    recParamData[6] = (jint) patchHandle;
+    env->SetIntArrayRegion(recParamArray, 0, REC_PARAM_SIZE, recParamData);
 
     // callback into java
     jclass clazz = env->FindClass(kClassPathName);
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 22f4f04..0c8c530 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -877,6 +877,26 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        AudioFormat that = (AudioFormat) o;
+
+        if (mPropertySetMask != that.mPropertySetMask) return false;
+
+        // return false if any of the properties is set and the values differ
+        return !((((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0)
+                            && (mEncoding != that.mEncoding))
+                    || (((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
+                            && (mSampleRate != that.mSampleRate))
+                    || (((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0)
+                            && (mChannelMask != that.mChannelMask))
+                    || (((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0)
+                            && (mChannelIndexMask != that.mChannelIndexMask)));
+    }
+
+    @Override
     public int hashCode() {
         return Objects.hash(mPropertySetMask, mSampleRate, mEncoding, mChannelMask,
                 mChannelIndexMask);
diff --git a/media/java/android/media/AudioPatch.java b/media/java/android/media/AudioPatch.java
index acadb41..6c70213 100644
--- a/media/java/android/media/AudioPatch.java
+++ b/media/java/android/media/AudioPatch.java
@@ -53,6 +53,13 @@
         return mSinks;
     }
 
+    /**
+     * Get the system unique patch ID.
+     */
+    public int id() {
+        return mHandle.id();
+    }
+
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
diff --git a/media/java/android/media/AudioRecordConfiguration.java b/media/java/android/media/AudioRecordConfiguration.java
index 61d239c..c2cd9b3 100644
--- a/media/java/android/media/AudioRecordConfiguration.java
+++ b/media/java/android/media/AudioRecordConfiguration.java
@@ -18,7 +18,9 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -28,6 +30,7 @@
  *
  */
 public class AudioRecordConfiguration implements Parcelable {
+    private final static String TAG = new String("AudioRecordConfiguration");
 
     private final int mSessionId;
 
@@ -36,30 +39,18 @@
     private final AudioFormat mDeviceFormat;
     private final AudioFormat mClientFormat;
 
-    private final AudioDeviceInfo mRecDevice;
-
-    /**
-     * @hide
-     */
-    public AudioRecordConfiguration(int session, int source,
-            AudioFormat clientFormat, AudioFormat deviceFormat) {
-        mSessionId = session;
-        mClientSource = source;
-        mDeviceFormat = deviceFormat;
-        mClientFormat = clientFormat;
-        mRecDevice = null;
-    }
+    private final int mPatchHandle;
 
     /**
      * @hide
      */
     public AudioRecordConfiguration(int session, int source, AudioFormat devFormat,
-            AudioFormat clientFormat, AudioDeviceInfo device) {
+            AudioFormat clientFormat, int patchHandle) {
         mSessionId = session;
         mClientSource = source;
         mDeviceFormat = devFormat;
         mClientFormat = clientFormat;
-        mRecDevice = device;
+        mPatchHandle = patchHandle;
     }
 
     /**
@@ -96,10 +87,38 @@
     public AudioFormat getClientFormat() { return mClientFormat; }
 
     /**
-     * Returns the audio input device used for this recording.
-     * @return the audio recording device
+     * Returns information about the audio input device used for this recording.
+     * @return the audio recording device or null if this information cannot be retrieved
      */
-    public AudioDeviceInfo getAudioDevice() { return mRecDevice; }
+    public AudioDeviceInfo getAudioDevice() {
+        // build the AudioDeviceInfo from the patch handle
+        ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
+        if (AudioManager.listAudioPatches(patches) != AudioManager.SUCCESS) {
+            Log.e(TAG, "Error retrieving list of audio patches");
+            return null;
+        }
+        for (int i = 0 ; i < patches.size() ; i++) {
+            final AudioPatch patch = patches.get(i);
+            if (patch.id() == mPatchHandle) {
+                final AudioPortConfig[] sources = patch.sources();
+                if ((sources != null) && (sources.length > 0)) {
+                    // not supporting multiple sources, so just look at the first source
+                    final int devId = sources[0].port().id();
+                    final AudioDeviceInfo[] devices =
+                            AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
+                    for (int j = 0; j < devices.length; j++) {
+                        if (devices[j].getId() == devId) {
+                            return devices[j];
+                        }
+                    }
+                }
+                // patch handle is unique, there won't be another with the same handle
+                break;
+            }
+        }
+        Log.e(TAG, "Couldn't find device for recording, did recording end already?");
+        return null;
+    }
 
     public static final Parcelable.Creator<AudioRecordConfiguration> CREATOR
             = new Parcelable.Creator<AudioRecordConfiguration>() {
@@ -132,7 +151,7 @@
         dest.writeInt(mClientSource);
         mClientFormat.writeToParcel(dest, 0);
         mDeviceFormat.writeToParcel(dest, 0);
-        //TODO marshall device info
+        dest.writeInt(mPatchHandle);
     }
 
     private AudioRecordConfiguration(Parcel in) {
@@ -140,8 +159,7 @@
         mClientSource = in.readInt();
         mClientFormat = AudioFormat.CREATOR.createFromParcel(in);
         mDeviceFormat = AudioFormat.CREATOR.createFromParcel(in);
-        //TODO unmarshall device info
-        mRecDevice = null;
+        mPatchHandle = in.readInt();
     }
 
     @Override
@@ -149,8 +167,12 @@
         if (this == o) return true;
         if (o == null || !(o instanceof AudioRecordConfiguration)) return false;
 
-        final AudioRecordConfiguration that = (AudioRecordConfiguration) o;
-         return ((mSessionId == that.mSessionId)
-                 && (mClientSource == that.mClientSource));
+        AudioRecordConfiguration that = (AudioRecordConfiguration) o;
+
+        return ((mSessionId == that.mSessionId)
+                && (mClientSource == that.mClientSource)
+                && (mPatchHandle == that.mPatchHandle)
+                && (mClientFormat.equals(that.mClientFormat))
+                && (mDeviceFormat.equals(that.mDeviceFormat)));
     }
 }
\ No newline at end of file
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 59782cb..247c4ae 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -279,8 +279,14 @@
          * @param session
          * @param source
          * @param recordingFormat an array of ints containing respectively the client and device
-         *    recording configuration. Each set of parameters contains the following parameters
-         *    in this order: format, channel mask, sample rate
+         *    recording configurations (2*3 ints), followed by the patch handle:
+         *    index 0: client format
+         *          1: client channel mask
+         *          2: client sample rate
+         *          3: device format
+         *          4: device channel mask
+         *          5: device sample rate
+         *          6: patch handle
          */
         void onRecordingConfigurationChanged(int event, int session, int source,
                 int[] recordingFormat);
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 4b0a142..7e76ac4 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -50,11 +50,11 @@
      * Implementation of android.media.AudioSystem.AudioRecordingCallback
      */
     public void onRecordingConfigurationChanged(int event, int session, int source,
-            int[] recordingFormat) {
+            int[] recordingInfo) {
         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
             return;
         }
-        if (updateSnapshot(event, session, source, recordingFormat)) {
+        if (updateSnapshot(event, session, source, recordingInfo)) {
             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
             synchronized(mClients) {
                 while (clientIterator.hasNext()) {
@@ -117,7 +117,7 @@
      *     for the definition of the contents of the array
      * @return true if the list of active recording sessions has been modified, false otherwise.
      */
-    private boolean updateSnapshot(int event, int session, int source, int[] recordingFormat) {
+    private boolean updateSnapshot(int event, int session, int source, int[] recordingInfo) {
         synchronized(mRecordConfigs) {
             switch (event) {
             case AudioManager.RECORD_CONFIG_EVENT_STOP:
@@ -125,26 +125,35 @@
                 return (mRecordConfigs.remove(new Integer(session)) != null);
             case AudioManager.RECORD_CONFIG_EVENT_START:
                 final AudioFormat clientFormat = new AudioFormat.Builder()
-                        .setEncoding(recordingFormat[0])
+                        .setEncoding(recordingInfo[0])
                         // FIXME this doesn't support index-based masks
-                        .setChannelMask(recordingFormat[1])
-                        .setSampleRate(recordingFormat[2])
+                        .setChannelMask(recordingInfo[1])
+                        .setSampleRate(recordingInfo[2])
                         .build();
                 final AudioFormat deviceFormat = new AudioFormat.Builder()
-                        .setEncoding(recordingFormat[3])
+                        .setEncoding(recordingInfo[3])
                         // FIXME this doesn't support index-based masks
-                        .setChannelMask(recordingFormat[4])
-                        .setSampleRate(recordingFormat[5])
+                        .setChannelMask(recordingInfo[4])
+                        .setSampleRate(recordingInfo[5])
                         .build();
-                if (mRecordConfigs.containsKey(new Integer(session))) {
-                    // start of session that's already tracked, not worth an update
-                    // TO DO in the future when tracking record format: there might be a record
-                    //       format change during a recording that requires reporting
-                    return false;
-                } else {
-                    mRecordConfigs.put(new Integer(session),
+                final int patchHandle = recordingInfo[6];
+                final Integer sessionKey = new Integer(session);
+                if (mRecordConfigs.containsKey(sessionKey)) {
+                    final AudioRecordConfiguration updatedConfig =
                             new AudioRecordConfiguration(session, source,
-                                    clientFormat, deviceFormat));
+                                    clientFormat, deviceFormat, patchHandle);
+                    if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
+                        return false;
+                    } else {
+                        // config exists but has been modified
+                        mRecordConfigs.remove(sessionKey);
+                        mRecordConfigs.put(sessionKey, updatedConfig);
+                        return true;
+                    }
+                } else {
+                    mRecordConfigs.put(sessionKey,
+                            new AudioRecordConfiguration(session, source,
+                                    clientFormat, deviceFormat, patchHandle));
                     return true;
                 }
             default: