Merge "Separate old and new test config files."
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 4035af7..8386124 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -143,6 +143,7 @@
CtsLocation2TestCases \
CtsMediaStressTestCases \
CtsMediaTestCases \
+ CtsMidiTestCases \
CtsNativeOpenGLTestCases \
CtsNdefTestCases \
CtsNetTestCases \
@@ -162,6 +163,7 @@
CtsSecurityTestCases \
CtsSignatureTestCases \
CtsSpeechTestCases \
+ CtsTelecomTestCases \
CtsTelephonyTestCases \
CtsTextTestCases \
CtsTextureViewTestCases \
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 4ce9ecd..98e8acd 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1483,6 +1483,30 @@
<meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
</activity>
+ <activity android:name=".audio.AudioDeviceNotificationsActivity"
+ android:label="@string/audio_devices_notifications_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>
+
+ <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_dev_notify.xml b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
new file mode 100644
index 0000000..98dbd8b
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
@@ -0,0 +1,54 @@
+<?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_devices_notification_instructions" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/audio_dev_notification_connect_clearmsgs_btn"
+ android:text="@string/audio_dev_notification_clearmsgs"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/audio_dev_notification_connect_msg"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/audio_dev_notification_disconnect_msg"/>
+
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
\ No newline at end of file
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 675188a..d22f5ec 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1605,4 +1605,33 @@
<string name="error_screen_pinning_did_not_start">Screen was not pinned.</string>
<string name="error_screen_pinning_did_not_exit">Screen was not unpinned.</string>
<string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
+
+ <!-- Audio Devices Notifcations Test -->
+ <string name="audio_devices_notifications_test">Audio Devices Notifications Test</string>
+ <string name="audio_devices_notification_instructions">
+ Click the "Clear Messages" button then connect and disconnect a wired headset.
+ Note if the appropriate notification messages appear below.
+ </string>
+ <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/AudioDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioDeviceNotificationsActivity.java
new file mode 100644
index 0000000..93e0507
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioDeviceNotificationsActivity.java
@@ -0,0 +1,87 @@
+/*
+ * 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.os.Bundle;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Tests Audio Device Connection events by prompting the user to insert/remove a wired headset
+ * and noting the presence (or absence) of notifictions.
+ */
+public class AudioDeviceNotificationsActivity extends PassFailButtons.Activity {
+ Context mContext;
+
+ TextView mConnectView;
+ TextView mDisconnectView;
+ Button mClearMsgsBtn;
+
+ private class TestAudioDeviceCallback extends AudioDeviceCallback {
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ if (addedDevices.length != 0) {
+ mConnectView.setText(
+ mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
+ }
+ }
+
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ if (removedDevices.length != 0) {
+ mDisconnectView.setText(
+ mContext.getResources().getString(
+ R.string.audio_dev_notification_disconnectMsg));
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.audio_dev_notify);
+
+ mContext = this;
+
+ mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
+ mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
+
+ mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
+ mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mConnectView.setText("");
+ mDisconnectView.setText("");
+ }
+ });
+
+ AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
+ audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
+
+ setPassFailButtonClickListeners();
+ }
+}
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;
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.mk b/hostsidetests/devicepolicy/app/IntentSender/Android.mk
index 21d2866..e5246c5 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.mk
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.mk
@@ -24,7 +24,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner android-support-v4
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 ctstestrunner
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index b74a7b6..f4adb31 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -24,9 +24,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit android-support-v4
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 6cddfd1..a20befb 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -43,6 +43,12 @@
android:label="@string/permlab_costMoney"
android:description="@string/permdesc_costMoney"/>
+ <permission android:name="com.android.cts.content.CALL_ABROAD_PERMISSION"
+ android:label="@string/permlab_callAbroad"
+ android:description="@string/permdesc_callAbroad"
+ android:protectionLevel="normal"
+ android:permissionGroup="android.permission-group.COST_MONEY" />
+
<!-- Used for PackageManager test, don't delete! -->
<uses-configuration/>
<uses-feature android:name="android.hardware.camera" />
diff --git a/tests/tests/content/res/values/strings.xml b/tests/tests/content/res/values/strings.xml
index c546d8a..8ffb477 100644
--- a/tests/tests/content/res/values/strings.xml
+++ b/tests/tests/content/res/values/strings.xml
@@ -179,5 +179,7 @@
<string name="permlab_costMoney">Cost money</string>
<string name="permdesc_costMoney">Do things that can cost you money.</string>
+ <string name="permlab_callAbroad">Call abroad</string>
+ <string name="permdesc_callAbroad">Make calls abroad</string>
</resources>
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index aaab8c4..cb3de2a 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -107,10 +107,10 @@
checkActivityInfoName(RECEIVER_NAME, broadcastReceivers);
// Test queryPermissionsByGroup, queryContentProviders
- String testPermissionsGroup = "android.permission-group.NETWORK";
+ String testPermissionsGroup = "android.permission-group.COST_MONEY";
List<PermissionInfo> permissions = mPackageManager.queryPermissionsByGroup(
testPermissionsGroup, PackageManager.GET_META_DATA);
- checkPermissionInfoName(PERMISSION_NAME, permissions);
+ checkPermissionInfoName("com.android.cts.content.CALL_ABROAD_PERMISSION", permissions);
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
List<ProviderInfo> providers = mPackageManager.queryContentProviders(PACKAGE_NAME,
@@ -148,17 +148,12 @@
}
private void checkPermissionInfoName(String expectedName, List<PermissionInfo> permissions) {
- boolean isContained = false;
- Iterator<PermissionInfo> infoIterator = permissions.iterator();
- String current;
- while (infoIterator.hasNext()) {
- current = infoIterator.next().name;
- if (current.equals(expectedName)) {
- isContained = true;
- break;
- }
+ List<String> names = new ArrayList<String>();
+ for (PermissionInfo permission : permissions) {
+ names.add(permission.name);
}
- assertTrue(isContained);
+ boolean isContained = names.contains(expectedName);
+ assertTrue("Permission " + expectedName + " not present in " + names, isContained);
}
private void checkProviderInfoName(String expectedName, List<ProviderInfo> providers) {
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
index b572dcf..769e110 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
@@ -16,12 +16,12 @@
package android.graphics.drawable.cts;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.test.ActivityInstrumentationTestCase2;
import android.util.AttributeSet;
@@ -190,31 +190,27 @@
assertEquals(originalAlpha, d3.getAlpha());
}
- public void testAddRemoveListener() {
- AnimatorListenerAdapter listener1 = new AnimatorListenerAdapter() {};
- AnimatorListenerAdapter listener2 = new AnimatorListenerAdapter() {};
- AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
-
- d1.addListener(listener1);
- d1.addListener(listener2);
-
- assertTrue(d1.getListeners().contains(listener1));
- assertTrue(d1.getListeners().contains(listener2));
-
- d1.removeListener(listener1);
- assertFalse(d1.getListeners().contains(listener1));
-
- d1.removeListener(listener2);
- assertTrue(d1.getListeners() == null);
- }
-
- public void testListener() throws InterruptedException {
- MyListener listener = new MyListener();
+ public void testReset() {
final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
-
- d1.addListener(listener);
// The AVD has a duration as 100ms.
mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ d1.reset();
+ assertFalse(d1.isRunning());
+ }
+ });
+
+ }
+
+ public void testAddCallback() throws InterruptedException {
+ MyCallback callback = new MyCallback();
+ final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
+
+ d1.registerAnimationCallback(callback);
+ // The AVD has a duration as 100ms.
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
public void run() {
d1.start();
}
@@ -222,31 +218,62 @@
Thread.sleep(200);
- assertTrue(listener.mStart);
- assertTrue(listener.mEnd);
- assertFalse(listener.mCancel);
+ assertTrue(callback.mStart);
+ assertTrue(callback.mEnd);
}
- class MyListener implements Animator.AnimatorListener{
+ public void testRemoveCallback() throws InterruptedException {
+ MyCallback callback = new MyCallback();
+ final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
+
+ d1.registerAnimationCallback(callback);
+ assertTrue(d1.unregisterAnimationCallback(callback));
+ // The AVD has a duration as 100ms.
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ d1.start();
+ }
+ });
+
+ Thread.sleep(200);
+
+ assertFalse(callback.mStart);
+ assertFalse(callback.mEnd);
+ }
+
+ public void testClearCallback() throws InterruptedException {
+ MyCallback callback = new MyCallback();
+ final AnimatedVectorDrawable d1 = (AnimatedVectorDrawable) mResources.getDrawable(mResId);
+
+ d1.registerAnimationCallback(callback);
+ d1.clearAnimationCallbacks();
+ // The AVD has a duration as 100ms.
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ d1.start();
+ }
+ });
+
+ Thread.sleep(200);
+
+ assertFalse(callback.mStart);
+ assertFalse(callback.mEnd);
+ }
+
+ class MyCallback extends Animatable2.AnimationCallback {
boolean mStart = false;
boolean mEnd = false;
- boolean mCancel = false;
- int mRepeat = 0;
- public void onAnimationCancel(Animator animation) {
- mCancel = true;
- }
-
- public void onAnimationEnd(Animator animation) {
- mEnd = true;
- }
-
- public void onAnimationRepeat(Animator animation) {
- mRepeat++;
- }
-
- public void onAnimationStart(Animator animation) {
+ @Override
+ public void onAnimationStart(Drawable drawable) {
mStart = true;
}
+
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ mEnd = true;
+ }
}
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
index fdb8dae..fdc7b7a 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
@@ -21,6 +21,9 @@
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.VectorDrawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.test.AndroidTestCase;
@@ -288,4 +291,12 @@
d2.setAlpha(originalAlpha);
}
+
+ public void testColorFilter() {
+ PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.RED, Mode.SRC_IN);
+ VectorDrawable vectorDrawable = new VectorDrawable();
+ vectorDrawable.setColorFilter(filter);
+
+ assertEquals(filter, vectorDrawable.getColorFilter());
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java
index 8ca650f..a9a6fce 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java
@@ -77,7 +77,7 @@
* polynomial approximates the default tonemapping curve used for ACR3.
*/
private static final float[] DEFAULT_ACR3_TONEMAP_CURVE_COEFFS = new float[] {
- 1.041f, -2.973f, 2.932f, 0f
+ -1.087f, 1.643f, 0.443f, 0f
};
/**
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 9014f8d..ee40207 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -1134,7 +1134,7 @@
public void testCodecEarlyEOSHEVC() throws Exception {
testCodecEarlyEOS(
- R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
+ R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz,
120 /* eosframe */);
}
diff --git a/tests/tests/midi/Android.mk b/tests/tests/midi/Android.mk
new file mode 100755
index 0000000..f202933
--- /dev/null
+++ b/tests/tests/midi/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_PACKAGE_NAME := CtsMidiTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/midi/AndroidManifest.xml b/tests/tests/midi/AndroidManifest.xml
new file mode 100755
index 0000000..2cdd211
--- /dev/null
+++ b/tests/tests/midi/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.midi.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name="MidiEchoTestService">
+ <intent-filter>
+ <action android:name="android.media.midi.MidiDeviceService" />
+ </intent-filter>
+ <meta-data android:name="android.media.midi.MidiDeviceService"
+ android:resource="@xml/echo_device_info" />
+ </service>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS MIDI tests"
+ android:targetPackage="android.midi.cts" >
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/midi/res/xml/echo_device_info.xml b/tests/tests/midi/res/xml/echo_device_info.xml
new file mode 100644
index 0000000..936216a
--- /dev/null
+++ b/tests/tests/midi/res/xml/echo_device_info.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<devices>
+ <device manufacturer="AndroidCTS" product="MidiEcho" tags="echo,test">
+ <input-port name="input" />
+ <output-port name="output" />
+ </device>
+</devices>
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
new file mode 100644
index 0000000..f9ef68f
--- /dev/null
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
@@ -0,0 +1,485 @@
+/*
+ * 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 android.midi.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDevice.MidiConnection;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceInfo.PortInfo;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiSender;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * Test MIDI using a virtual MIDI device that echos input to output.
+ */
+public class MidiEchoTest extends AndroidTestCase {
+ public static final String TEST_MANUFACTURER = "AndroidCTS";
+ public static final String ECHO_PRODUCT = "MidiEcho";
+ // I am overloading the timestamp for some tests. It is passed
+ // directly through the Echo server unchanged.
+ // The high 32-bits has a recognizable value.
+ // The low 32-bits can contain data used to identify messages.
+ private static final long TIMESTAMP_MARKER = 0x1234567800000000L;
+ private static final long TIMESTAMP_MARKER_MASK = 0xFFFFFFFF00000000L;
+ private static final long TIMESTAMP_DATA_MASK = 0x00000000FFFFFFFFL;
+ private static final long NANOS_PER_MSEC = 1000L * 1000L;
+
+ // Store device and ports related to the Echo service.
+ static class MidiTestContext {
+ MidiDeviceInfo echoInfo;
+ MidiDevice echoDevice;
+ MidiInputPort echoInputPort;
+ MidiOutputPort echoOutputPort;
+ }
+
+ // Store complete MIDI message so it can be put in an array.
+ static class MidiMessage {
+ public final byte[] data;
+ public final long timestamp;
+ public final long timeReceived;
+
+ MidiMessage(byte[] buffer, int offset, int length, long timestamp) {
+ timeReceived = System.nanoTime();
+ data = new byte[length];
+ System.arraycopy(buffer, offset, data, 0, length);
+ this.timestamp = timestamp;
+ }
+ }
+
+ // Listens for an asynchronous device open and notifies waiting foreground
+ // test.
+ class MyTestOpenCallback implements MidiManager.OnDeviceOpenedListener {
+ MidiDevice mDevice;
+
+ @Override
+ public synchronized void onDeviceOpened(MidiDevice device) {
+ mDevice = device;
+ notifyAll();
+ }
+
+ public synchronized MidiDevice waitForOpen(int msec)
+ throws InterruptedException {
+ wait(msec);
+ return mDevice;
+ }
+ }
+
+ // Store received messages in an array.
+ class MyLoggingReceiver extends MidiReceiver {
+ ArrayList<MidiMessage> messages = new ArrayList<MidiMessage>();
+
+ @Override
+ public synchronized void onSend(byte[] data, int offset, int count,
+ long timestamp) {
+ messages.add(new MidiMessage(data, offset, count, timestamp));
+ notifyAll();
+ }
+
+ public synchronized int getMessageCount() {
+ return messages.size();
+ }
+
+ public synchronized MidiMessage getMessage(int index) {
+ return messages.get(index);
+ }
+
+ /**
+ * Wait until count messages have arrived.
+ * This is a cumulative total.
+ * @param count
+ * @param timeoutMs
+ * @throws InterruptedException
+ */
+ public synchronized void waitForMessages(int count, int timeoutMs)
+ throws InterruptedException {
+ long endTimeMs = System.currentTimeMillis() + timeoutMs + 1;
+ long timeToWait = timeoutMs + 1;
+ while ((getMessageCount() < count)
+ && (timeToWait > 0)) {
+ wait(timeToWait);
+ timeToWait = endTimeMs - System.currentTimeMillis();
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ // Search through the available devices for the ECHO loop-back device.
+ protected MidiDeviceInfo findEchoDevice() {
+ MidiManager midiManager = (MidiManager) mContext.getSystemService(
+ Context.MIDI_SERVICE);
+ MidiDeviceInfo[] infos = midiManager.getDevices();
+ MidiDeviceInfo echoInfo = null;
+ for (MidiDeviceInfo info : infos) {
+ Bundle properties = info.getProperties();
+ String manufacturer = (String) properties.get(
+ MidiDeviceInfo.PROPERTY_MANUFACTURER);
+
+ if (TEST_MANUFACTURER.equals(manufacturer)) {
+ String product = (String) properties.get(
+ MidiDeviceInfo.PROPERTY_PRODUCT);
+ if (ECHO_PRODUCT.equals(product)) {
+ echoInfo = info;
+ break;
+ }
+ }
+ }
+ assertTrue("could not find " + ECHO_PRODUCT, echoInfo != null);
+ return echoInfo;
+ }
+
+ protected MidiTestContext setUpEchoServer() throws Exception {
+ MidiManager midiManager = (MidiManager) mContext.getSystemService(
+ Context.MIDI_SERVICE);
+
+ MidiDeviceInfo echoInfo = findEchoDevice();
+
+ // Open device.
+ MyTestOpenCallback callback = new MyTestOpenCallback();
+ midiManager.openDevice(echoInfo, callback, null);
+ int timeoutMs = 1000;
+ MidiDevice echoDevice = callback.waitForOpen(timeoutMs);
+ assertTrue("could not open " + ECHO_PRODUCT, echoDevice != null);
+
+ // Query echo service directly to see if it is getting status updates.
+ MidiEchoTestService echoService = MidiEchoTestService.getInstance();
+ assertEquals("virtual device status, input port before open", false,
+ echoService.inputOpened);
+ assertEquals("virtual device status, output port before open", 0,
+ echoService.outputOpenCount);
+
+ // Open input port.
+ MidiInputPort echoInputPort = echoDevice.openInputPort(0);
+ assertTrue("could not open input port", echoInputPort != null);
+ assertEquals("input port number", 0, echoInputPort.getPortNumber());
+ assertEquals("virtual device status, input port after open", true,
+ echoService.inputOpened);
+ assertEquals("virtual device status, output port before open", 0,
+ echoService.outputOpenCount);
+
+ // Open output port.
+ MidiOutputPort echoOutputPort = echoDevice.openOutputPort(0);
+ assertTrue("could not open output port", echoOutputPort != null);
+ assertEquals("output port number", 0, echoOutputPort.getPortNumber());
+ assertEquals("virtual device status, input port after open", true,
+ echoService.inputOpened);
+ assertEquals("virtual device status, output port after open", 1,
+ echoService.outputOpenCount);
+
+ MidiTestContext mc = new MidiTestContext();
+ mc.echoInfo = echoInfo;
+ mc.echoDevice = echoDevice;
+ mc.echoInputPort = echoInputPort;
+ mc.echoOutputPort = echoOutputPort;
+ return mc;
+ }
+
+ /**
+ * Close ports and check device status.
+ *
+ * @param mc
+ */
+ protected void tearDownEchoServer(MidiTestContext mc) throws IOException {
+ // Query echo service directly to see if it is getting status updates.
+ MidiEchoTestService echoService = MidiEchoTestService.getInstance();
+ assertEquals("virtual device status, input port before close", true,
+ echoService.inputOpened);
+ assertEquals("virtual device status, output port before close", 1,
+ echoService.outputOpenCount);
+
+ // Close output port.
+ mc.echoOutputPort.close();
+ assertEquals("virtual device status, input port before close", true,
+ echoService.inputOpened);
+ assertEquals("virtual device status, output port after close", 0,
+ echoService.outputOpenCount);
+ mc.echoOutputPort.close();
+ mc.echoOutputPort.close(); // should be safe to close twice
+
+ // Close input port.
+ mc.echoInputPort.close();
+ assertEquals("virtual device status, input port after close", false,
+ echoService.inputOpened);
+ assertEquals("virtual device status, output port after close", 0,
+ echoService.outputOpenCount);
+ mc.echoInputPort.close();
+ mc.echoInputPort.close(); // should be safe to close twice
+
+ mc.echoDevice.close();
+ mc.echoDevice.close(); // should be safe to close twice
+ }
+
+ /**
+ * @param mc
+ * @param echoInfo
+ */
+ protected void checkEchoDeviceInfo(MidiTestContext mc,
+ MidiDeviceInfo echoInfo) {
+ assertEquals("echo input port count wrong", 1,
+ echoInfo.getInputPortCount());
+ assertEquals("echo output port count wrong", 1,
+ echoInfo.getOutputPortCount());
+
+ Bundle properties = echoInfo.getProperties();
+ String tags = (String) properties.get("tags");
+ assertEquals("attributes from device XML", "echo,test", tags);
+
+ PortInfo[] ports = echoInfo.getPorts();
+ assertEquals("port info array size", 2, ports.length);
+
+ boolean foundInput = false;
+ boolean foundOutput = false;
+ for (PortInfo portInfo : ports) {
+ if (portInfo.getType() == PortInfo.TYPE_INPUT) {
+ foundInput = true;
+ assertEquals("input port name", "input", portInfo.getName());
+
+ assertEquals("info port number", portInfo.getPortNumber(),
+ mc.echoInputPort.getPortNumber());
+ } else if (portInfo.getType() == PortInfo.TYPE_OUTPUT) {
+ foundOutput = true;
+ assertEquals("output port name", "output", portInfo.getName());
+ assertEquals("info port number", portInfo.getPortNumber(),
+ mc.echoOutputPort.getPortNumber());
+ }
+ }
+ assertTrue("found input port info", foundInput);
+ assertTrue("found output port info", foundOutput);
+
+ assertEquals("MIDI device type", MidiDeviceInfo.TYPE_VIRTUAL,
+ echoInfo.getType());
+ }
+
+ // Is the MidiManager supported?
+ public void testMidiManager() throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ return; // Not supported so don't test it.
+ }
+
+ MidiManager midiManager = (MidiManager) mContext.getSystemService(
+ Context.MIDI_SERVICE);
+ assertTrue("MidiManager not supported.", midiManager != null);
+
+ // There should be at least one device for the Echo server.
+ MidiDeviceInfo[] infos = midiManager.getDevices();
+ assertTrue("device list was null", infos != null);
+ assertTrue("device list was empty", infos.length >= 1);
+ }
+
+ public void testDeviceInfo() throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ return; // Not supported so don't test it.
+ }
+
+ MidiTestContext mc = setUpEchoServer();
+ checkEchoDeviceInfo(mc, mc.echoInfo);
+ checkEchoDeviceInfo(mc, mc.echoDevice.getInfo());
+ assertTrue("device info equal",
+ mc.echoInfo.equals(mc.echoDevice.getInfo()));
+ tearDownEchoServer(mc);
+ }
+
+ public void testEchoSmallMessage() throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ return; // Not supported so don't test it.
+ }
+
+ MidiTestContext mc = setUpEchoServer();
+
+ MyLoggingReceiver receiver = new MyLoggingReceiver();
+ mc.echoOutputPort.connect(receiver);
+
+ final byte[] buffer = { (byte) 0x93, 0x47, 0x52 };
+ long timestamp = 0x0123765489ABFEDCL;
+
+ mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP
+ mc.echoInputPort.send(buffer, 0, buffer.length, timestamp);
+ mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP
+
+ // Wait for message to pass quickly through echo service.
+ final int numMessages = 1;
+ final int timeoutMs = 20;
+ synchronized (receiver) {
+ receiver.waitForMessages(numMessages, timeoutMs);
+ }
+ assertEquals("number of messages.", numMessages, receiver.getMessageCount());
+ MidiMessage message = receiver.getMessage(0);
+
+ assertEquals("byte count of message", buffer.length,
+ message.data.length);
+ assertEquals("timestamp in message", timestamp, message.timestamp);
+ for (int i = 0; i < buffer.length; i++) {
+ assertEquals("message byte[" + i + "]", buffer[i] & 0x0FF,
+ message.data[i] & 0x0FF);
+ }
+
+ mc.echoOutputPort.disconnect(receiver);
+ tearDownEchoServer(mc);
+ }
+
+ public void testEchoLatency() throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ return; // Not supported so don't test it.
+ }
+
+ MidiTestContext mc = setUpEchoServer();
+ MyLoggingReceiver receiver = new MyLoggingReceiver();
+ mc.echoOutputPort.connect(receiver);
+
+ final int numMessages = 10;
+ final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
+ byte[] buffer = { (byte) 0x93, 0, 64 };
+
+ // Send multiple messages in a burst.
+ for (int index = 0; index < numMessages; index++) {
+ buffer[1] = (byte) (60 + index);
+ mc.echoInputPort.send(buffer, 0, buffer.length, System.nanoTime());
+ }
+
+ // Wait for messages to pass quickly through echo service.
+ final int timeoutMs = 100;
+ synchronized (receiver) {
+ receiver.waitForMessages(numMessages, timeoutMs);
+ }
+ assertEquals("number of messages.", numMessages, receiver.getMessageCount());
+
+ for (int index = 0; index < numMessages; index++) {
+ MidiMessage message = receiver.getMessage(index);
+ assertEquals("message index", (byte) (60 + index), message.data[1]);
+ long elapsedNanos = message.timeReceived - message.timestamp;
+ // If this test fails then there may be a problem with the thread scheduler
+ // or there may be kernel activity that is blocking execution at the user level.
+ assertTrue("MIDI round trip latency[" + index + "] too large, " + elapsedNanos + " nanoseconds",
+ (elapsedNanos < maxLatencyNanos));
+ }
+
+ mc.echoOutputPort.disconnect(receiver);
+ tearDownEchoServer(mc);
+ }
+
+ public void testEchoMultipleMessages() throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ return; // Not supported so don't test it.
+ }
+
+ MidiTestContext mc = setUpEchoServer();
+
+ MyLoggingReceiver receiver = new MyLoggingReceiver();
+ mc.echoOutputPort.connect(receiver);
+
+ final byte[] buffer = new byte[2048];
+
+ final int numMessages = 100;
+ Random random = new Random(1972941337);
+ int bytesSent = 0;
+ byte value = 0;
+
+ // Send various length messages with sequential bytes.
+ long timestamp = TIMESTAMP_MARKER;
+ for (int messageIndex = 0; messageIndex < numMessages;
+ messageIndex++) {
+ // Sweep numData across critical region of
+ // MidiPortImpl.MAX_PACKET_DATA_SIZE
+ int numData = 1000 + messageIndex;
+ for (int dataIndex = 0; dataIndex < numData; dataIndex++) {
+ buffer[dataIndex] = value;
+ value++;
+ }
+ // This may get split into multiple sends internally.
+ mc.echoInputPort.send(buffer, 0, numData, timestamp);
+ bytesSent += numData;
+ timestamp++;
+ }
+
+ // Check messages. Data must be sequential bytes.
+ value = 0;
+ int bytesReceived = 0;
+ int messageReceivedIndex = 0;
+ int messageSentIndex = 0;
+ int expectedMessageSentIndex = 0;
+ while (bytesReceived < bytesSent) {
+ final int timeoutMs = 500;
+ // Wait for next message.
+ synchronized (receiver) {
+ receiver.waitForMessages(messageReceivedIndex + 1, timeoutMs);
+ }
+ MidiMessage message = receiver.getMessage(messageReceivedIndex++);
+ // parse timestamp marker and data
+ long timestampMarker = message.timestamp & TIMESTAMP_MARKER_MASK;
+ assertEquals("timestamp marker corrupted", TIMESTAMP_MARKER, timestampMarker);
+ messageSentIndex = (int) (message.timestamp & TIMESTAMP_DATA_MASK);
+
+ int numData = message.data.length;
+ for (int dataIndex = 0; dataIndex < numData; dataIndex++) {
+ String msg = String.format("message[%d/%d].data[%d/%d]",
+ messageReceivedIndex, messageSentIndex, dataIndex,
+ numData);
+ assertEquals(msg, value, message.data[dataIndex]);
+ value++;
+ }
+ bytesReceived += numData;
+ // May not advance if message got split
+ if (messageSentIndex > expectedMessageSentIndex) {
+ expectedMessageSentIndex++; // only advance by one each message
+ }
+ assertEquals("timestamp in message", expectedMessageSentIndex,
+ messageSentIndex);
+ }
+
+ mc.echoOutputPort.disconnect(receiver);
+ tearDownEchoServer(mc);
+ }
+
+ // What happens if the app does bad things.
+ public void testEchoBadBehavior() throws Exception {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ return; // Not supported so don't test it.
+ }
+ MidiTestContext mc = setUpEchoServer();
+
+ // This should fail because it is already open.
+ MidiInputPort echoInputPort2 = mc.echoDevice.openInputPort(0);
+ assertTrue("input port opened twice", echoInputPort2 == null);
+
+ tearDownEchoServer(mc);
+ }
+}
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTestService.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTestService.java
new file mode 100644
index 0000000..ae5373e
--- /dev/null
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTestService.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.midi.cts;
+
+import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiReceiver;
+
+import java.io.IOException;
+
+/**
+ * Virtual MIDI Device that copies its input to its output.
+ * This is used for loop-back testing of MIDI I/O.
+ */
+
+public class MidiEchoTestService extends MidiDeviceService {
+
+ // Other apps will write to this port.
+ private MidiReceiver mInputReceiver = new MyReceiver();
+ // This app will copy the data to this port.
+ private MidiReceiver mOutputReceiver;
+ private static MidiEchoTestService mInstance;
+
+ // These are public so we can easily read them from CTS test.
+ public int statusChangeCount;
+ public boolean inputOpened;
+ public int outputOpenCount;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mInstance = this;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ // For CTS testing, so I can read test fields.
+ public static MidiEchoTestService getInstance() {
+ return mInstance;
+ }
+
+ @Override
+ public MidiReceiver[] onGetInputPortReceivers() {
+ return new MidiReceiver[] { mInputReceiver };
+ }
+
+ class MyReceiver extends MidiReceiver {
+ @Override
+ public void onSend(byte[] data, int offset, int count, long timestamp)
+ throws IOException {
+ if (mOutputReceiver == null) {
+ mOutputReceiver = getOutputPortReceivers()[0];
+ }
+ // Copy input to output.
+ mOutputReceiver.send(data, offset, count, timestamp);
+ }
+ }
+
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ statusChangeCount++;
+ inputOpened = status.isInputPortOpen(0);
+ outputOpenCount = status.getOutputPortOpenCount(0);
+ }
+}
diff --git a/tests/tests/telecom/Android.mk b/tests/tests/telecom/Android.mk
new file mode 100644
index 0000000..51d97f5
--- /dev/null
+++ b/tests/tests/telecom/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsTelecomTestCases
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
new file mode 100644
index 0000000..91c22f1
--- /dev/null
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -0,0 +1,80 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.telecom">
+ <uses-sdk android:minSdkVersion="21" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />>
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name="android.telecom.cts.MockConnectionService"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+ <intent-filter>
+ <action android:name="android.telecom.ConnectionService" />
+ </intent-filter>
+ </service>
+
+ <service android:name="android.telecom.cts.MockInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE" >
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name="android.telecom.cts.MockDialerActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:mimeType="vnd.android.cursor.item/phone" />
+ <data android:mimeType="vnd.android.cursor.item/person" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="voicemail" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.telecom"
+ android:label="CTS tests for android.telecom package">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/tests/telecom/src/android/telecom/cts/BasicInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/BasicInCallServiceTest.java
new file mode 100644
index 0000000..0b5fe61
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/BasicInCallServiceTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 android.telecom.cts;
+
+import static android.telecom.cts.TestUtils.shouldTestTelecom;
+
+import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sanity test that adding a new call via the CALL intent works correctly.
+ */
+public class BasicInCallServiceTest extends InstrumentationTestCase {
+
+ private static final Uri TEST_NUMBER = Uri.fromParts("tel", "7", null);
+
+ private Context mContext;
+ private String mPreviousDefaultDialer = null;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+ mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
+ TestUtils.setDefaultDialer(getInstrumentation(), TestUtils.PACKAGE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
+ TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests that when sending a CALL intent via the Telecom -> Telephony stack, Telecom
+ * binds to the registered {@link InCallService}s and adds a new call. This test will
+ * actually place a phone call to the number 7. It should still pass even if there is no
+ * SIM card inserted.
+ */
+ public void testTelephonyCall_bindsToInCallServiceAndAddsCall() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_CALL, TEST_NUMBER);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final InCallServiceCallbacks callbacks = createCallbacks();
+
+ MockInCallService.setCallbacks(callbacks);
+
+ mContext.startActivity(intent);
+
+ try {
+ if (callbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
+ return;
+ }
+ } catch (InterruptedException e) {
+ }
+
+ fail("No call added to InCallService.");
+ }
+
+ private MockInCallService.InCallServiceCallbacks createCallbacks() {
+ final InCallServiceCallbacks callbacks = new InCallServiceCallbacks() {
+ @Override
+ public void onCallAdded(Call call, int numCalls) {
+ assertEquals("InCallService should have 1 call after adding call", 1, numCalls);
+ call.disconnect();
+ lock.release();
+ }
+ };
+ return callbacks;
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
new file mode 100644
index 0000000..9a1ebc9
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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 android.telecom.cts;
+
+import static android.telecom.cts.TestUtils.shouldTestTelecom;
+
+import android.os.Build;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.test.AndroidTestCase;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class ConnectionTest extends AndroidTestCase {
+
+ public void testStateCallbacks() {
+ if (!shouldTestTelecom(getContext())) {
+ return;
+ }
+
+ final Semaphore lock = new Semaphore(0);
+ Connection connection = createConnection(lock);
+
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_NEW, connection.getState());
+
+ connection.setInitializing();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_INITIALIZING, connection.getState());
+
+ connection.setInitialized();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_NEW, connection.getState());
+
+ connection.setRinging();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_RINGING, connection.getState());
+
+ connection.setDialing();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_DIALING, connection.getState());
+
+ connection.setActive();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_ACTIVE, connection.getState());
+
+ connection.setOnHold();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_HOLDING, connection.getState());
+
+ connection.setDisconnected(
+ new DisconnectCause(DisconnectCause.LOCAL, "Test call"));
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_DISCONNECTED, connection.getState());
+
+ connection.setRinging();
+ waitForStateChange(lock);
+ assertEquals("Connection should not move out of STATE_DISCONNECTED.",
+ Connection.STATE_DISCONNECTED, connection.getState());
+ }
+
+ /**
+ * {@link UnsupportedOperationException} is only thrown in L MR1+.
+ */
+ public void testFailedState() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
+ return;
+ }
+ Connection connection = Connection.createFailedConnection(
+ new DisconnectCause(DisconnectCause.LOCAL, "Test call"));
+ assertEquals(Connection.STATE_DISCONNECTED, connection.getState());
+
+ try {
+ connection.setRinging();
+ } catch (UnsupportedOperationException e) {
+ return;
+ }
+ fail("Connection should not move out of STATE_DISCONNECTED");
+ }
+
+ /**
+ * {@link UnsupportedOperationException} is only thrown in L MR1+.
+ */
+ public void testCanceledState() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
+ return;
+ }
+ Connection connection = Connection.createCanceledConnection();
+ assertEquals(Connection.STATE_DISCONNECTED, connection.getState());
+
+ try {
+ connection.setDialing();
+ } catch (UnsupportedOperationException e) {
+ return;
+ }
+ fail("Connection should not move out of STATE_DISCONNECTED");
+ }
+
+ private static Connection createConnection(final Semaphore lock) {
+ BasicConnection connection = new BasicConnection();
+ connection.setLock(lock);
+ return connection;
+ }
+
+ private static void waitForStateChange(Semaphore lock) {
+ try {
+ lock.tryAcquire(1000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("State transition timed out");
+ }
+ }
+
+ private static final class BasicConnection extends Connection {
+ private Semaphore mLock;
+
+ public void setLock(Semaphore lock) {
+ mLock = lock;
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ mLock.release();
+ }
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
new file mode 100644
index 0000000..1a9d8d7
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
@@ -0,0 +1,439 @@
+/*
+ * 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 android.telecom.cts;
+
+import static android.telecom.cts.TestUtils.*;
+
+import android.telecom.cts.MockConnectionService.ConnectionServiceCallbacks;
+import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.telecom.CallAudioState;
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.InCallService;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Extended suite of tests that use {@MockConnectionService} and {@MockInCallService} to verify
+ * the functionality of the Telecom service. Requires that the version of GmsCore installed on the
+ * device has the REGISTER_CALL_PROVIDER permission.
+ */
+public class ExtendedInCallServiceTest extends InstrumentationTestCase {
+ public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
+ new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID);
+
+ public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder(
+ TEST_PHONE_ACCOUNT_HANDLE, LABEL)
+ .setAddress(Uri.parse("tel:555-TEST"))
+ .setSubscriptionAddress(Uri.parse("tel:555-TEST"))
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setHighlightColor(Color.RED)
+ .setShortDescription(LABEL)
+ .setSupportedUriSchemes(Arrays.asList("tel"))
+ .build();
+
+ private Context mContext;
+ private TelecomManager mTelecomManager;
+ private InCallServiceCallbacks mInCallCallbacks;
+ private ConnectionServiceCallbacks mConnectionCallbacks;
+ private String mPreviousDefaultDialer = null;
+
+ private static int sCounter = 0;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+ mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+
+ if (shouldTestTelecom(mContext)) {
+ mTelecomManager.registerPhoneAccount(TEST_PHONE_ACCOUNT);
+ TestUtils.enablePhoneAccount(getInstrumentation(), TEST_PHONE_ACCOUNT_HANDLE);
+ mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
+ TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
+ setupCallbacks();
+ placeAndVerifyCall();
+ verifyConnectionService();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (shouldTestTelecom(mContext)) {
+ if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) {
+ mInCallCallbacks.getService().disconnectLastCall();
+ assertNumCalls(mInCallCallbacks.getService(), 0);
+ }
+ if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
+ TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
+ }
+ mTelecomManager.unregisterPhoneAccount(TEST_PHONE_ACCOUNT_HANDLE);
+ }
+ super.tearDown();
+ }
+
+ public void testWithMockConnection_AddNewOutgoingCallAndThenDisconnect() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final MockInCallService inCallService = mInCallCallbacks.getService();
+ inCallService.disconnectLastCall();
+
+ assertNumCalls(inCallService, 0);
+ }
+
+ public void testWithMockConnection_MuteAndUnmutePhone() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final MockInCallService inCallService = mInCallCallbacks.getService();
+
+ final Call call = inCallService.getLastCall();
+ final MockConnection connection = mConnectionCallbacks.outgoingConnection;
+
+ assertCallState(call, Call.STATE_ACTIVE);
+
+ assertMuteState(connection, false);
+
+ inCallService.setMuted(true);;
+
+ assertMuteState(connection, true);
+ assertMuteState(inCallService, true);
+
+ inCallService.setMuted(false);
+ assertMuteState(connection, false);
+ assertMuteState(inCallService, false);
+ }
+
+ public void testWithMockConnection_SwitchAudioRoutes() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final MockInCallService inCallService = mInCallCallbacks.getService();
+ final MockConnection connection = mConnectionCallbacks.outgoingConnection;
+
+ final Call call = inCallService.getLastCall();
+ assertCallState(call, Call.STATE_ACTIVE);
+
+ // Only test speaker and earpiece modes because the other modes are dependent on having
+ // a bluetooth headset or wired headset connected.
+
+ inCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+ assertAudioRoute(connection, CallAudioState.ROUTE_SPEAKER);
+ assertAudioRoute(inCallService, CallAudioState.ROUTE_SPEAKER);
+
+ inCallService.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+ assertAudioRoute(connection, CallAudioState.ROUTE_EARPIECE);
+ assertAudioRoute(inCallService, CallAudioState.ROUTE_EARPIECE);
+ }
+
+ /**
+ * Tests that DTMF Tones are sent from the {@link InCallService} to the
+ * {@link ConnectionService} in the correct sequence.
+ */
+ public void testWithMockConnection_DtmfTones() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final MockInCallService inCallService = mInCallCallbacks.getService();
+ final MockConnection connection = mConnectionCallbacks.outgoingConnection;
+
+ final Call call = inCallService.getLastCall();
+ assertCallState(call, Call.STATE_ACTIVE);
+
+ assertDtmfString(connection, "");
+
+ call.playDtmfTone('1');
+ assertDtmfString(connection, "1");
+
+ call.playDtmfTone('2');
+ assertDtmfString(connection, "12");
+
+ call.playDtmfTone('3');
+ call.playDtmfTone('4');
+ call.playDtmfTone('5');
+ assertDtmfString(connection, "12345");
+ }
+
+ public void testWithMockConnection_HoldAndUnholdCall() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final MockInCallService inCallService = mInCallCallbacks.getService();
+ final MockConnection connection = mConnectionCallbacks.outgoingConnection;
+
+ final Call call = inCallService.getLastCall();
+
+ assertCallState(call, Call.STATE_ACTIVE);
+
+ call.hold();
+ assertCallState(call, Call.STATE_HOLDING);
+ assertEquals(Connection.STATE_HOLDING, connection.getState());
+
+ call.unhold();
+ assertCallState(call, Call.STATE_ACTIVE);
+ assertEquals(Connection.STATE_ACTIVE, connection.getState());
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private void setupCallbacks() {
+ mInCallCallbacks = new InCallServiceCallbacks() {
+ @Override
+ public void onCallAdded(Call call, int numCalls) {
+ this.lock.release();
+ }
+ };
+
+ MockInCallService.setCallbacks(mInCallCallbacks);
+
+ mConnectionCallbacks = new ConnectionServiceCallbacks() {
+ @Override
+ public void onCreateOutgoingConnection(MockConnection connection,
+ ConnectionRequest request) {
+ this.lock.release();
+ }
+ };
+
+ MockConnectionService.setCallbacks(mConnectionCallbacks);
+ }
+
+ /**
+ * Puts Telecom in a state where there is an active call provided by the
+ * {@link MockConnectionService} which can be tested.
+ */
+ private void placeAndVerifyCall() {
+ placeNewCallWithPhoneAccount();
+
+ try {
+ if (!mInCallCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
+ fail("No call added to InCallService.");
+ }
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Test interrupted!");
+ }
+
+ assertEquals("InCallService should contain 1 call after adding a call.", 1,
+ mInCallCallbacks.getService().getCallCount());
+ assertTrue("TelecomManager should be in a call", mTelecomManager.isInCall());
+ }
+
+ private void verifyConnectionService() {
+ try {
+ if (!mConnectionCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
+ fail("No outgoing call connection requested by Telecom");
+ }
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Test interrupted!");
+ }
+
+ assertNotNull("Telecom should bind to and create ConnectionService",
+ mConnectionCallbacks.getService());
+ assertNotNull("Telecom should create outgoing connection for outgoing call",
+ mConnectionCallbacks.outgoingConnection);
+ assertNull("Telecom should not create incoming connection for outgoing call",
+ mConnectionCallbacks.incomingConnection);
+
+ final MockConnection connection = mConnectionCallbacks.outgoingConnection;
+ connection.setDialing();
+ connection.setActive();
+
+ assertEquals(Connection.STATE_ACTIVE, connection.getState());
+ }
+
+ /**
+ * Place a new outgoing call via the {@link MockConnectionService}
+ */
+ private void placeNewCallWithPhoneAccount() {
+ final Intent intent = new Intent(Intent.ACTION_CALL, getTestNumber());
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_PHONE_ACCOUNT_HANDLE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ /**
+ * Create a new number each time for a new test. Telecom has special logic to reuse certain
+ * calls if multiple calls to the same number are placed within a short period of time which
+ * can cause certain tests to fail.
+ */
+ private Uri getTestNumber() {
+ return Uri.fromParts("tel", String.valueOf(sCounter++), null);
+ }
+
+ private void assertNumCalls(final MockInCallService inCallService, final int numCalls) {
+ waitUntilConditionIsTrueOrTimeout(new Condition() {
+ @Override
+ public Object expected() {
+ return numCalls;
+ }
+ @Override
+ public Object actual() {
+ return inCallService.getCallCount();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "InCallService should contain " + numCalls + " calls."
+ );
+ }
+
+ private void assertMuteState(final InCallService incallService, final boolean isMuted) {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return isMuted;
+ }
+
+ @Override
+ public Object actual() {
+ return incallService.getCallAudioState().isMuted();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "Phone's mute state should be: " + isMuted
+ );
+ }
+
+ private void assertMuteState(final MockConnection connection, final boolean isMuted) {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return isMuted;
+ }
+
+ @Override
+ public Object actual() {
+ return connection.getCallAudioState().isMuted();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "Connection's mute state should be: " + isMuted
+ );
+ }
+
+ private void assertAudioRoute(final InCallService incallService, final int route) {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return route;
+ }
+
+ @Override
+ public Object actual() {
+ return incallService.getCallAudioState().getRoute();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "Phone's audio route should be: " + route
+ );
+ }
+
+ private void assertAudioRoute(final MockConnection connection, final int route) {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return route;
+ }
+
+ @Override
+ public Object actual() {
+ return connection.getCallAudioState().getRoute();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "Connection's audio route should be: " + route
+ );
+ }
+
+ private void assertCallState(final Call call, final int state) {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return state;
+ }
+
+ @Override
+ public Object actual() {
+ return call.getState();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "Call should be in state " + state
+ );
+ }
+
+ private void assertDtmfString(final MockConnection connection, final String dtmfString) {
+ waitUntilConditionIsTrueOrTimeout(new Condition() {
+ @Override
+ public Object expected() {
+ return dtmfString;
+ }
+
+ @Override
+ public Object actual() {
+ return connection.getDtmfString();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
+ );
+ }
+
+ private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+ String description) {
+ final long start = System.currentTimeMillis();
+ while (!condition.expected().equals(condition.actual())
+ && System.currentTimeMillis() - start < timeout) {
+ sleep(50);
+ }
+ assertEquals(description, condition.expected(), condition.actual());
+ }
+
+ private interface Condition {
+ Object expected();
+ Object actual();
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
new file mode 100644
index 0000000..67a0fe0
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.telecom.cts;
+
+import static android.telecom.CallAudioState.*;
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.util.Log;
+
+/**
+ * {@link Connection} subclass that immediately performs any state changes that are a result of
+ * callbacks sent from Telecom.
+ */
+public class MockConnection extends Connection {
+
+ private CallAudioState mCallAudioState =
+ new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, ROUTE_EARPIECE | ROUTE_SPEAKER);
+ private int mState = STATE_NEW;
+ private String mDtmfString = "";
+
+ @Override
+ public void onAnswer() {
+ setActive();
+ }
+
+ @Override
+ public void onReject() {
+ setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+ }
+
+ @Override
+ public void onHold() {
+ setOnHold();
+ }
+
+ @Override
+ public void onUnhold() {
+ setActive();
+ }
+
+ @Override
+ public void onDisconnect() {
+ setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ destroy();
+ }
+
+ @Override
+ public void onAbort() {
+ }
+
+ @Override
+ public void onPlayDtmfTone(char c) {
+ mDtmfString += c;
+ }
+
+ @Override
+ public void onCallAudioStateChanged(CallAudioState state) {
+ mCallAudioState = state;
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ mState = state;
+ }
+
+ public int getCurrentState() {
+ return mState;
+ }
+
+ public CallAudioState getCurrentCallAudioState() {
+ return mCallAudioState;
+ }
+
+ public String getDtmfString() {
+ return mDtmfString;
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
new file mode 100644
index 0000000..2b54f32
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
@@ -0,0 +1,95 @@
+/*
+ * 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 android.telecom.cts;
+
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import java.util.concurrent.Semaphore;
+
+public class MockConnectionService extends ConnectionService {
+ private static ConnectionServiceCallbacks sCallbacks;
+ private static Object sLock = new Object();
+
+ public static abstract class ConnectionServiceCallbacks {
+ private MockConnectionService mService;
+ public MockConnection outgoingConnection;
+ public MockConnection incomingConnection;
+ public Semaphore lock = new Semaphore(0);
+
+ public void onCreateOutgoingConnection(MockConnection connection,
+ ConnectionRequest request) {};
+ public void onCreateIncomingConnection(MockConnection connection,
+ ConnectionRequest request) {};
+
+ final public MockConnectionService getService() {
+ return mService;
+ }
+
+ final public void setService(MockConnectionService service) {
+ mService = service;
+ }
+ }
+
+ @Override
+ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+ final MockConnection connection = new MockConnection();
+ connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+
+ final ConnectionServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.setService(this);
+ callbacks.outgoingConnection = connection;
+ callbacks.onCreateOutgoingConnection(connection, request);
+ }
+ return connection;
+ }
+
+ @Override
+ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+ final MockConnection connection = new MockConnection();
+ connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+
+ final ConnectionServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.setService(this);
+ callbacks.incomingConnection = connection;
+ callbacks.onCreateIncomingConnection(connection, request);
+ }
+ return connection;
+ }
+
+ public static void setCallbacks(ConnectionServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ private ConnectionServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
new file mode 100644
index 0000000..cecc603
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -0,0 +1,122 @@
+/*
+ * 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 android.telecom.cts;
+
+import android.telecom.Call;
+import android.telecom.InCallService;
+
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+public class MockInCallService extends InCallService {
+ private ArrayList<Call> mCalls = new ArrayList<>();
+ private static InCallServiceCallbacks sCallbacks;
+
+ private static final Object sLock = new Object();
+
+ public static abstract class InCallServiceCallbacks {
+ private MockInCallService mService;
+ public Semaphore lock = new Semaphore(0);
+
+ public void onCallAdded(Call call, int numCalls) {};
+ public void onCallRemoved(Call call, int numCalls) {};
+ public void onCallStateChanged(Call call, int state) {};
+
+ final public MockInCallService getService() {
+ return mService;
+ }
+
+ final public void setService(MockInCallService service) {
+ mService = service;
+ }
+ }
+
+ private Call.Callback mCallCallback = new Call.Callback() {
+ @Override
+ public void onStateChanged(Call call, int state) {
+ if (getCallbacks() != null) {
+ getCallbacks().onCallStateChanged(call, state);
+ }
+ }
+ };
+
+ @Override
+ public android.os.IBinder onBind(android.content.Intent intent) {
+ if (getCallbacks() != null) {
+ getCallbacks().setService(this);
+ }
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (!mCalls.contains(call)) {
+ mCalls.add(call);
+ call.registerCallback(mCallCallback);
+ }
+ if (getCallbacks() != null) {
+ getCallbacks().onCallAdded(call, mCalls.size());
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ mCalls.remove(call);
+ if (getCallbacks() != null) {
+ getCallbacks().onCallRemoved(call, mCalls.size());
+ }
+ }
+
+ /**
+ * @return the number of calls currently added to the {@code InCallService}.
+ */
+ public int getCallCount() {
+ return mCalls.size();
+ }
+
+ /**
+ * @return the most recently added call that exists inside the {@code InCallService}
+ */
+ public Call getLastCall() {
+ if (mCalls.size() >= 1) {
+ return mCalls.get(mCalls.size() - 1);
+ }
+ return null;
+ }
+
+ public void disconnectLastCall() {
+ final Call call = getLastCall();
+ if (call != null) {
+ call.disconnect();
+ }
+ }
+
+ public static void setCallbacks(InCallServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ private InCallServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java b/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java
new file mode 100644
index 0000000..cc0afe4
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/TelecomAvailabilityTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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 android.telecom.cts;
+
+import static android.telecom.cts.TestUtils.shouldTestTelecom;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for Telecom service. These tests only run on L+ devices because Telecom was
+ * added in L.
+ */
+public class TelecomAvailabilityTest extends InstrumentationTestCase {
+ private static final String TAG = TelecomAvailabilityTest.class.getSimpleName();
+ private static final String TELECOM_PACKAGE_NAME = "com.android.server.telecom";
+ private static final String TELEPHONY_PACKAGE_NAME = "com.android.phone";
+
+ private PackageManager mPackageManager;
+ private Context mContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+ mPackageManager = getInstrumentation().getTargetContext().getPackageManager();
+ }
+
+ /**
+ * Test that the Telecom APK is pre-installed and a system app (FLAG_SYSTEM).
+ */
+ public void testTelecomIsPreinstalledAndSystem() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ PackageInfo packageInfo = findOnlyTelecomPackageInfo(mPackageManager);
+ ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+ assertTrue("Telecom APK must be FLAG_SYSTEM",
+ (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ Log.d(TAG, String.format("Telecom APK is FLAG_SYSTEM %d", applicationInfo.flags));
+ }
+
+ /**
+ * Test that the Telecom APK is registered to handle CALL intents, and that the Telephony APK
+ * is not.
+ */
+ public void testTelecomHandlesCallIntents() {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_CALL, Uri.fromParts("tel", "1234567", null));
+ final List<ResolveInfo> activities =
+ mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+
+ boolean telecomMatches = false;
+ boolean telephonyMatches = false;
+ for (ResolveInfo resolveInfo : activities) {
+ if (resolveInfo.activityInfo == null) {
+ continue;
+ }
+ if (!telecomMatches
+ && TELECOM_PACKAGE_NAME.equals(resolveInfo.activityInfo.packageName)) {
+ telecomMatches = true;
+ } else if (!telephonyMatches
+ && TELEPHONY_PACKAGE_NAME.equals(resolveInfo.activityInfo.packageName)) {
+ telephonyMatches = true;
+ }
+ }
+
+ assertTrue("Telecom APK must be registered to handle CALL intents", telecomMatches);
+ assertFalse("Telephony APK must NOT be registered to handle CALL intents",
+ telephonyMatches);
+ }
+
+ /**
+ * @return The {@link PackageInfo} of the only app named {@code PACKAGE_NAME}.
+ */
+ private static PackageInfo findOnlyTelecomPackageInfo(PackageManager packageManager) {
+ List<PackageInfo> telecomPackages = findMatchingPackages(packageManager);
+ assertEquals(String.format("There must be only one package named %s", TELECOM_PACKAGE_NAME),
+ 1, telecomPackages.size());
+ return telecomPackages.get(0);
+ }
+
+ /**
+ * Finds all packages that have {@code PACKAGE_NAME} name.
+ *
+ * @param pm the android package manager
+ * @return a list of {@link PackageInfo} records
+ */
+ private static List<PackageInfo> findMatchingPackages(PackageManager pm) {
+ List<PackageInfo> packageInfoList = new ArrayList<PackageInfo>();
+ for (PackageInfo info : pm.getInstalledPackages(0)) {
+ if (TELECOM_PACKAGE_NAME.equals(info.packageName)) {
+ packageInfoList.add(info);
+ }
+ }
+ return packageInfoList;
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
new file mode 100644
index 0000000..8cca04c
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.telecom.cts;
+
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.telecom.PhoneAccountHandle;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+public class TestUtils {
+ static final String TAG = "TelecomXTSTests";
+ static final boolean HAS_TELECOM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 10000;
+
+ public static final String PACKAGE = "com.android.cts.telecom";
+ public static final String COMPONENT = "android.telecom.cts.MockConnectionService";
+ public static final String ACCOUNT_ID = "xtstest_CALL_PROVIDER_ID";
+
+ public static final String LABEL = "CTS_MockConnectionService";
+
+ private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
+
+ private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer";
+
+ private static final String COMMAND_ENABLE = "telecom set-phone-account-enabled ";
+
+ public static boolean shouldTestTelecom(Context context) {
+ if (!HAS_TELECOM) {
+ return false;
+ }
+ final PackageManager pm = context.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ }
+
+ public static void setDefaultDialer(Instrumentation instrumentation, String packageName)
+ throws Exception {
+ executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER + packageName);
+ }
+
+ public static String getDefaultDialer(Instrumentation instrumentation) throws Exception {
+ return executeShellCommand(instrumentation, COMMAND_GET_DEFAULT_DIALER);
+ }
+
+ public static void enablePhoneAccount(Instrumentation instrumentation,
+ PhoneAccountHandle handle) throws Exception {
+ final ComponentName component = handle.getComponentName();
+ executeShellCommand(instrumentation, COMMAND_ENABLE
+ + component.getPackageName() + "/" + component.getClassName() + " "
+ + handle.getId());
+ }
+
+ /**
+ * Executes the given shell command and returns the output in a string. Note that even
+ * if we don't care about the output, we have to read the stream completely to make the
+ * command execute.
+ */
+ public static String executeShellCommand(Instrumentation instrumentation,
+ String command) throws Exception {
+ final ParcelFileDescriptor pfd =
+ instrumentation.getUiAutomation().executeShellCommand(command);
+ BufferedReader br = null;
+ try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
+ br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+ String str = null;
+ StringBuilder out = new StringBuilder();
+ while ((str = br.readLine()) != null) {
+ out.append(str);
+ }
+ return out.toString();
+ } finally {
+ if (br != null) {
+ closeQuietly(br);
+ }
+ closeQuietly(pfd);
+ }
+ }
+
+ private static void closeQuietly(AutoCloseable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+}
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
old mode 100644
new mode 100755
index d3d15a5..26e0c54
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -70,6 +70,7 @@
"311660", // MetroPCS
"310120", // Sprint
"44050", // KDDI
+ "44051", // KDDI
"44053", // KDDI
"44054", // KDDI
"44070", // KDDI
@@ -129,6 +130,7 @@
Arrays.asList(
"44010", // NTT DOCOMO
"44020", // SBM
+ "44051", // KDDI
"302720", // Rogers
"30272", // Rogers
"302370", // Fido
@@ -167,6 +169,7 @@
Arrays.asList(
"44010", // NTT DOCOMO
"44020", // SBM
+ "44051", // KDDI
"302720", // Rogers
"30272", // Rogers
"302370", // Fido
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk b/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
index 60a0ba6..3827754 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := CtsUiAutomatorApp
-LOCAL_JAVA_LIBRARIES = android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4
LOCAL_PROGUARD_ENABLED := disabled