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