am fe91f772: am 28a108a9: Merge "CTSVerifier tests for audio (re)routing callbacks." into mnc-dev

* commit 'fe91f7724a39b640108cf1c653d8c6488d1640d6':
  CTSVerifier tests for audio (re)routing callbacks.
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 6c515fd..98e8acd 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1495,6 +1495,18 @@
             -->
         </activity>
 
+        <activity android:name=".audio.AudioRoutingNotificationsActivity"
+                  android:label="@string/audio_routingnotifications_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <!--
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
+            -->
+        </activity>
+
         <service android:name=".tv.MockTvInputService"
             android:permission="android.permission.BIND_TV_INPUT">
             <intent-filter>
diff --git a/apps/CtsVerifier/res/layout/audio_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_routingnotifications_test.xml
new file mode 100644
index 0000000..cef30d6
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_routingnotifications_test.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="10dip"
+        android:orientation="vertical">
+
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:scrollbars="vertical"
+      android:gravity="bottom"
+      android:id="@+id/info_text"
+      android:text="@string/audio_dev_routingnotification_instructions" />
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="vertical"
+      android:id="@+id/audioTrackRoutingLayout">
+      <TextView
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+              android:text="@string/audio_routingnotification_playHeader"/>
+
+      <TextView
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:id="@+id/audio_routingnotification_audioTrack_change"/>
+
+      <LinearLayout
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:orientation="horizontal">
+          <Button
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:id="@+id/audio_routingnotification_playBtn"
+              android:text="@string/audio_routingnotification_playBtn"/>
+
+          <Button
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:id="@+id/audio_routingnotification_playStopBtn"
+              android:text="@string/audio_routingnotification_playStopBtn"/>
+      </LinearLayout>
+    </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="vertical"
+      android:id="@+id/audioRecordRoutingLayout">
+      <TextView
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+              android:text="@string/audio_routingnotification_recHeader"/>
+
+      <TextView
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:id="@+id/audio_routingnotification_audioRecord_change"/>
+
+      <LinearLayout
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:orientation="horizontal">
+          <Button
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:id="@+id/audio_routingnotification_recordBtn"
+              android:text="@string/audio_routingnotification_recBtn"/>
+
+          <Button
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:id="@+id/audio_routingnotification_recordStopBtn"
+              android:text="@string/audio_routingnotification_recStopBtn"/>
+      </LinearLayout>
+    </LinearLayout>
+
+  <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 1ccd6b7..d22f5ec 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1615,4 +1615,23 @@
     <string name="audio_dev_notification_clearmsgs">Clear Messages</string>
     <string name="audio_dev_notification_connectMsg">CONNECT DETECTED</string>
     <string name="audio_dev_notification_disconnectMsg">DISCONNECT DETECTED</string>
+
+    <!--  Audio Routing Notifcations Test -->
+    <string name="audio_routingnotifications_test">Audio Routing Notifications Test</string>
+    <string name="audio_dev_routingnotification_instructions">
+          Click on the "Play" button in the AudioTrack Routing Notifictions section below to
+          start (silent) playback. Insert a wired headset. Observe a message acknowledging the
+          rerouting event below. Remove the wired headset and observe the new routing message.
+          Click on the "Stop" button to stop playback.\n
+          Repeat the process with "Record" and "Stop" button in the AudioRecord Routing
+          Notifications section below.
+    </string>
+    <string name="audio_routingnotification_playBtn">Play</string>
+    <string name="audio_routingnotification_playStopBtn">Stop</string>
+    <string name="audio_routingnotification_recBtn">Record</string>
+    <string name="audio_routingnotification_recStopBtn">Stop</string>
+    <string name="audio_routingnotification_playHeader">AudioTrack Routing Notifications</string>
+    <string name="audio_routingnotification_recHeader">AudioRecord Routing Notifications</string>
+    <string name="audio_routingnotification_trackRoutingMsg">AudioTrack rerouting</string>
+    <string name="audio_routingnotification_recordRoutingMsg">AudioRecord rerouting</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioRoutingNotificationsActivity.java
new file mode 100644
index 0000000..b6a4255
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioRoutingNotificationsActivity.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 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 com.android.cts.verifier.audio;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.Context;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Tests AudioTrack and AudioRecord (re)Routing messages.
+ */
+public class AudioRoutingNotificationsActivity extends PassFailButtons.Activity {
+    private static final String TAG = "AudioRoutingNotificationsActivity";
+
+    Context mContext;
+
+    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+
+    int mNumTrackNotifications = 0;
+    int mNumRecordNotifications = 0;
+
+    TrivialPlayer mAudioPlayer = new TrivialPlayer();
+    TrivialRecorder mAudioRecorder = new TrivialRecorder();
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.audio_routingnotification_playBtn:
+                    Log.i(TAG, "audio_routingnotification_playBtn");
+                    mAudioPlayer.start();
+                    break;
+
+                case R.id.audio_routingnotification_playStopBtn:
+                    Log.i(TAG, "audio_routingnotification_playStopBtn");
+                    mAudioPlayer.stop();
+                    break;
+
+                case R.id.audio_routingnotification_recordBtn:
+                    break;
+
+                case R.id.audio_routingnotification_recordStopBtn:
+                    break;
+            }
+        }
+    }
+
+    private class AudioTrackRoutingChangeListener implements AudioTrack.OnRoutingChangedListener {
+        public void onRoutingChanged(AudioTrack audioTrack) {
+            mNumTrackNotifications++;
+            TextView textView =
+                (TextView)findViewById(R.id.audio_routingnotification_audioTrack_change);
+            String msg = mContext.getResources().getString(
+                    R.string.audio_routingnotification_trackRoutingMsg);
+            AudioDeviceInfo routedDevice = audioTrack.getRoutedDevice();
+            CharSequence deviceName = routedDevice != null ? routedDevice.getProductName() : "none";
+            int deviceType = routedDevice != null ? routedDevice.getType() : -1;
+            textView.setText(msg + " - " +
+                             deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
+                             " - " + mNumTrackNotifications);
+        }
+    }
+
+    private class AudioRecordRoutingChangeListener implements AudioRecord.OnRoutingChangedListener {
+        public void onRoutingChanged(AudioRecord audioRecord) {
+            mNumRecordNotifications++;
+            TextView textView =
+                    (TextView)findViewById(R.id.audio_routingnotification_audioRecord_change);
+            String msg = mContext.getResources().getString(
+                    R.string.audio_routingnotification_recordRoutingMsg);
+            AudioDeviceInfo routedDevice = audioRecord.getRoutedDevice();
+            CharSequence deviceName = routedDevice != null ? routedDevice.getProductName() : "none";
+            int deviceType = routedDevice != null ? routedDevice.getType() : -1;
+            textView.setText(msg + " - " +
+                             deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
+                             " - " + mNumRecordNotifications);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.audio_routingnotifications_test);
+
+        Button btn;
+        btn = (Button)findViewById(R.id.audio_routingnotification_playBtn);
+        btn.setOnClickListener(mBtnClickListener);
+        btn = (Button)findViewById(R.id.audio_routingnotification_playStopBtn);
+        btn.setOnClickListener(mBtnClickListener);
+        btn = (Button)findViewById(R.id.audio_routingnotification_recordBtn);
+        btn.setOnClickListener(mBtnClickListener);
+        btn = (Button)findViewById(R.id.audio_routingnotification_recordStopBtn);
+        btn.setOnClickListener(mBtnClickListener);
+
+        mContext = this;
+
+        AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+        audioTrack.addOnRoutingChangedListener(
+            new AudioTrackRoutingChangeListener(), new Handler());
+
+        AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+        audioRecord.addOnRoutingChangedListener(
+            new AudioRecordRoutingChangeListener(), new Handler());
+
+        setPassFailButtonClickListeners();
+    }
+
+    @Override
+    public void onBackPressed () {
+        mAudioPlayer.shutDown();
+        mAudioRecorder.shutDown();
+        super.onBackPressed();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java
new file mode 100644
index 0000000..af09504
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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 com.android.cts.verifier.audio;
+
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+
+import java.lang.InterruptedException;
+import java.lang.Math;
+import java.lang.Runnable;
+
+public class TrivialPlayer implements Runnable {
+    AudioTrack mAudioTrack;
+    int mBufferSize;
+
+    boolean mPlay;
+    boolean mIsPlaying;
+
+    short[] mAudioData;
+
+    Thread mFillerThread = null;
+
+    public TrivialPlayer() {
+        mBufferSize =
+                AudioTrack.getMinBufferSize(
+                    41000,
+                    AudioFormat.CHANNEL_OUT_STEREO,
+                    AudioFormat.ENCODING_PCM_16BIT);
+        mAudioTrack =
+            new AudioTrack(
+                AudioManager.STREAM_MUSIC,
+                41000,
+                AudioFormat.CHANNEL_OUT_STEREO,
+                AudioFormat.ENCODING_PCM_16BIT,
+                mBufferSize,
+                AudioTrack.MODE_STREAM);
+
+        mPlay = false;
+        mIsPlaying = false;
+
+        // setup audio data (silence will suffice)
+        mAudioData = new short[mBufferSize];
+        for (int index = 0; index < mBufferSize; index++) {
+            // mAudioData[index] = 0;
+            // keep this code since one might want to hear the playnig audio
+            // for debugging/verification.
+            mAudioData[index] =
+                (short)(((Math.random() * 2.0) - 1.0) * (double)Short.MAX_VALUE/2.0);
+        }
+    }
+
+    public AudioTrack getAudioTrack() { return mAudioTrack; }
+
+    public boolean isPlaying() {
+        synchronized (this) {
+            return mIsPlaying;
+        }
+    }
+
+    public void start() {
+        mPlay = true;
+        mFillerThread = new Thread(this);
+        mFillerThread.start();
+    }
+
+    public void stop() {
+        mPlay = false;
+        mFillerThread = null;
+    }
+
+    public void shutDown() {
+        stop();
+        while (isPlaying()) {
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException ex) {
+            }
+        }
+        mAudioTrack.release();
+    }
+
+    @Override
+    public void run() {
+        mAudioTrack.play();
+        synchronized (this) {
+            mIsPlaying = true;
+        }
+        while (mAudioTrack != null && mPlay) {
+            mAudioTrack.write(mAudioData, 0, mBufferSize);
+        }
+        synchronized (this) {
+            mIsPlaying = false;
+        }
+        mAudioTrack.stop();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java
new file mode 100644
index 0000000..f684681
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 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 com.android.cts.verifier.audio;
+
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+
+import android.media.MediaRecorder;
+
+import java.lang.InterruptedException;
+import java.lang.Runnable;
+
+public class TrivialRecorder implements Runnable {
+    AudioRecord mAudioRecord;
+    int mBufferSize;
+
+    boolean mRecord;
+    boolean mIsRecording;
+
+    short[] mAudioData;
+
+    Thread mReaderThread = null;
+
+    public TrivialRecorder() {
+        mBufferSize =
+                AudioRecord.getMinBufferSize(
+                    41000,
+                    AudioFormat.CHANNEL_IN_MONO,
+                    AudioFormat.ENCODING_PCM_16BIT);
+        mAudioRecord =
+            new AudioRecord(
+                MediaRecorder.AudioSource.DEFAULT,
+                41000,
+                AudioFormat.CHANNEL_IN_MONO,
+                AudioFormat.ENCODING_PCM_16BIT,
+                mBufferSize);
+
+        mRecord = false;
+        mIsRecording = false;
+
+        // setup audio data (silence will suffice)
+        mAudioData = new short[mBufferSize];
+    }
+
+    public AudioRecord getAudioRecord() { return mAudioRecord; }
+
+    public boolean mIsRecording() {
+        synchronized (this) {
+            return mIsRecording;
+        }
+    }
+
+    public void start() {
+        mRecord = true;
+        mReaderThread = new Thread(this);
+        mReaderThread.start();
+    }
+
+    public void stop() {
+        mRecord = false;
+        mReaderThread = null;
+    }
+
+    public void shutDown() {
+        stop();
+        while (mIsRecording()) {
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException ex) {
+            }
+        }
+        mAudioRecord.release();
+    }
+
+    @Override
+    public void run() {
+        mAudioRecord.startRecording();
+        synchronized (this) {
+            mIsRecording = true;
+        }
+       while (mRecord) {
+            mAudioRecord.read(mAudioData, 0, mBufferSize);
+        }
+        mAudioRecord.stop();
+        synchronized (this) {
+            mIsRecording = false;
+        }
+    }
+}