Merge "Make fewer assumptions in AndroidKeyStoreTest." into mnc-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 4035af7b..4e1e008 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -67,6 +67,7 @@
cts_support_packages := \
CtsAccelerationTestStubs \
CtsAppTestStubs \
+ CtsAtraceTestApp \
CtsCertInstallerApp \
CtsDeviceAdmin \
CtsDeviceOpenGl \
@@ -143,6 +144,7 @@
CtsLocation2TestCases \
CtsMediaStressTestCases \
CtsMediaTestCases \
+ CtsMidiTestCases \
CtsNativeOpenGLTestCases \
CtsNdefTestCases \
CtsNetTestCases \
@@ -162,6 +164,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/atrace/AtraceTestApp/Android.mk b/hostsidetests/atrace/AtraceTestApp/Android.mk
new file mode 100644
index 0000000..0eb7cfd
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2009 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_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_PACKAGE_NAME := CtsAtraceTestApp
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+#LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+#LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..a86f7f0
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.atracetestapp">
+ <!--
+ A simple app with a tracing section to test that apps tracing signals are
+ emitted by atrace.
+ -->
+ <application>
+ <activity android:name=".AtraceTestAppActivity">
+ <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>
+</manifest>
diff --git a/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java
new file mode 100644
index 0000000..0269d0d
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.atracetestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Trace;
+
+public class AtraceTestAppActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Trace.beginSection("traceable-app-test-section");
+ super.onCreate(savedInstanceState);
+ Trace.endSection();
+ }
+}
diff --git a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
index f7af31c..ee0511d 100644
--- a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
@@ -16,27 +16,40 @@
package android.atrace.cts;
+import com.android.cts.tradefed.build.CtsBuildHelper;
import com.android.ddmlib.Log;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.StringReader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Test to check that atrace is usable, to enable usage of systrace.
*/
-public class AtraceHostTest extends DeviceTestCase {
+public class AtraceHostTest extends DeviceTestCase implements IBuildReceiver {
private static final String TAG = "AtraceHostTest";
- private ITestDevice mDevice;
+ private static final String TEST_APK = "CtsAtraceTestApp.apk";
+ private static final String TEST_PKG = "com.android.cts.atracetestapp";
+ private CtsBuildHelper mCtsBuild;
+
+ /**
+ * {@inheritDoc}
+ */
@Override
- protected void setUp() throws Exception {
- super.setUp();
- mDevice = getDevice();
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
}
// Collection of all userspace tags, and 'sched'
@@ -65,7 +78,7 @@
* Tests that atrace exists and is runnable with no args
*/
public void testSimpleRun() throws Exception {
- String output = mDevice.executeShellCommand("atrace");
+ String output = getDevice().executeShellCommand("atrace");
String[] lines = output.split("\\r?\\n");
// check for expected stdout
@@ -80,7 +93,7 @@
* Tests the output of "atrace --list_categories" to ensure required categories exist.
*/
public void testCategories() throws Exception {
- String output = mDevice.executeShellCommand("atrace --list_categories");
+ String output = getDevice().executeShellCommand("atrace --list_categories");
String[] categories = output.split("\\r?\\n");
Set<String> requiredCategories = new HashSet<String>(sRequiredCategoriesList);
@@ -101,4 +114,120 @@
fail("Expected categories missing from atrace");
}
}
+
+
+ private static final String TRACE_MARKER_REGEX =
+ "\\s*(\\S+)-(\\d+)\\s+\\(\\s*(\\d+)\\).*tracing_mark_write:\\s*(B|E)(.*)";
+ /**
+ * Tests that atrace captures app launch, including app level tracing
+ */
+ public void testTracingContent() throws Exception {
+ String atraceOutput = null;
+ try {
+ // cleanup test apps that might be installed from previous partial test run
+ getDevice().uninstallPackage(TEST_PKG);
+
+ // install the test app
+ File testAppFile = mCtsBuild.getTestApp(TEST_APK);
+ String installResult = getDevice().installPackage(testAppFile, false);
+ assertNull(
+ String.format("failed to install simple app. Reason: %s", installResult),
+ installResult);
+
+ // capture a launch of the app with async tracing
+ String atraceArgs = "-a " + TEST_PKG + " -c -b 16000 view"; // TODO: zipping
+ getDevice().executeShellCommand("atrace --async_stop " + atraceArgs);
+ getDevice().executeShellCommand("atrace --async_start " + atraceArgs);
+ String start = getDevice().executeShellCommand("am start " + TEST_PKG);
+ getDevice().executeShellCommand("sleep 1");
+ atraceOutput = getDevice().executeShellCommand("atrace --async_dump " + atraceArgs);
+ } finally {
+ getDevice().uninstallPackage(TEST_PKG);
+ }
+ assertNotNull(atraceOutput);
+
+
+ // now parse the trace data (see external/chromium-trace/systrace.py)
+ final String MARKER = "TRACE:";
+ int dataStart = atraceOutput.indexOf(MARKER);
+ assertTrue(dataStart >= 0);
+ String traceData = atraceOutput.substring(dataStart + MARKER.length());
+
+ /**
+ * Pattern that matches standard begin/end userspace tracing.
+ *
+ * Groups are:
+ * 1 - truncated thread name
+ * 2 - tid
+ * 3 - pid
+ * 4 - B/E
+ * 5 - ignored, for grouping
+ * 6 - if B, section title, else null
+ */
+ final Pattern beginEndPattern = Pattern.compile(
+ "\\s*(\\S+)-(\\d+)\\s+\\(\\s*(\\d+)\\).*tracing_mark_write:\\s*(B|E)(\\|\\d+\\|(.+))?");
+
+ int appPid = -1;
+ String line;
+
+ // list of tags expected to be seen on app launch, in order.
+ String[] requiredSectionList = {
+ "traceable-app-test-section",
+ "inflate",
+ "performTraversals",
+ "measure",
+ "layout",
+ "draw",
+ "Record View#draw()"
+ };
+ int nextSectionIndex = 0;
+ int matches = 0;
+ try (BufferedReader reader = new BufferedReader(new StringReader(traceData))) {
+ while ((line = reader.readLine()) != null) {
+ Matcher matcher = beginEndPattern.matcher(line);
+ if (matcher.find()) {
+ matches++;
+
+ String truncatedThreadName = matcher.group(1);
+ assertNotNull(truncatedThreadName);
+
+ int tid = assertInt(matcher.group(2));
+ assertTrue(tid > 0);
+ int pid = assertInt(matcher.group(3));
+ assertTrue(pid > 0);
+
+ if (TEST_PKG.endsWith(truncatedThreadName)) {
+ // should be something like "s.aptracetestapp" since beginning may be truncated
+ if (appPid == -1) {
+ appPid = pid;
+ } else {
+ assertEquals(appPid, pid);
+ }
+
+ if ("B".equals(matcher.group(4))) {
+ String sectionTitle = matcher.group(6);
+ if (nextSectionIndex < requiredSectionList.length
+ && requiredSectionList[nextSectionIndex].equals(sectionTitle)) {
+ nextSectionIndex++;
+ }
+ }
+ }
+ }
+ }
+ }
+ assertTrue("Unable to parse any userspace sections from atrace output",
+ matches != 0);
+ assertEquals("Didn't see required list of traced sections, in order",
+ requiredSectionList.length, nextSectionIndex);
+ }
+
+ private static int assertInt(String input) {
+ try {
+ return Integer.parseInt(input);
+ } catch (NumberFormatException e) {
+ fail("Expected an integer but found \"" + input + "\"");
+ // Won't be hit, above throws AssertException
+ return -1;
+ }
+ }
}
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/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
new file mode 100644
index 0000000..2faf158
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.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 com.android.cts.managedprofile;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserManager;
+
+import com.android.cts.managedprofile.BaseManagedProfileTest;
+
+import org.junit.Ignore;
+
+/**
+ * Test Runtime Permissions APIs in DevicePolicyManager.
+ */
+public class PermissionsTest extends BaseManagedProfileTest {
+
+ private static final String SIMPLE_APP_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
+ private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Make sure we are running in a managed profile, otherwise risk wiping the primary user's
+ // data.
+ assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+ assertTrue(mDevicePolicyManager.isProfileOwnerApp(
+ ADMIN_RECEIVER_COMPONENT.getPackageName()));
+ }
+
+ public void testPermissionGrantState() {
+ PackageManager pm = mContext.getPackageManager();
+ mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+ assertTrue(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME) ==
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+ assertEquals(pm.checkPermission(PERMISSION_NAME, SIMPLE_APP_PACKAGE_NAME),
+ PackageManager.PERMISSION_DENIED);
+
+ mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ assertTrue(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME) ==
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ // Should stay denied
+ assertEquals(pm.checkPermission(PERMISSION_NAME, SIMPLE_APP_PACKAGE_NAME),
+ PackageManager.PERMISSION_DENIED);
+
+ mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+ assertTrue(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME) ==
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+ assertEquals(pm.checkPermission(PERMISSION_NAME, SIMPLE_APP_PACKAGE_NAME),
+ PackageManager.PERMISSION_GRANTED);
+
+ mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ assertTrue(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ SIMPLE_APP_PACKAGE_NAME, PERMISSION_NAME) ==
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ // Should stay granted
+ assertEquals(pm.checkPermission(PERMISSION_NAME, SIMPLE_APP_PACKAGE_NAME),
+ PackageManager.PERMISSION_GRANTED);
+
+ mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
+ assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
+
+ mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
+ assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
+
+ mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
+ DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+ assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
+ DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index af74f57..791ae56 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.launcherapps.simpleapp">
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+
<application>
<activity android:name=".SimpleActivity" >
<intent-filter>
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 5c2048e..7d8fa67 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -30,6 +30,8 @@
private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+ private static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
+
private static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
private static final String INTENT_SENDER_APK = "CtsIntentSenderApp.apk";
@@ -406,6 +408,15 @@
}
}
+ public void testPermissionGrant() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installApp(SIMPLE_APP_APK);
+ assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
+ "testPermissionGrantState", mUserId));
+ }
+
private void disableActivityForUser(String activityName, int userId)
throws DeviceNotAvailableException {
String command = "am start -W --user " + userId
diff --git a/tests/tests/accounts/AndroidManifest.xml b/tests/tests/accounts/AndroidManifest.xml
index c671ff0..93529d0 100644
--- a/tests/tests/accounts/AndroidManifest.xml
+++ b/tests/tests/accounts/AndroidManifest.xml
@@ -43,10 +43,21 @@
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
-
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
+
+ <service android:name="MockCustomTokenAccountService" android:exported="true"
+ android:process="android.accounts.cts">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+ <meta-data android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/custom_token_authenticator" />
+ <meta-data android:name="android.accounts.AccountAuthenticator.customTokens"
+ android:value="1" />
+
+ </service>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/accounts/res/drawable/ic_cts_minitab_selected_custom_account.png b/tests/tests/accounts/res/drawable/ic_cts_minitab_selected_custom_account.png
new file mode 100644
index 0000000..3fbbc94
--- /dev/null
+++ b/tests/tests/accounts/res/drawable/ic_cts_minitab_selected_custom_account.png
Binary files differ
diff --git a/tests/tests/accounts/res/drawable/ic_cts_selected_custom_account.png b/tests/tests/accounts/res/drawable/ic_cts_selected_custom_account.png
new file mode 100644
index 0000000..70e35c0
--- /dev/null
+++ b/tests/tests/accounts/res/drawable/ic_cts_selected_custom_account.png
Binary files differ
diff --git a/tests/tests/accounts/res/xml/custom_token_authenticator.xml b/tests/tests/accounts/res/xml/custom_token_authenticator.xml
new file mode 100644
index 0000000..3337917
--- /dev/null
+++ b/tests/tests/accounts/res/xml/custom_token_authenticator.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2009, 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.
+ */
+-->
+
+<!-- The attributes in this XML file provide configuration information -->
+<!-- for the Account Manager. -->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="android.accounts.cts.custom.account.type"
+ android:icon="@drawable/ic_cts_selected_custom_account"
+ android:smallIcon="@drawable/ic_cts_minitab_selected_custom_account"
+ android:customTokens="true"
+ android:label="@string/label"
+/>
diff --git a/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java b/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
index c582c79..0e84fb9 100644
--- a/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
+++ b/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
@@ -26,16 +26,19 @@
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.StrictMode;
import android.test.ActivityInstrumentationTestCase2;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
* You can run those unit tests with the following command line:
@@ -52,13 +55,15 @@
public static final String ACCOUNT_NAME_OTHER = "android.accounts.cts.account.name.other";
public static final String ACCOUNT_TYPE = "android.accounts.cts.account.type";
- public static final String ACCOUNT_TYPE_OTHER = "android.accounts.cts.account.type.other";
+ public static final String ACCOUNT_TYPE_CUSTOM = "android.accounts.cts.custom.account.type";
+ public static final String ACCOUNT_TYPE_ABSENT = "android.accounts.cts.account.type.absent";
public static final String ACCOUNT_PASSWORD = "android.accounts.cts.account.password";
- public static final String AUTH_TOKEN = "mockAuthToken";
public static final String AUTH_TOKEN_TYPE = "mockAuthTokenType";
+ public static final String AUTH_EXPIRING_TOKEN_TYPE = "mockAuthExpiringTokenType";
public static final String AUTH_TOKEN_LABEL = "mockAuthTokenLabel";
+ public static final long AUTH_TOKEN_DURATION_MILLIS = 10000L; // Ten seconds.
public static final String FEATURE_1 = "feature.1";
public static final String FEATURE_2 = "feature.2";
@@ -86,6 +91,9 @@
MockAccountAuthenticator.ACCOUNT_NAME_FOR_NEW_REMOVE_API, ACCOUNT_TYPE);
public static final Account ACCOUNT_SAME_TYPE = new Account(ACCOUNT_NAME_OTHER, ACCOUNT_TYPE);
+ public static final Account CUSTOM_TOKEN_ACCOUNT =
+ new Account(ACCOUNT_NAME,ACCOUNT_TYPE_CUSTOM);
+
private static MockAccountAuthenticator mockAuthenticator;
private static final int LATCH_TIMEOUT_MS = 500;
private static AccountManager am;
@@ -130,16 +138,101 @@
assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, mActivity, null /* callback */).getBoolean(
AccountManager.KEY_BOOLEAN_RESULT));
+ // Clean out any other accounts added during the tests.
+ Account[] ctsAccounts = am.getAccountsByType(ACCOUNT_TYPE);
+ Account[] ctsCustomAccounts = am.getAccountsByType(ACCOUNT_TYPE_CUSTOM);
+ ArrayList<Account> accounts = new ArrayList<>(Arrays.asList(ctsAccounts));
+ accounts.addAll(Arrays.asList(ctsCustomAccounts));
+ for (Account ctsAccount : accounts) {
+ removeAccount(am, ctsAccount, mActivity, null /* callback */);
+ }
+
// need to clean up the authenticator cached data
mockAuthenticator.clearData();
super.tearDown();
}
- private void validateAccountAndAuthTokenResult(Bundle result) {
- assertEquals(ACCOUNT_NAME, result.get(AccountManager.KEY_ACCOUNT_NAME));
- assertEquals(ACCOUNT_TYPE, result.get(AccountManager.KEY_ACCOUNT_TYPE));
- assertEquals(AUTH_TOKEN, result.get(AccountManager.KEY_AUTHTOKEN));
+ interface TokenFetcher {
+ public Bundle fetch(String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException;
+ public Account getAccount();
+ }
+
+ private void validateSuccessfulTokenFetchingLifecycle(TokenFetcher fetcher, String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException {
+ Account account = fetcher.getAccount();
+ Bundle expected = new Bundle();
+ expected.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ expected.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+ // First fetch.
+ Bundle actual = fetcher.fetch(tokenType);
+ assertTrue(mockAuthenticator.isRecentlyCalled());
+ validateAccountAndAuthTokenResult(expected, actual);
+
+ /*
+ * On the second fetch the cache will be populated if we are using a authenticator with
+ * customTokens=false or we are using a scope that will cause the authenticator to set an
+ * expiration time (and that expiration time hasn't been reached).
+ */
+ actual = fetcher.fetch(tokenType);
+
+ boolean isCachingExpected =
+ ACCOUNT_TYPE.equals(account.type) || AUTH_EXPIRING_TOKEN_TYPE.equals(tokenType);
+ assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
+ validateAccountAndAuthTokenResult(expected, actual);
+
+ try {
+ // Delay further execution until expiring tokens can actually expire.
+ Thread.sleep(mockAuthenticator.getTokenDurationMillis() + 1L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ /*
+ * With the time shift above, the third request will result in cache hits only from
+ * customToken=false authenticators.
+ */
+ actual = fetcher.fetch(tokenType);
+ isCachingExpected = ACCOUNT_TYPE.equals(account.type);
+ assertEquals(isCachingExpected, !mockAuthenticator.isRecentlyCalled());
+ validateAccountAndAuthTokenResult(expected, actual);
+
+ // invalidate token
+ String token = actual.getString(AccountManager.KEY_AUTHTOKEN);
+ am.invalidateAuthToken(account.type, token);
+
+ /*
+ * Upon invalidating the token, the cache should be clear regardless of authenticator.
+ */
+ actual = fetcher.fetch(tokenType);
+ assertTrue(mockAuthenticator.isRecentlyCalled());
+ validateAccountAndAuthTokenResult(expected, actual);
+ }
+
+ private void validateAccountAndAuthTokenResult(Bundle actual) {
+ assertEquals(
+ ACCOUNT.name,
+ actual.get(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(
+ ACCOUNT.type,
+ actual.get(AccountManager.KEY_ACCOUNT_TYPE));
+ assertEquals(
+ mockAuthenticator.getLastTokenServed(),
+ actual.get(AccountManager.KEY_AUTHTOKEN));
+ }
+
+ private void validateAccountAndAuthTokenResult(Bundle expected, Bundle actual) {
+ assertEquals(
+ expected.get(AccountManager.KEY_ACCOUNT_NAME),
+ actual.get(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(
+ expected.get(AccountManager.KEY_ACCOUNT_TYPE),
+ actual.get(AccountManager.KEY_ACCOUNT_TYPE));
+ assertEquals(
+ mockAuthenticator.getLastTokenServed(),
+ actual.get(AccountManager.KEY_AUTHTOKEN));
}
private void validateAccountAndNoAuthTokenResult(Bundle result) {
@@ -224,7 +317,6 @@
private boolean removeAccount(AccountManager am, Account account,
AccountManagerCallback<Boolean> callback) throws IOException, AuthenticatorException,
OperationCanceledException {
-
AccountManagerFuture<Boolean> futureBoolean = am.removeAccount(account,
callback,
null /* handler */);
@@ -357,6 +449,7 @@
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
@@ -483,9 +576,7 @@
/**
* Test addAccountExplicitly() and removeAccountExplictly().
*/
- public void testAddAccountExplicitlyAndRemoveAccountExplicitly() throws IOException,
- AuthenticatorException, OperationCanceledException {
-
+ public void testAddAccountExplicitlyAndRemoveAccountExplicitly() {
final int expectedAccountsCount = getAccountsCount();
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
@@ -581,7 +672,7 @@
assertEquals(2, accounts.length);
// Check if we dont have any account from the other type
- accounts = am.getAccountsByType(ACCOUNT_TYPE_OTHER);
+ accounts = am.getAccountsByType(ACCOUNT_TYPE_ABSENT);
assertEquals(0, accounts.length);
}
@@ -684,6 +775,7 @@
final CountDownLatch latch1 = new CountDownLatch(1);
AccountManagerCallback<Account[]> callback1 = new AccountManagerCallback<Account[]>() {
+ @Override
public void run(AccountManagerFuture<Account[]> accountsFuture) {
try {
Account[] accounts = accountsFuture.getResult();
@@ -724,6 +816,7 @@
final CountDownLatch latch2 = new CountDownLatch(1);
AccountManagerCallback<Account[]> callback2 = new AccountManagerCallback<Account[]>() {
+ @Override
public void run(AccountManagerFuture<Account[]> accountsFuture) {
try {
Account[] accounts = accountsFuture.getResult();
@@ -765,25 +858,24 @@
*/
public void testSetAndPeekAndInvalidateAuthToken() {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
-
- am.setAuthToken(ACCOUNT, AUTH_TOKEN_TYPE, AUTH_TOKEN);
+ String expected = "x";
+ am.setAuthToken(ACCOUNT, AUTH_TOKEN_TYPE, expected);
// Ask for the AuthToken
String token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
assertNotNull(token);
- assertEquals(AUTH_TOKEN, token);
+ assertEquals(expected, token);
- am.invalidateAuthToken(ACCOUNT_TYPE, AUTH_TOKEN);
+ am.invalidateAuthToken(ACCOUNT_TYPE, token);
token = am.peekAuthToken(ACCOUNT, AUTH_TOKEN_TYPE);
assertNull(token);
}
/**
- * Test blockingGetAuthToken()
+ * Test successful blockingGetAuthToken() with customTokens=false authenticator.
*/
- public void testBlockingGetAuthToken() throws IOException, AuthenticatorException,
- OperationCanceledException {
-
+ public void testBlockingGetAuthToken_DefaultToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
String token = am.blockingGetAuthToken(ACCOUNT,
@@ -792,17 +884,61 @@
// Ask for the AuthToken
assertNotNull(token);
- assertEquals(AUTH_TOKEN, token);
+ assertEquals(mockAuthenticator.getLastTokenServed(), token);
+ }
+
+ private static class BlockingGetAuthTokenFetcher implements TokenFetcher {
+ private final Account mAccount;
+
+ BlockingGetAuthTokenFetcher(Account account) {
+ mAccount = account;
+ }
+
+ @Override
+ public Bundle fetch(String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException {
+ String token = am.blockingGetAuthToken(
+ getAccount(),
+ tokenType,
+ false /* no failure notification */);
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_AUTHTOKEN, token);
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
+ return result;
+ }
+ @Override
+ public Account getAccount() {
+ return CUSTOM_TOKEN_ACCOUNT;
+ }
}
/**
- * Test getAuthToken()
+ * Test successful blockingGetAuthToken() with customTokens=true authenticator.
*/
- public void testGetAuthToken() throws IOException, AuthenticatorException,
- OperationCanceledException {
+ public void testBlockingGetAuthToken_CustomToken_NoCaching_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
+ TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
+ validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
+ }
+ /**
+ * Test successful blockingGetAuthToken() with customTokens=true authenticator.
+ */
+ public void testBlockingGetAuthToken_CustomToken_ExpiringCache_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
+ TokenFetcher f = new BlockingGetAuthTokenFetcher(CUSTOM_TOKEN_ACCOUNT);
+ validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
+ }
+
+ /**
+ * Test successful getAuthToken() using a future with customTokens=false authenticator.
+ */
+ public void testDeprecatedGetAuthTokenWithFuture_NoOptions_DefaultToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
-
AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
AUTH_TOKEN_TYPE,
false /* no failure notification */,
@@ -820,6 +956,226 @@
}
/**
+ * Test successful getAuthToken() using a future with customTokens=false without
+ * expiring tokens.
+ */
+ public void testDeprecatedGetAuthTokenWithFuture_NoOptions_CustomToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
+ // validateSuccessfulTokenFetchingLifecycle(AccountManager am, TokenFetcher fetcher, String tokenType)
+ TokenFetcher f = new TokenFetcher() {
+ @Override
+ public Bundle fetch(String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException {
+ AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
+ getAccount(),
+ tokenType,
+ false /* no failure notification */,
+ null /* no callback */,
+ null /* no handler */
+ );
+ Bundle actual = futureBundle.getResult();
+ assertTrue(futureBundle.isDone());
+ assertNotNull(actual);
+ return actual;
+ }
+
+ @Override
+ public Account getAccount() {
+ return CUSTOM_TOKEN_ACCOUNT;
+ }
+ };
+ validateSuccessfulTokenFetchingLifecycle(f, AUTH_EXPIRING_TOKEN_TYPE);
+ validateSuccessfulTokenFetchingLifecycle(f, AUTH_TOKEN_TYPE);
+ }
+
+ /**
+ * Test successful getAuthToken() using a future with customTokens=false without
+ * expiring tokens.
+ */
+ public void testGetAuthTokenWithFuture_Options_DefaultToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+ AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
+ AUTH_TOKEN_TYPE,
+ OPTIONS_BUNDLE,
+ mActivity,
+ null /* no callback */,
+ null /* no handler */
+ );
+
+ Bundle resultBundle = futureBundle.getResult();
+
+ assertTrue(futureBundle.isDone());
+ assertNotNull(resultBundle);
+
+ // Assert returned result
+ validateAccountAndAuthTokenResult(resultBundle);
+
+ validateOptions(null, mockAuthenticator.mOptionsAddAccount);
+ validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
+ validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
+ validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsGetAuthToken);
+ validateSystemOptions(mockAuthenticator.mOptionsGetAuthToken);
+ }
+
+ /**
+ * Test successful getAuthToken() using a future with customTokens=false without
+ * expiring tokens.
+ */
+ public void testGetAuthTokenWithFuture_Options_CustomToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
+ TokenFetcher fetcherWithOptions = new TokenFetcher() {
+ @Override
+ public Bundle fetch(String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException {
+ AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(
+ getAccount(),
+ tokenType,
+ OPTIONS_BUNDLE,
+ false /* no failure notification */,
+ null /* no callback */,
+ null /* no handler */
+ );
+ Bundle actual = futureBundle.getResult();
+ assertTrue(futureBundle.isDone());
+ assertNotNull(actual);
+ return actual;
+ }
+
+ @Override
+ public Account getAccount() {
+ return CUSTOM_TOKEN_ACCOUNT;
+ }
+ };
+ validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
+ validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
+ }
+
+
+ /**
+ * Test successful getAuthToken() using a future with customTokens=false without
+ * expiring tokens.
+ */
+ public void testGetAuthTokenWithCallback_Options_Handler_DefaultToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null);
+ final HandlerThread handlerThread = new HandlerThread("accounts.test");
+ handlerThread.start();
+ TokenFetcher fetcherWithOptions = new TokenFetcher() {
+ @Override
+ public Bundle fetch(String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException {
+ final AtomicReference<Bundle> actualRef = new AtomicReference<>();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
+ public void run(AccountManagerFuture<Bundle> bundleFuture) {
+ Bundle resultBundle = null;
+ try {
+ resultBundle = bundleFuture.getResult();
+ actualRef.set(resultBundle);
+ } catch (OperationCanceledException e) {
+ fail("should not throw an OperationCanceledException");
+ } catch (IOException e) {
+ fail("should not throw an IOException");
+ } catch (AuthenticatorException e) {
+ fail("should not throw an AuthenticatorException");
+ } finally {
+ latch.countDown();
+ }
+ }
+ };
+
+ am.getAuthToken(getAccount(),
+ tokenType,
+ OPTIONS_BUNDLE,
+ false /* no failure notification */,
+ callback,
+ new Handler(handlerThread.getLooper()));
+
+ // Wait with timeout for the callback to do its work
+ try {
+ latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("should not throw an InterruptedException");
+ }
+ return actualRef.get();
+ }
+
+ @Override
+ public Account getAccount() {
+ return ACCOUNT;
+ }
+ };
+ validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
+ validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
+ }
+
+ /**
+ * Test successful getAuthToken() using a future with customTokens=false without
+ * expiring tokens.
+ */
+ public void testGetAuthTokenWithCallback_Options_Handler_CustomToken_Success()
+ throws IOException, AuthenticatorException, OperationCanceledException {
+ addAccountExplicitly(CUSTOM_TOKEN_ACCOUNT, ACCOUNT_PASSWORD, null);
+ final HandlerThread handlerThread = new HandlerThread("accounts.test");
+ handlerThread.start();
+ TokenFetcher fetcherWithOptions = new TokenFetcher() {
+ @Override
+ public Bundle fetch(String tokenType)
+ throws OperationCanceledException, AuthenticatorException, IOException {
+ final AtomicReference<Bundle> actualRef = new AtomicReference<>();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
+ public void run(AccountManagerFuture<Bundle> bundleFuture) {
+ Bundle resultBundle = null;
+ try {
+ resultBundle = bundleFuture.getResult();
+ actualRef.set(resultBundle);
+ } catch (OperationCanceledException e) {
+ fail("should not throw an OperationCanceledException");
+ } catch (IOException e) {
+ fail("should not throw an IOException");
+ } catch (AuthenticatorException e) {
+ fail("should not throw an AuthenticatorException");
+ } finally {
+ latch.countDown();
+ }
+ }
+ };
+
+ am.getAuthToken(getAccount(),
+ tokenType,
+ OPTIONS_BUNDLE,
+ false /* no failure notification */,
+ callback,
+ new Handler(handlerThread.getLooper()));
+
+ // Wait with timeout for the callback to do its work
+ try {
+ latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("should not throw an InterruptedException");
+ }
+ return actualRef.get();
+ }
+
+ @Override
+ public Account getAccount() {
+ return CUSTOM_TOKEN_ACCOUNT;
+ }
+ };
+ validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_TOKEN_TYPE);
+ validateSuccessfulTokenFetchingLifecycle(fetcherWithOptions, AUTH_EXPIRING_TOKEN_TYPE);
+ }
+
+ /**
* Test getAuthToken() with callback and handler
*/
public void testGetAuthTokenWithCallbackAndHandler() throws IOException, AuthenticatorException,
@@ -837,6 +1193,7 @@
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
@@ -880,37 +1237,6 @@
}
/**
- * test getAuthToken() with options
- */
- public void testGetAuthTokenWithOptions() throws IOException, AuthenticatorException,
- OperationCanceledException {
-
- addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
-
- AccountManagerFuture<Bundle> futureBundle = am.getAuthToken(ACCOUNT,
- AUTH_TOKEN_TYPE,
- OPTIONS_BUNDLE,
- mActivity,
- null /* no callback */,
- null /* no handler */
- );
-
- Bundle resultBundle = futureBundle.getResult();
-
- assertTrue(futureBundle.isDone());
- assertNotNull(resultBundle);
-
- // Assert returned result
- validateAccountAndAuthTokenResult(resultBundle);
-
- validateOptions(null, mockAuthenticator.mOptionsAddAccount);
- validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
- validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
- validateOptions(OPTIONS_BUNDLE, mockAuthenticator.mOptionsGetAuthToken);
- validateSystemOptions(mockAuthenticator.mOptionsGetAuthToken);
- }
-
- /**
* test getAuthToken() with options and callback and handler
*/
public void testGetAuthTokenWithOptionsAndCallback() throws IOException,
@@ -928,15 +1254,14 @@
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
try {
resultBundle = bundleFuture.getResult();
-
// Assert returned result
validateAccountAndAuthTokenResult(resultBundle);
-
} catch (OperationCanceledException e) {
fail("should not throw an OperationCanceledException");
} catch (IOException e) {
@@ -1039,7 +1364,6 @@
validateOptions(null, mockAuthenticator.mOptionsUpdateCredentials);
validateOptions(null, mockAuthenticator.mOptionsConfirmCredentials);
validateOptions(null, mockAuthenticator.mOptionsGetAuthToken);
-
}
/**
@@ -1154,8 +1478,7 @@
/**
* Test confirmCredentials() with callback
*/
- public void testConfirmCredentialsWithCallbackAndHandler() throws IOException,
- AuthenticatorException, OperationCanceledException {
+ public void testConfirmCredentialsWithCallbackAndHandler() {
addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
@@ -1163,12 +1486,10 @@
testConfirmCredentialsWithCallbackAndHandler(new Handler(Looper.getMainLooper()));
}
- private void testConfirmCredentialsWithCallbackAndHandler(Handler handler) throws IOException,
- AuthenticatorException, OperationCanceledException {
-
+ private void testConfirmCredentialsWithCallbackAndHandler(Handler handler) {
final CountDownLatch latch = new CountDownLatch(1);
-
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
@@ -1191,15 +1512,11 @@
}
}
};
-
AccountManagerFuture<Bundle> futureBundle = am.confirmCredentials(ACCOUNT,
OPTIONS_BUNDLE,
mActivity,
callback,
handler);
-
- // futureBundle.getResult();
-
// Wait with timeout for the callback to do its work
try {
latch.await(3 * LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -1249,6 +1566,7 @@
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
Bundle resultBundle = null;
@@ -1320,6 +1638,7 @@
final CountDownLatch latch = new CountDownLatch(1);
AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+ @Override
public void run(AccountManagerFuture<Bundle> bundleFuture) {
try {
// Assert returned result
@@ -1381,6 +1700,7 @@
final CountDownLatch latch = new CountDownLatch(1);
OnAccountsUpdateListener listener = new OnAccountsUpdateListener() {
+ @Override
public void onAccountsUpdated(Account[] accounts) {
latch.countDown();
}
@@ -1423,6 +1743,7 @@
final CountDownLatch latch = new CountDownLatch(1);
OnAccountsUpdateListener listener = new OnAccountsUpdateListener() {
+ @Override
public void onAccountsUpdated(Account[] accounts) {
fail("should not be called");
}
@@ -1513,6 +1834,7 @@
private AccountManagerCallback<Boolean> getAssertTrueCallback(final CountDownLatch latch) {
return new AccountManagerCallback<Boolean>() {
+ @Override
public void run(AccountManagerFuture<Boolean> booleanFuture) {
try {
// Assert returned result should be TRUE
@@ -1528,6 +1850,7 @@
private AccountManagerCallback<Boolean> getAssertFalseCallback(final CountDownLatch latch) {
return new AccountManagerCallback<Boolean>() {
+ @Override
public void run(AccountManagerFuture<Boolean> booleanFuture) {
try {
// Assert returned result should be FALSE
@@ -1613,7 +1936,7 @@
Bundle options = new Bundle();
options.putBoolean(MockAccountAuthenticator.KEY_RETURN_INTENT, true);
// Not really confirming, but a way to get last authenticated timestamp
- Bundle result = am.confirmCredentials(ACCOUNT,
+ Bundle result = am.confirmCredentials(account,
options,// OPTIONS_BUNDLE,
null, /* activity */
null /* callback */,
@@ -1631,8 +1954,7 @@
/**
* Tests that AccountManagerService is properly caching data.
*/
- public void testGetsAreCached() throws IOException, AuthenticatorException,
- OperationCanceledException {
+ public void testGetsAreCached() {
// Add an account,
assertEquals(false, isAccountPresent(am.getAccounts(), ACCOUNT));
diff --git a/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java b/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java
index eec4eff..faebd53 100644
--- a/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java
+++ b/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java
@@ -24,13 +24,17 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* A simple Mock Account Authenticator
*/
public class MockAccountAuthenticator extends AbstractAccountAuthenticator {
+ private static String TAG = "AccountManagerTest";
public static String KEY_ACCOUNT_INFO = "key_account_info";
public static String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "key_account_authenticator_response";
@@ -40,6 +44,9 @@
public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API1 = "call new removeAccount api";
private final Context mContext;
+ private final AtomicInteger mTokenCounter = new AtomicInteger(0);
+ private final AtomicBoolean mIsRecentlyCalled = new AtomicBoolean(false);
+
AccountAuthenticatorResponse mResponse;
String mAccountType;
String mAuthTokenType;
@@ -52,6 +59,7 @@
String[] mFeatures;
final ArrayList<String> mockFeatureList = new ArrayList<String>();
+ private final long mTokenDurationMillis = 1000; // 1 second
public MockAccountAuthenticator(Context context) {
super(context);
@@ -62,6 +70,18 @@
mockFeatureList.add(AccountManagerTest.FEATURE_2);
}
+ public long getTokenDurationMillis() {
+ return mTokenDurationMillis;
+ }
+
+ public boolean isRecentlyCalled() {
+ return mIsRecentlyCalled.getAndSet(false);
+ }
+
+ public String getLastTokenServed() {
+ return Integer.toString(mTokenCounter.get());
+ }
+
public AccountAuthenticatorResponse getResponse() {
return mResponse;
}
@@ -113,8 +133,9 @@
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE);
- result.putString(AccountManager.KEY_AUTHTOKEN, AccountManagerTest.AUTH_TOKEN);
-
+ result.putString(
+ AccountManager.KEY_AUTHTOKEN,
+ Integer.toString(mTokenCounter.incrementAndGet()));
return result;
}
@@ -125,13 +146,11 @@
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
String authTokenType, String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
-
this.mResponse = response;
this.mAccountType = accountType;
this.mAuthTokenType = authTokenType;
this.mRequiredFeatures = requiredFeatures;
this.mOptionsAddAccount = options;
-
return createResultBundle();
}
@@ -141,12 +160,10 @@
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle options) throws NetworkErrorException {
-
this.mResponse = response;
this.mAccount = account;
this.mAuthTokenType = authTokenType;
this.mOptionsUpdateCredentials = options;
-
return createResultBundle();
}
@@ -157,10 +174,8 @@
*/
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
-
this.mResponse = response;
this.mAccountType = accountType;
-
return createResultBundle();
}
@@ -170,11 +185,9 @@
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
Bundle options) throws NetworkErrorException {
-
this.mResponse = response;
this.mAccount = account;
this.mOptionsConfirmCredentials = options;
-
Bundle result = new Bundle();
if (options.containsKey(KEY_RETURN_INTENT)) {
Intent intent = new Intent();
@@ -191,15 +204,35 @@
* Gets the authtoken for an account.
*/
@Override
- public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
- String authTokenType, Bundle options) throws NetworkErrorException {
-
+ public Bundle getAuthToken(
+ AccountAuthenticatorResponse response,
+ Account account,
+ String authTokenType,
+ Bundle options) throws NetworkErrorException {
+ Log.w(TAG, "MockAuth - getAuthToken@" + System.currentTimeMillis());
+ mIsRecentlyCalled.set(true);
this.mResponse = response;
this.mAccount = account;
this.mAuthTokenType = authTokenType;
this.mOptionsGetAuthToken = options;
+ Bundle result = new Bundle();
- return createResultBundle();
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ String token;
+ if (AccountManagerTest.AUTH_EXPIRING_TOKEN_TYPE.equals(authTokenType)) {
+ /*
+ * The resultant token should simply be the expiration timestamp. E.g. the time after
+ * which getting a new AUTH_EXPIRING_TOKEN_TYPE typed token will return a different
+ * value.
+ */
+ long expiry = System.currentTimeMillis() + mTokenDurationMillis;
+ result.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, expiry);
+ }
+ result.putString(
+ AccountManager.KEY_AUTHTOKEN,
+ Integer.toString(mTokenCounter.incrementAndGet()));
+ return result;
}
/**
@@ -208,7 +241,6 @@
@Override
public String getAuthTokenLabel(String authTokenType) {
this.mAuthTokenType = authTokenType;
-
return AccountManagerTest.AUTH_TOKEN_LABEL;
}
@@ -261,4 +293,4 @@
}
return result;
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/accounts/src/android/accounts/cts/MockCustomTokenAccountService.java b/tests/tests/accounts/src/android/accounts/cts/MockCustomTokenAccountService.java
new file mode 100644
index 0000000..ea06a41
--- /dev/null
+++ b/tests/tests/accounts/src/android/accounts/cts/MockCustomTokenAccountService.java
@@ -0,0 +1,13 @@
+package android.accounts.cts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class MockCustomTokenAccountService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return AccountManagerTest.getMockAuthenticator(this).getIBinder();
+ }
+}
diff --git a/tests/tests/app/AndroidManifest.xml b/tests/tests/app/AndroidManifest.xml
index 231727f..8e17396 100644
--- a/tests/tests/app/AndroidManifest.xml
+++ b/tests/tests/app/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="com.android.cts.app">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
<application>
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
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/location/src/android/location/cts/LocationTest.java b/tests/tests/location/src/android/location/cts/LocationTest.java
index b60b8b1..db2820c 100644
--- a/tests/tests/location/src/android/location/cts/LocationTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationTest.java
@@ -437,7 +437,7 @@
final long timeout = 500;
// should refuse binding
- IBinder binder = service.onBind(intent);
+ IBinder binder = service.callOnBind(intent);
assertNull("onBind should always fail.", binder);
// test if result consistent with the truth
@@ -445,7 +445,7 @@
service.setEnabled(false);
resultHandler.expectEnabled(false);
resultHandler.expectMessage(true);
- ret = service.onStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
+ ret = service.callOnStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
assertEquals("onStartCommand return value invalid in (enabled == false) case.",
ret, SettingInjectorService.START_NOT_STICKY);
assertTrue("Message time out in (enabled == false case).",
@@ -455,7 +455,7 @@
service.setEnabled(true);
resultHandler.expectEnabled(true);
resultHandler.expectMessage(true);
- ret = service.onStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
+ ret = service.callOnStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
assertEquals("onStartCommand return value invalid in (enabled == true) case.",
ret, SettingInjectorService.START_NOT_STICKY);
assertTrue("Message time out in (enabled == true) case.",
@@ -463,7 +463,7 @@
// should not respond to the deprecated method
resultHandler.expectMessage(false);
- service.onStart(intent, 0);
+ service.callOnStart(intent, 0);
resultHandler.waitForMessage(timeout);
}
@@ -574,6 +574,20 @@
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
+
+ // API coverage dashboard will not cound method call from derived class.
+ // Thus, it is necessary to make explicit call to SettingInjectorService public methods.
+ public IBinder callOnBind(Intent intent) {
+ return super.onBind(intent);
+ }
+
+ public void callOnStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+ }
+
+ public int callOnStartCommand(Intent intent, int flags, int startId) {
+ return super.onStartCommand(intent, flags, startId);
+ }
}
}
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/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index 3a64658..e3f0e0a 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -21,6 +21,7 @@
import android.text.GetChars;
import android.text.GraphicsOperations;
import android.text.Layout.Alignment;
+import android.text.TextUtils.TruncateAt;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.SpannedString;
@@ -113,6 +114,104 @@
}
}
+ public void testBuilder() {
+ {
+ // Obtain.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ StaticLayout layout = builder.build();
+ // Check values passed to obtain().
+ assertEquals(LAYOUT_TEXT, layout.getText());
+ assertEquals(mDefaultPaint, layout.getPaint());
+ assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
+ // Check default values.
+ assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ layout.getTextDirectionHeuristic());
+ assertEquals(Alignment.ALIGN_NORMAL, layout.getAlignment());
+ assertEquals(0.0f, layout.getSpacingAdd());
+ assertEquals(1.0f, layout.getSpacingMultiplier());
+ assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth());
+ }
+ {
+ // Obtain with null objects.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(null, 0, 0, null, 0);
+ try {
+ StaticLayout layout = builder.build();
+ fail("should throw NullPointerException here");
+ } catch (NullPointerException e) {
+ }
+ }
+ {
+ // setText.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setText(LAYOUT_TEXT_SINGLE_LINE);
+ StaticLayout layout = builder.build();
+ assertEquals(LAYOUT_TEXT_SINGLE_LINE, layout.getText());
+ }
+ {
+ // setAlignment.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setAlignment(DEFAULT_ALIGN);
+ StaticLayout layout = builder.build();
+ assertEquals(DEFAULT_ALIGN, layout.getAlignment());
+ }
+ {
+ // setTextDirection.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setTextDirection(TextDirectionHeuristics.RTL);
+ StaticLayout layout = builder.build();
+ // Always returns TextDirectionHeuristics.FIRSTSTRONG_LTR.
+ assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ layout.getTextDirectionHeuristic());
+ }
+ {
+ // setLineSpacing.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setLineSpacing(1.0f, 2.0f);
+ StaticLayout layout = builder.build();
+ assertEquals(1.0f, layout.getSpacingAdd());
+ assertEquals(2.0f, layout.getSpacingMultiplier());
+ }
+ {
+ // setEllipsizedWidth and setEllipsize.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setEllipsize(TruncateAt.END);
+ builder.setEllipsizedWidth(ELLIPSIZE_WIDTH);
+ StaticLayout layout = builder.build();
+ assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth());
+ assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
+ assertTrue(layout.getEllipsisCount(0) == 0);
+ assertTrue(layout.getEllipsisCount(5) > 0);
+ }
+ {
+ // setMaxLines.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setMaxLines(1);
+ builder.setEllipsize(TruncateAt.END);
+ StaticLayout layout = builder.build();
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ assertEquals(1, layout.getLineCount());
+ }
+ {
+ // Setter methods that cannot be directly tested.
+ // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setIndents.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY);
+ builder.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_FULL);
+ builder.setIncludePad(true);
+ builder.setIndents(null, null);
+ StaticLayout layout = builder.build();
+ assertNotNull(layout);
+ }
+ }
+
/*
* Get the line number corresponding to the specified vertical position.
* If you ask for a position above 0, you get 0. above 0 means pixel above the fire line
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index bf7f757..e3144eb 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -53,6 +53,7 @@
<TextView android:id="@+id/textview_text"
android:text="@string/text_view_hello"
+ android:breakStrategy="simple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
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