implemented synchronous audio capture

Added the infrastructure to support the synchronization of playback and
capture actions on specific events.
The first requirement for this feature is to synchronize the audio capture
start with the full rendering of a given audio content.
The applications can further be extended to other use cases
(synchronized playback start...) by adding new synchronization events and
new synchronous control methods on player or recorders.

Also added a method to query the audio session from a ToneGenerator.

Change-Id: I4e47f5108c7cbbd3bd334a7fad9b3b6c5ba55d88
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 480c3a6..2fe0b9e 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -254,7 +254,7 @@
 
 // ----------------------------------------------------------------------------
 static int
-android_media_AudioRecord_start(JNIEnv *env, jobject thiz)
+android_media_AudioRecord_start(JNIEnv *env, jobject thiz, jint event, jint triggerSession)
 {
     AudioRecord *lpRecorder =
             (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
@@ -263,7 +263,8 @@
         return AUDIORECORD_ERROR;
     }
 
-    return android_media_translateRecorderErrorCode(lpRecorder->start());
+    return android_media_translateRecorderErrorCode(
+            lpRecorder->start((AudioSystem::sync_event_t)event, triggerSession));
 }
 
 
@@ -508,7 +509,7 @@
 // ----------------------------------------------------------------------------
 static JNINativeMethod gMethods[] = {
     // name,               signature,  funcPtr
-    {"native_start",         "()I",    (void *)android_media_AudioRecord_start},
+    {"native_start",         "(II)I",    (void *)android_media_AudioRecord_start},
     {"native_stop",          "()V",    (void *)android_media_AudioRecord_stop},
     {"native_setup",         "(Ljava/lang/Object;IIIII[I)I",
                                        (void *)android_media_AudioRecord_setup},
diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp
index 31151a6..da6f1ed 100644
--- a/core/jni/android_media_ToneGenerator.cpp
+++ b/core/jni/android_media_ToneGenerator.cpp
@@ -65,6 +65,16 @@
     lpToneGen->stopTone();
 }
 
+static jint android_media_ToneGenerator_getAudioSessionId(JNIEnv *env, jobject thiz) {
+    ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz,
+            fields.context);
+    if (lpToneGen == NULL) {
+        jniThrowRuntimeException(env, "Method called after release()");
+        return 0;
+    }
+    return lpToneGen->getSessionId();
+}
+
 static void android_media_ToneGenerator_release(JNIEnv *env, jobject thiz) {
     ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz,
             fields.context);
@@ -120,6 +130,7 @@
 static JNINativeMethod gMethods[] = {
     { "startTone", "(II)Z", (void *)android_media_ToneGenerator_startTone },
     { "stopTone", "()V", (void *)android_media_ToneGenerator_stopTone },
+    { "getAudioSessionId", "()I", (void *)android_media_ToneGenerator_getAudioSessionId},
     { "release", "()V", (void *)android_media_ToneGenerator_release },
     { "native_setup", "(II)V", (void *)android_media_ToneGenerator_native_setup },
     { "native_finalize", "()V", (void *)android_media_ToneGenerator_native_finalize }
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 5cc24c0..4e112af 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -519,13 +519,34 @@
 
         // start recording
         synchronized(mRecordingStateLock) {
-            if (native_start() == SUCCESS) {
+            if (native_start(MediaSyncEvent.SYNC_EVENT_NONE, 0) == SUCCESS) {
                 mRecordingState = RECORDSTATE_RECORDING;
             }
         }
     }
 
+    /**
+     * Starts recording from the AudioRecord instance when the specified synchronization event
+     * occurs on the specified audio session.
+     * @throws IllegalStateException
+     * @param syncEvent event that triggers the capture.
+     * @see MediaSyncEvent
+     * @hide
+     */
+    public void startRecording(MediaSyncEvent syncEvent)
+    throws IllegalStateException {
+        if (mState != STATE_INITIALIZED) {
+            throw(new IllegalStateException("startRecording() called on an "
+                    +"uninitialized AudioRecord."));
+        }
 
+        // start recording
+        synchronized(mRecordingStateLock) {
+            if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) {
+                mRecordingState = RECORDSTATE_RECORDING;
+            }
+        }
+    }
 
     /**
      * Stops recording.
@@ -787,7 +808,7 @@
     
     private native final void native_release();
 
-    private native final int native_start();
+    private native final int native_start(int syncEvent, int sessionId);
 
     private native final void native_stop();
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index b5e832c..18a00bc 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -311,6 +311,10 @@
     public static final int FOR_DOCK = 3;
     private static final int NUM_FORCE_USE = 4;
 
+    // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
+    public static final int SYNC_EVENT_NONE = 0;
+    public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
+
     public static native int setDeviceConnectionState(int device, int state, String device_address);
     public static native int getDeviceConnectionState(int device, String device_address);
     public static native int setPhoneState(int state);
diff --git a/media/java/android/media/MediaSyncEvent.java b/media/java/android/media/MediaSyncEvent.java
new file mode 100644
index 0000000..d2a0735
--- /dev/null
+++ b/media/java/android/media/MediaSyncEvent.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012 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.media;
+
+/**
+ * The MediaSyncEvent class defines events that can be used to synchronize playback or capture
+ * actions between different players and recorders.
+ * <p>For instance, {@link AudioRecord#startRecording(MediaSyncEvent)} is used to start capture
+ * only when the playback on a particular audio session is complete.
+ * The audio session ID is retrieved from a player (e.g {@link MediaPlayer}, {@link AudioTrack} or
+ * {@link ToneGenerator}) by use of the getAudioSessionId() method.
+ * @hide
+ */
+public class MediaSyncEvent {
+
+    /**
+     * No sync event specified. When used with a synchronized playback or capture method, the
+     * behavior is equivalent to calling the corresponding non synchronized method.
+     */
+    public static final int SYNC_EVENT_NONE = AudioSystem.SYNC_EVENT_NONE;
+
+    /**
+     * The corresponding action is triggered only when the presentation is completed
+     * (meaning the media has been presented to the user) on the specified session.
+     * A synchronization of this type requires a source audio session ID to be set via
+     * {@link #setAudioSessionId(int) method.
+     */
+    public static final int SYNC_EVENT_PRESENTATION_COMPLETE =
+                                                    AudioSystem.SYNC_EVENT_PRESENTATION_COMPLETE;
+
+
+    /**
+     * Creates a synchronization event of the sepcified type.
+     *
+     * <p>The type specifies which kind of event is monitored.
+     * For instance, event {@link #SYNC_EVENT_PRESENTATION_COMPLETE} corresponds to the audio being
+     * presented to the user on a particular audio session.
+     * @param type the synchronization event type.
+     * @return the MediaSyncEvent created.
+     * @throws java.lang.IllegalArgumentException
+     */
+    public static MediaSyncEvent createEvent(int eventType)
+                            throws IllegalArgumentException {
+        if (!isValidType(eventType)) {
+            throw (new IllegalArgumentException(eventType
+                    + "is not a valid MediaSyncEvent type."));
+        } else {
+            return new MediaSyncEvent(eventType);
+        }
+    }
+
+    private final int mType;
+    private int mAudioSession = 0;
+
+    private MediaSyncEvent(int eventType) {
+        mType = eventType;
+    }
+
+    /**
+     * Sets the event source audio session ID.
+     *
+     * <p>The audio session ID specifies on which audio session the synchronization event should be
+     * monitored.
+     * It is mandatory for certain event types (e.g. {@link #SYNC_EVENT_PRESENTATION_COMPLETE}).
+     * For instance, the audio session ID can be retrieved via
+     * {@link MediaPlayer#getAudioSessionId()} when monitoring an event on a particular MediaPlayer.
+     * @param audioSessionId the audio session ID of the event source being monitored.
+     * @return the MediaSyncEvent the method is called on.
+     * @throws java.lang.IllegalArgumentException
+     */
+    public MediaSyncEvent setAudioSessionId(int audioSessionId)
+            throws IllegalArgumentException {
+        if (audioSessionId > 0) {
+            mAudioSession = audioSessionId;
+        } else {
+            throw (new IllegalArgumentException(audioSessionId + " is not a valid session ID."));
+        }
+        return this;
+    }
+
+    /**
+     * Gets the synchronization event type.
+     *
+     * @return the synchronization event type.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Gets the synchronization event audio session ID.
+     *
+     * @return the synchronization audio session ID. The returned audio session ID is 0 if it has
+     * not been set.
+     */
+    public int getAudioSessionId() {
+        return mAudioSession;
+    }
+
+    private static boolean isValidType(int type) {
+        switch (type) {
+        case SYNC_EVENT_NONE:
+        case SYNC_EVENT_PRESENTATION_COMPLETE:
+            return true;
+        default:
+            return false;
+        }
+    }
+}
diff --git a/media/java/android/media/ToneGenerator.java b/media/java/android/media/ToneGenerator.java
index d232265..4907a13 100644
--- a/media/java/android/media/ToneGenerator.java
+++ b/media/java/android/media/ToneGenerator.java
@@ -875,6 +875,15 @@
 
     private native final void native_finalize();
 
+    /**
+    * Returns the audio session ID.
+    *
+    * @return the ID of the audio session this ToneGenerator belongs to or 0 if an error
+    * occured.
+    * @hide
+    */
+    public native final int getAudioSessionId();
+
     @Override
     protected void finalize() { native_finalize(); }