Merge "Unhide audio recording notification API"
diff --git a/api/current.txt b/api/current.txt
index 9a5ffdc..4f801cb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19297,6 +19297,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -19319,6 +19320,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -19342,6 +19344,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -19438,6 +19441,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioRecordingCallback {
+    ctor public AudioManager.AudioRecordingCallback();
+    method public void onRecordConfigChanged();
+  }
+
   public static abstract interface AudioManager.OnAudioFocusChangeListener {
     method public abstract void onAudioFocusChange(int);
   }
@@ -19508,6 +19516,14 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public class AudioRecordConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioSessionId();
+    method public int getClientAudioSource();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+  }
+
   public abstract interface AudioRouting {
     method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public abstract android.media.AudioDeviceInfo getPreferredDevice();
diff --git a/api/system-current.txt b/api/system-current.txt
index 3bafa01..47a51d4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -20623,6 +20623,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -20647,6 +20648,7 @@
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
     method public int registerAudioPolicy(android.media.audiopolicy.AudioPolicy);
+    method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -20673,6 +20675,7 @@
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
     method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
+    method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -20772,6 +20775,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioRecordingCallback {
+    ctor public AudioManager.AudioRecordingCallback();
+    method public void onRecordConfigChanged();
+  }
+
   public static abstract interface AudioManager.OnAudioFocusChangeListener {
     method public abstract void onAudioFocusChange(int);
   }
@@ -20845,6 +20853,14 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public class AudioRecordConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioSessionId();
+    method public int getClientAudioSource();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+  }
+
   public abstract interface AudioRouting {
     method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public abstract android.media.AudioDeviceInfo getPreferredDevice();
diff --git a/api/test-current.txt b/api/test-current.txt
index 5f76739..6b5b8b0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -19305,6 +19305,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -19327,6 +19328,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -19350,6 +19352,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
     method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -19446,6 +19449,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioRecordingCallback {
+    ctor public AudioManager.AudioRecordingCallback();
+    method public void onRecordConfigChanged();
+  }
+
   public static abstract interface AudioManager.OnAudioFocusChangeListener {
     method public abstract void onAudioFocusChange(int);
   }
@@ -19516,6 +19524,14 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public class AudioRecordConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioSessionId();
+    method public int getClientAudioSource();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+  }
+
   public abstract interface AudioRouting {
     method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public abstract android.media.AudioDeviceInfo getPreferredDevice();
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5ad6b08..dc534be 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2163,7 +2163,7 @@
      * audio service.
      */
     private final ServiceEventHandlerDelegate mServiceEventHandlerDelegate =
-            new ServiceEventHandlerDelegate();
+            new ServiceEventHandlerDelegate(null);
 
     /**
      * Event types
@@ -2177,10 +2177,14 @@
     private class ServiceEventHandlerDelegate {
         private final Handler mHandler;
 
-        ServiceEventHandlerDelegate() {
+        ServiceEventHandlerDelegate(Handler handler) {
             Looper looper;
-            if ((looper = Looper.myLooper()) == null) {
-                looper = Looper.getMainLooper();
+            if (handler == null) {
+                if ((looper = Looper.myLooper()) == null) {
+                    looper = Looper.getMainLooper();
+                }
+            } else {
+                looper = handler.getLooper();
             }
 
             if (looper != null) {
@@ -2201,27 +2205,9 @@
                                 }
                                 break;
                             case MSSG_RECORDING_CONFIG_CHANGE:
-                                // optimizing for the case of a single callback
-                                AudioRecordingCallback singleCallback = null;
-                                ArrayList<AudioRecordingCallback> multipleCallbacks = null;
-                                synchronized(mRecordCallbackLock) {
-                                    if ((mRecordCallbackList != null)
-                                            && (mRecordCallbackList.size() != 0)) {
-                                        if (mRecordCallbackList.size() == 1) {
-                                            singleCallback = mRecordCallbackList.get(0);
-                                        } else {
-                                            multipleCallbacks =
-                                                    new ArrayList<AudioRecordingCallback>(
-                                                            mRecordCallbackList);
-                                        }
-                                    }
-                                }
-                                if (singleCallback != null) {
-                                    singleCallback.onRecordConfigChanged();
-                                } else if (multipleCallbacks != null) {
-                                    for (int i=0 ; i < multipleCallbacks.size() ; i++) {
-                                        multipleCallbacks.get(i).onRecordConfigChanged();
-                                    }
+                                final AudioRecordingCallback cb = (AudioRecordingCallback) msg.obj;
+                                if (cb != null) {
+                                    cb.onRecordConfigChanged();
                                 }
                                 break;
                             default:
@@ -2798,34 +2784,51 @@
     //====================================================================
     // Recording configuration
     /**
-     * @hide
-     * candidate for public API
+     * Interface for receiving update notifications about the recording configuration. Extend
+     * this abstract class and register it with
+     * {@link AudioManager#registerAudioRecordingCallback(AudioRecordingCallback, Handler)}
+     * to be notified.
+     * Use {@link AudioManager#getActiveRecordConfigurations()} to query the current configuration.
      */
     public static abstract class AudioRecordingCallback {
         /**
-         * @hide
-         * candidate for public API
+         * Called whenever the device recording configuration has changed.
          */
         public void onRecordConfigChanged() {}
     }
 
+    private static class AudioRecordingCallbackInfo {
+        final AudioRecordingCallback mCb;
+        final Handler mHandler;
+        AudioRecordingCallbackInfo(AudioRecordingCallback cb, Handler handler) {
+            mCb = cb;
+            mHandler = handler;
+        }
+    }
+
     /**
-     * @hide
-     * candidate for public API
-     * @param non-null callback
+     * Register a callback to be notified of audio recording changes through
+     * {@link AudioRecordingCallback}
+     * @param cb non-null callback to register
+     * @param handler the {@link Handler} object for the thread on which to execute
+     * the callback. If <code>null</code>, the {@link Handler} associated with the main
+     * {@link Looper} will be used.
      */
-    public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
+    public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb, Handler handler)
+    {
         if (cb == null) {
             throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument");
         }
+
         synchronized(mRecordCallbackLock) {
             // lazy initialization of the list of recording callbacks
             if (mRecordCallbackList == null) {
-                mRecordCallbackList = new ArrayList<AudioRecordingCallback>();
+                mRecordCallbackList = new ArrayList<AudioRecordingCallbackInfo>();
             }
             final int oldCbCount = mRecordCallbackList.size();
-            if (!mRecordCallbackList.contains(cb)) {
-                mRecordCallbackList.add(cb);
+            if (!hasRecordCallback_sync(cb)) {
+                mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb,
+                        new ServiceEventHandlerDelegate(handler).getHandler()));
                 final int newCbCount = mRecordCallbackList.size();
                 if ((oldCbCount == 0) && (newCbCount > 0)) {
                     // register binder for callbacks
@@ -2844,9 +2847,9 @@
     }
 
     /**
-     * @hide
-     * candidate for public API
-     * @param non-null callback
+     * Unregister an audio recording callback previously registered with
+     * {@link #registerAudioRecordingCallback(AudioRecordingCallback, Handler)}.
+     * @param cb non-null callback to unregister
      */
     public void unregisterAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
         if (cb == null) {
@@ -2857,7 +2860,7 @@
                 return;
             }
             final int oldCbCount = mRecordCallbackList.size();
-            if (mRecordCallbackList.remove(cb)) {
+            if (removeRecordCallback_sync(cb)) {
                 final int newCbCount = mRecordCallbackList.size();
                 if ((oldCbCount > 0) && (newCbCount == 0)) {
                     // unregister binder for callbacks
@@ -2876,8 +2879,7 @@
     }
 
     /**
-     * @hide
-     * candidate for public API
+     * Returns the current active audio recording configurations of the device.
      * @return a non-null array of recording configurations. An array of length 0 indicates there is
      *     no recording active when queried.
      */
@@ -2902,18 +2904,57 @@
 
     /**
      * All operations on this list are sync'd on mRecordCallbackLock.
-     * List is lazy-initialized in {@link #registerAudioRecordingCallback(AudioRecordingCallback)}.
+     * List is lazy-initialized in
+     * {@link #registerAudioRecordingCallback(AudioRecordingCallback, Handler)}.
      * List can be null.
      */
-    private List<AudioRecordingCallback> mRecordCallbackList;
+    private List<AudioRecordingCallbackInfo> mRecordCallbackList;
     private final Object mRecordCallbackLock = new Object();
 
+    /**
+     * Must be called synchronized on mRecordCallbackLock
+     */
+    private boolean hasRecordCallback_sync(@NonNull AudioRecordingCallback cb) {
+        if (mRecordCallbackList != null) {
+            for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+                if (cb.equals(mRecordCallbackList.get(i).mCb)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Must be called synchronized on mRecordCallbackLock
+     */
+    private boolean removeRecordCallback_sync(@NonNull AudioRecordingCallback cb) {
+        if (mRecordCallbackList != null) {
+            for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+                if (cb.equals(mRecordCallbackList.get(i).mCb)) {
+                    mRecordCallbackList.remove(i);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() {
 
         public void dispatchRecordingConfigChange() {
-            final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
-                    MSSG_RECORDING_CONFIG_CHANGE/*what*/);
-            mServiceEventHandlerDelegate.getHandler().sendMessage(m);
+            synchronized(mRecordCallbackLock) {
+                if (mRecordCallbackList != null) {
+                    for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+                        final AudioRecordingCallbackInfo arci = mRecordCallbackList.get(i);
+                        if (arci.mHandler != null) {
+                            final Message m = arci.mHandler.obtainMessage(
+                                    MSSG_RECORDING_CONFIG_CHANGE/*what*/, arci.mCb/*obj*/);
+                            arci.mHandler.sendMessage(m);
+                        }
+                    }
+                }
+            }
         }
 
     };
diff --git a/media/java/android/media/AudioRecordConfiguration.java b/media/java/android/media/AudioRecordConfiguration.java
index aefe692..c7d219d 100644
--- a/media/java/android/media/AudioRecordConfiguration.java
+++ b/media/java/android/media/AudioRecordConfiguration.java
@@ -22,8 +22,9 @@
 import java.util.Objects;
 
 /**
- * @hide
- * Candidate for public API, see AudioManager.getActiveRecordConfiguration()
+ * The AudioRecordConfiguration class collects the information describing an audio recording
+ * session. This information is returned through the 
+ * {@link AudioManager#getActiveRecordConfigurations()} method.
  *
  */
 public class AudioRecordConfiguration implements Parcelable {
@@ -41,19 +42,23 @@
     }
 
     /**
-     * @return one of AudioSource.MIC, AudioSource.VOICE_UPLINK,
-     *       AudioSource.VOICE_DOWNLINK, AudioSource.VOICE_CALL,
-     *       AudioSource.CAMCORDER, AudioSource.VOICE_RECOGNITION,
-     *       AudioSource.VOICE_COMMUNICATION.
+     * Returns the audio source being used for the recording.
+     * @return one of {@link MediaRecorder.AudioSource#MIC},
+     *       {@link MediaRecorder.AudioSource#VOICE_UPLINK},
+     *       {@link MediaRecorder.AudioSource#VOICE_DOWNLINK},
+     *       {@link MediaRecorder.AudioSource#VOICE_CALL},
+     *       {@link MediaRecorder.AudioSource#CAMCORDER},
+     *       {@link MediaRecorder.AudioSource#VOICE_RECOGNITION},
+     *       {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
      */
     public int getClientAudioSource() { return mClientSource; }
 
     /**
-     * @return the session number of the recorder.
+     * Returns the session number of the recording, see {@link AudioRecord#getAudioSessionId()}.
+     * @return the session number.
      */
     public int getAudioSessionId() { return mSessionId; }
 
-
     public static final Parcelable.Creator<AudioRecordConfiguration> CREATOR
             = new Parcelable.Creator<AudioRecordConfiguration>() {
         /**