am 3d6e8f23: am ed3ee3c1: Merge "    [FM] Fix 18847131 There is no sound output from BT earphone while BT earphone is connected" into lmp-mr1-dev

* commit '3d6e8f233a41363b7df0c8854261ad5de30d6ab5':
  [FM] Fix 18847131 There is no sound output from BT earphone while BT earphone is connected
diff --git a/src/com/android/fmradio/FmService.java b/src/com/android/fmradio/FmService.java
index e84ad99..c00e8cf 100644
--- a/src/com/android/fmradio/FmService.java
+++ b/src/com/android/fmradio/FmService.java
@@ -100,7 +100,7 @@
     private static final int NOTIFICATION_ID = 1;
 
     // ignore audio data
-    private static final int AUDIO_IGNORED_NUM = 3;
+    private static final int AUDIO_FRAMES_TO_IGNORE_COUNT = 3;
 
     // Set audio policy for FM
     // should check AUDIO_POLICY_FORCE_FOR_MEDIA in audio_policy.h
@@ -457,7 +457,7 @@
     class RenderThread extends Thread {
         private int mCurrentFrame = 0;
         private boolean isAudioFrameNeedIgnore() {
-            return mCurrentFrame < AUDIO_IGNORED_NUM;
+            return mCurrentFrame < AUDIO_FRAMES_TO_IGNORE_COUNT;
         }
 
         @Override
@@ -466,10 +466,13 @@
                 byte[] buffer = new byte[RECORD_BUF_SIZE];
                 while (!Thread.interrupted()) {
                     if (isRender()) {
+                        // Speaker mode or BT a2dp mode will come here and keep reading and writing.
+                        // If we want FM sound output from speaker or BT a2dp, we must record data
+                        // to AudioRecrd and write data to AudioTrack.
                         if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
                             mAudioRecord.startRecording();
                         }
-                        // need rendering
+
                         if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
                             mAudioTrack.play();
                         }
@@ -478,7 +481,6 @@
                         // to avoid pop noise.
                         if (isAudioFrameNeedIgnore()) {
                             mCurrentFrame += 1;
-                            Log.d(TAG, "EYES ignore " + mCurrentFrame);
                             continue ;
                         }
                         if (size <= 0) {
@@ -488,23 +490,23 @@
                         }
                         byte[] tmpBuf = new byte[size];
                         System.arraycopy(buffer, 0, tmpBuf, 0, size);
-                        // write to audio track
+                        // Check again to avoid noises, because mIsRender may be changed
+                        // while AudioRecord is reading.
                         if (isRender()) {
                             mAudioTrack.write(tmpBuf, 0, tmpBuf.length);
                         }
                     } else {
+                        // Earphone mode will come here and wait.
                         mCurrentFrame = 0;
-                        // Do not stop audio track to keep the native audio patch
+
                         if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                             mAudioTrack.stop();
                         }
 
-                        // only status wait for render stop all
                         if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                             mAudioRecord.stop();
                         }
 
-                        //enableFmAudio(true);
                         synchronized (mRenderLock) {
                             mRenderLock.wait();
                         }
@@ -1274,7 +1276,11 @@
         }
     }
 
-    private void initAudioRecordSink() {
+    // This function may be called in different threads.
+    // Need to add "synchronized" to make sure mAudioRecord and mAudioTrack are the newest.
+    // Thread 1: onCreate() or startRender()
+    // Thread 2: onAudioPatchListUpdate() or startRender()
+    private synchronized void initAudioRecordSink() {
         mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.FM_TUNER,
                 SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, RECORD_BUF_SIZE);
         mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
@@ -1336,19 +1342,26 @@
         @Override
         public void onAudioPatchListUpdate(AudioPatch[] patchList) {
             if (mPowerStatus != POWER_UP) {
-                Log.d(TAG, "onAudioPortListUpdate, not power up");
+                Log.d(TAG, "onAudioPatchListUpdate, not power up");
                 return;
             }
 
             if (!mIsAudioFocusHeld) {
-                Log.d(TAG, "onAudioPortListUpdate, Current not available return."
-                        + "mIsAudioFocusHeld:" + mIsAudioFocusHeld);
+                Log.d(TAG, "onAudioPatchListUpdate no audio focus");
                 return;
             }
 
             if (mAudioPatch != null) {
                 ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
                 mAudioManager.listAudioPatches(patches);
+                // When BT or WFD is connected, native will remove the patch (mixer -> device).
+                // Need to recreate AudioRecord and AudioTrack for this case.
+                if (isPatchMixerToDeviceRemoved(patches)) {
+                    Log.d(TAG, "onAudioPatchListUpdate reinit for BT or WFD connected");
+                    initAudioRecordSink();
+                    startRender();
+                    return;
+                }
                 if (isPatchMixerToEarphone(patches)) {
                     stopRender();
                 } else {
@@ -1675,6 +1688,26 @@
         return false;
     }
 
+    // Check whether the patch (mixer -> device) is removed by native.
+    // If no patch (mixer -> device), return true.
+    private boolean isPatchMixerToDeviceRemoved(ArrayList<AudioPatch> patches) {
+        boolean noMixerToDevice = true;
+        for (AudioPatch patch : patches) {
+            AudioPortConfig[] sources = patch.sources();
+            AudioPortConfig[] sinks = patch.sinks();
+            AudioPortConfig sourceConfig = sources[0];
+            AudioPortConfig sinkConfig = sinks[0];
+            AudioPort sourcePort = sourceConfig.port();
+            AudioPort sinkPort = sinkConfig.port();
+
+            if (sourcePort instanceof AudioMixPort && sinkPort instanceof AudioDevicePort) {
+                noMixerToDevice = false;
+                break;
+            }
+        }
+        return noMixerToDevice;
+    }
+
     /**
      * Show notification
      */