Merge "Don't try to create user when on a single-user device." into oc-dev am: a8d86dac2f
am: d1888109e5

Change-Id: I2a237a05b32ffa1c2b88a805c1410edc68b06832
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index fd17de2..70f5e1cc 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -69,6 +69,9 @@
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
+    <!-- Needed for Telecom self-managed ConnectionService tests. -->
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+
     <application android:label="@string/app_name"
             android:icon="@drawable/icon"
             android:backupAgent="VerifierBackupAgent"
@@ -3077,6 +3080,22 @@
             <meta-data
                 android:name="test_required_features"
                 android:value="android.hardware.telephony"/>
+            </activity>
+
+        <activity
+            android:name=".telecom.SelfManagedIncomingCallTestActivity"
+            android:label="@string/telecom_incoming_self_mgd_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_telecom"/>
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.telephony"/>
         </activity>
 
         <activity
diff --git a/apps/CtsVerifier/res/layout/telecom_self_managed_answer.xml b/apps/CtsVerifier/res/layout/telecom_self_managed_answer.xml
new file mode 100644
index 0000000..27aa55a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/telecom_self_managed_answer.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/telecom_incoming_self_mgd_info"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/js_padding"
+        android:layout_marginBottom="@dimen/js_padding">
+
+        <ImageView
+            android:id="@+id/step_1_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_marginRight="@dimen/js_padding"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <TextView
+            android:id="@+id/step_1_instructions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/telecom_incoming_self_mgd_step_1"
+            android:textSize="16dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/step_1_status" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/step_1_instructions"
+            android:layout_marginLeft="20dip"
+            android:layout_marginRight="20dip"
+            android:layout_toRightOf="@id/step_1_status"
+            android:id="@+id/telecom_incoming_self_mgd_register_button"
+            android:text="@string/telecom_incoming_self_mgd_register_button"/>
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/js_padding"
+        android:layout_marginBottom="@dimen/js_padding">
+
+        <ImageView
+            android:id="@+id/step_2_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_marginRight="@dimen/js_padding"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <TextView
+            android:id="@+id/step_2_instructions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/telecom_incoming_self_mgd_step_2"
+            android:textSize="16dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/step_2_status" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/step_2_instructions"
+            android:layout_marginLeft="20dip"
+            android:layout_marginRight="20dip"
+            android:layout_toRightOf="@id/step_2_status"
+            android:id="@+id/telecom_incoming_self_mgd_show_ui_button"
+            android:text="@string/telecom_incoming_self_mgd_show_ui_button"/>
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/js_padding"
+        android:layout_marginBottom="@dimen/js_padding">
+
+        <ImageView
+            android:id="@+id/step_3_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_marginRight="@dimen/js_padding"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <TextView
+            android:id="@+id/step_3_instructions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/telecom_incoming_self_mgd_step_3"
+            android:textSize="16dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/step_3_status" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/step_3_instructions"
+            android:layout_marginLeft="20dip"
+            android:layout_marginRight="20dip"
+            android:layout_toRightOf="@id/step_3_status"
+            android:id="@+id/telecom_incoming_self_mgd_confirm_answer_button"
+            android:text="@string/telecom_incoming_self_mgd_confirm_answer_button"/>
+    </RelativeLayout>
+
+    <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 1d3ea85..e3eefd6 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -3935,4 +3935,22 @@
         audio was audible.
     </string>
     <string name="telecom_incoming_call_confirm_button">Confirm</string>
+    <string name="telecom_incoming_self_mgd_test"> Incoming Self-Managed Connection Test</string>
+    <string name="telecom_incoming_self_mgd_info">
+        This test verifies that incoming calls from a Self-Managed Connection Service will trigger
+        a Telecom-managed incoming call UI when there is already an ongoing call on the device.
+    </string>
+    <string name="telecom_incoming_self_mgd_step_1">
+        Click the button below to register a test self-managed ConnectionService.
+    </string>
+    <string name="telecom_incoming_self_mgd_register_button">Register Self-Managed ConnectionService</string>
+    <string name="telecom_incoming_self_mgd_step_2">
+        Click the button below to test that the system incoming call notification shows.  When the
+        notification shows, "answer" the call.
+    </string>
+    <string name="telecom_incoming_self_mgd_show_ui_button">Show System Incoming UI</string>
+    <string name="telecom_incoming_self_mgd_step_3">
+        Click the button below to confirm that the incoming call was answered.
+    </string>
+    <string name="telecom_incoming_self_mgd_confirm_answer_button">Confirm Answer</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index ffe29d2..8c779c5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -118,11 +118,16 @@
 
         ICaseResult caseResult = moduleResult.getOrCreateResult(TEST_CASE_NAME);
         int count = mAdapter.getCount();
+        int notExecutedCount = 0;
         for (int i = 0; i < count; i++) {
             TestListItem item = mAdapter.getItem(i);
             if (item.isTest()) {
                 ITestResult currentTestResult = caseResult.getOrCreateResult(item.testName);
-                currentTestResult.setResultStatus(getTestResultStatus(mAdapter.getTestResult(i)));
+                TestStatus resultStatus = getTestResultStatus(mAdapter.getTestResult(i));
+                if (resultStatus == null) {
+                    ++notExecutedCount;
+                }
+                currentTestResult.setResultStatus(resultStatus);
                 // TODO: report test details with Extended Device Info (EDI) or CTS metrics
                 // String details = mAdapter.getTestDetails(i);
 
@@ -133,6 +138,7 @@
             }
         }
         moduleResult.setDone(true);
+        moduleResult.setNotExecuted(notExecutedCount);
 
         return result;
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java
index c935841..c004b73 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java
@@ -20,12 +20,16 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaPlayer;
+import android.telecom.CallAudioState;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.VideoProfile;
 
 import com.android.cts.verifier.R;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * An implementation of the {@link android.telecom.Connection} class used by the
  * {@link CtsConnectionService}.
@@ -51,6 +55,7 @@
     private final Listener mListener;
     private final MediaPlayer mMediaPlayer;
     private final Context mContext;
+    private CountDownLatch mWaitForCallAudioStateChanged = new CountDownLatch(1);
 
     public CtsConnection(Context context, boolean isIncomingCall,
             Listener listener, boolean hasAudio) {
@@ -131,6 +136,20 @@
         }
     }
 
+    public void onCallAudioStateChanged(CallAudioState state) {
+        mWaitForCallAudioStateChanged.countDown();
+        mWaitForCallAudioStateChanged = new CountDownLatch(1);
+
+    }
+
+    public void waitForAudioStateChanged() {
+        try {
+            mWaitForCallAudioStateChanged.await(CtsConnectionService.TIMEOUT_MILLIS,
+                    TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+        }
+    }
+
     private void setDisconnectedAndDestroy(DisconnectCause cause) {
         setDisconnected(cause);
         destroy();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java
index 528c221..7cfcbf8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java
@@ -26,35 +26,74 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * CTS Verifier ConnectionService implementation.
  */
 public class CtsConnectionService extends ConnectionService {
+    static final int TIMEOUT_MILLIS = 10000;
 
     private CtsConnection.Listener mConnectionListener =
             new CtsConnection.Listener() {
                 @Override
                 void onDestroyed(CtsConnection connection) {
-                    mConnections.remove(connection);
+                    synchronized (mConnectionsLock) {
+                        mConnections.remove(connection);
+                    }
                 }
             };
 
     private static CtsConnectionService sConnectionService;
+    private static CountDownLatch sBindingLatch = new CountDownLatch(1);
 
     private List<CtsConnection> mConnections = new ArrayList<>();
+    private Object mConnectionsLock = new Object();
+    private CountDownLatch mConnectionLatch = new CountDownLatch(1);
 
     public static CtsConnectionService getConnectionService() {
         return sConnectionService;
     }
 
+    public static CtsConnectionService waitForAndGetConnectionService() {
+        if (sConnectionService == null) {
+            try {
+                sBindingLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
+        }
+        return sConnectionService;
+    }
+
     public CtsConnectionService() throws Exception {
         super();
         sConnectionService = this;
+        if (sBindingLatch != null) {
+            sBindingLatch.countDown();
+        }
+        sBindingLatch = new CountDownLatch(1);
     }
 
     public List<CtsConnection> getConnections() {
-        return mConnections;
+        synchronized (mConnectionsLock) {
+            return new ArrayList<CtsConnection>(mConnections);
+        }
+    }
+
+    public CtsConnection waitForAndGetConnection() {
+        try {
+            mConnectionLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+        }
+        mConnectionLatch = new CountDownLatch(1);
+        synchronized (mConnectionsLock) {
+            if (mConnections.size() > 0) {
+                return mConnections.get(0);
+            } else {
+                return null;
+            }
+        }
     }
 
     @Override
@@ -109,7 +148,12 @@
         connection.putExtras(moreExtras);
         connection.setVideoState(request.getVideoState());
 
-        mConnections.add(connection);
+        synchronized (mConnectionsLock) {
+            mConnections.add(connection);
+        }
+        if (mConnectionLatch != null) {
+            mConnectionLatch.countDown();
+        }
         return connection;
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java
index cdc8665..e134b51 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java
@@ -57,6 +57,19 @@
             .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
             .build();
 
+    public static final String TEST_SELF_MAANGED_PHONE_ACCOUNT2_ID = "selfMgdTest2";
+    public static final String TEST_SELF_MANAGED_PHONE_ACCOUNT2_LABEL = "CTSVerifier2";
+
+    public static final PhoneAccountHandle TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2 =
+            new PhoneAccountHandle(new ComponentName(
+                    PassFailButtons.class.getPackage().getName(),
+                    CtsConnectionService.class.getName()), TEST_SELF_MAANGED_PHONE_ACCOUNT2_ID);
+    public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_2 = new PhoneAccount.Builder(
+            TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2, TEST_SELF_MANAGED_PHONE_ACCOUNT2_LABEL)
+            .setAddress(TEST_SELF_MANAGED_PHONE_ACCOUNT_ADDRESS)
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .build();
+
     /**
      * Registers the test phone account.
      * @param context The context.
@@ -96,6 +109,7 @@
         TelecomManager telecomManager = (TelecomManager) context.getSystemService(
                 Context.TELECOM_SERVICE);
         telecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT);
+        telecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_2);
     }
 
     /**
@@ -119,4 +133,15 @@
                 Context.TELECOM_SERVICE);
         return telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
     }
+
+    /**
+     * Retrieves the test phone account, or null if not registered.
+     * @param context The context.
+     * @return The Phone Account.
+     */
+    public static PhoneAccount getSelfManagedPhoneAccount2(Context context) {
+        TelecomManager telecomManager = (TelecomManager) context.getSystemService(
+                Context.TELECOM_SERVICE);
+        return telecomManager.getPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2);
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
new file mode 100644
index 0000000..530b246
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.telecom;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.List;
+
+/**
+ * This test verifies functionality associated with the Self-Managed
+ * {@link android.telecom.ConnectionService} APIs.  It ensures that Telecom will show an incoming
+ * call UI when a new incoming self-managed call is added when there is already an ongoing managed
+ * call or when there is an ongoing self-managed call in another app.
+ */
+public class SelfManagedIncomingCallTestActivity extends PassFailButtons.Activity {
+    private Uri TEST_DIAL_NUMBER_1 = Uri.fromParts("tel", "6505551212", null);
+    private Uri TEST_DIAL_NUMBER_2 = Uri.fromParts("tel", "4085551212", null);
+
+    private ImageView mStep1Status;
+    private Button mRegisterPhoneAccount;
+    private ImageView mStep2Status;
+    private Button mShowUi;
+    private ImageView mStep3Status;
+    private Button mConfirm;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.telecom_self_managed_answer, null);
+        setContentView(view);
+        setInfoResources(R.string.telecom_incoming_self_mgd_test,
+                R.string.telecom_incoming_self_mgd_info, -1);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mStep1Status = view.findViewById(R.id.step_1_status);
+        mRegisterPhoneAccount = view.findViewById(R.id.telecom_incoming_self_mgd_register_button);
+        mRegisterPhoneAccount.setOnClickListener(v -> {
+            PhoneAccountUtils.registerTestSelfManagedPhoneAccount(this);
+            PhoneAccount account = PhoneAccountUtils.getSelfManagedPhoneAccount(this);
+            PhoneAccount account2 = PhoneAccountUtils.getSelfManagedPhoneAccount2(this);
+            if (account != null &&
+                    account.isEnabled() &&
+                    account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) &&
+                    account2 != null &&
+                    account2.isEnabled() &&
+                    account2.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+                mRegisterPhoneAccount.setEnabled(false);
+                mShowUi.setEnabled(true);
+                mStep1Status.setImageResource(R.drawable.fs_good);
+            } else {
+                mStep1Status.setImageResource(R.drawable.fs_error);
+            }
+        });
+
+        mStep2Status = view.findViewById(R.id.step_2_status);
+        mShowUi = view.findViewById(R.id.telecom_incoming_self_mgd_show_ui_button);
+        mShowUi.setOnClickListener(v -> {
+            (new AsyncTask<Void, Void, Throwable>() {
+                @Override
+                protected Throwable doInBackground(Void... params) {
+                    try {
+                        Bundle extras = new Bundle();
+                        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                                TEST_DIAL_NUMBER_1);
+                        TelecomManager telecomManager =
+                                (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+                        if (telecomManager == null) {
+                            mStep2Status.setImageResource(R.drawable.fs_error);
+                            return new Throwable("Could not get telecom service.");
+                        }
+                        telecomManager.addNewIncomingCall(
+                                PhoneAccountUtils.TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE, extras);
+
+                        CtsConnectionService ctsConnectionService =
+                                CtsConnectionService.waitForAndGetConnectionService();
+                        if (ctsConnectionService == null) {
+                            mStep2Status.setImageResource(R.drawable.fs_error);
+                            return new Throwable("Could not get connection service.");
+                        }
+
+                        CtsConnection connection = ctsConnectionService.waitForAndGetConnection();
+                        if (connection == null) {
+                            mStep2Status.setImageResource(R.drawable.fs_error);
+                            return new Throwable("Could not get connection.");
+                        }
+                        // Wait until the connection knows its audio state changed; at this point
+                        // Telecom knows about the connection and can answer.
+                        connection.waitForAudioStateChanged();
+                        // Make it active to simulate an answer.
+                        connection.setActive();
+
+                        // Place the second call. It should trigger the incoming call UX.
+                        Bundle extras2 = new Bundle();
+                        extras2.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                                TEST_DIAL_NUMBER_2);
+                        telecomManager.addNewIncomingCall(
+                                PhoneAccountUtils.TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2,
+                                extras2);
+
+                        return null;
+                    } catch (Throwable t) {
+                        return  t;
+                    }
+                }
+
+                @Override
+                protected void onPostExecute(Throwable t) {
+                    if (t == null) {
+                        mStep2Status.setImageResource(R.drawable.fs_good);
+                        mShowUi.setEnabled(false);
+                        mConfirm.setEnabled(true);
+                    } else {
+                        mStep2Status.setImageResource(R.drawable.fs_error);
+                    }
+                }
+            }).execute();
+
+
+        });
+
+        mStep3Status = view.findViewById(R.id.step_2_status);
+        mConfirm = view.findViewById(R.id.telecom_incoming_self_mgd_confirm_answer_button);
+        mConfirm.setOnClickListener(v -> {
+            CtsConnectionService ctsConnectionService = CtsConnectionService.getConnectionService();
+            if (ctsConnectionService == null) {
+                mStep3Status.setImageResource(R.drawable.fs_error);
+                return;
+            }
+            List<CtsConnection> connections = ctsConnectionService.getConnections();
+            if (connections.size() != 1) {
+                mStep3Status.setImageResource(R.drawable.fs_error);
+                return;
+            }
+
+            if (connections.get(0).getState() == Connection.STATE_ACTIVE) {
+                mStep3Status.setImageResource(R.drawable.fs_good);
+                getPassButton().setEnabled(true);
+            } else {
+                mStep3Status.setImageResource(R.drawable.fs_error);
+            }
+        });
+
+        mShowUi.setEnabled(false);
+        mConfirm.setEnabled(false);
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 09fed81..970e35f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -18,7 +18,7 @@
 import com.android.compatibility.SuiteInfo;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
-import com.android.compatibility.common.tradefed.result.SubPlanCreator;
+import com.android.compatibility.common.tradefed.result.SubPlanHelper;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.ResultHandler;
@@ -404,7 +404,7 @@
     }
 
     private void addSubPlan(String[] flatArgs) {
-        SubPlanCreator creator = new SubPlanCreator();
+        SubPlanHelper creator = new SubPlanHelper();
         try {
             ArgsOptionParser optionParser = new ArgsOptionParser(creator);
             optionParser.parse(Arrays.asList(flatArgs));
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 6887d48..4df29c6 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -17,7 +17,7 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest.RetryType;
+import com.android.compatibility.common.tradefed.util.RetryType;
 import com.android.compatibility.common.util.ICaseResult;
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.IModuleResult;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanCreator.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
similarity index 89%
rename from common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanCreator.java
rename to common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
index 9dbbcbb..950a129 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanCreator.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
@@ -33,11 +33,15 @@
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+import com.android.tradefed.util.StreamUtil;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.InputStream;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
@@ -50,7 +54,9 @@
 /**
  * Class for creating subplans from compatibility result XML.
  */
-public class SubPlanCreator {
+public class SubPlanHelper {
+
+    private static final String XML_EXT = ".xml";
 
     // result types
     public static final String PASSED = "passed";
@@ -108,22 +114,45 @@
     IInvocationResult mResult = null;
 
     /**
-     * Create an empty {@link SubPlanCreator}.
+     * Create an empty {@link SubPlanHelper}.
      * <p/>
      * All {@link Option} fields must be populated via
      * {@link com.android.tradefed.config.ArgsOptionParser}
      */
-    public SubPlanCreator() {}
+    public SubPlanHelper() {}
 
     /**
-     * Create a {@link SubPlanCreator} using the specified option values.
+     * Create a {@link SubPlanHelper} using the specified option values.
      */
-    public SubPlanCreator(String name, int session, Collection<String> resultTypes) {
+    public SubPlanHelper(String name, int session, Collection<String> resultTypes) {
         mSubPlanName = name;
         mSessionId = session;
         mResultTypes.addAll(resultTypes);
     }
 
+    public static ISubPlan getSubPlanByName(CompatibilityBuildHelper buildHelper, String name) {
+        if (!name.endsWith(XML_EXT)) {
+            name = name + XML_EXT; // only append XML extension to name if not already there
+        }
+        InputStream subPlanInputStream = null;
+        try {
+            File subPlanFile = new File(buildHelper.getSubPlansDir(), name);
+            if (!subPlanFile.exists()) {
+                throw new IllegalArgumentException(
+                        String.format("Could not retrieve subplan \"%s\"", name));
+            }
+            subPlanInputStream = new FileInputStream(subPlanFile);
+            ISubPlan subPlan = new SubPlan();
+            subPlan.parse(subPlanInputStream);
+            return subPlan;
+        } catch (FileNotFoundException | ParseException e) {
+            throw new RuntimeException(
+                    String.format("Unable to find or parse subplan %s", name), e);
+        } finally {
+            StreamUtil.closeStream(subPlanInputStream);
+        }
+    }
+
     /**
      * Set the result from which to derive the subplan.
      * @param result
@@ -333,7 +362,7 @@
             mSubPlanName = createPlanName();
         }
         try {
-            mSubPlanFile = new File(buildHelper.getSubPlansDir(), mSubPlanName + ".xml");
+            mSubPlanFile = new File(buildHelper.getSubPlansDir(), mSubPlanName + XML_EXT);
             if (mSubPlanFile.exists()) {
                 throw new ConfigurationException(String.format("Subplan %s already exists",
                         mSubPlanName));
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 30d5f95..af1342a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -19,15 +19,16 @@
 import com.android.compatibility.SuiteInfo;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
-import com.android.compatibility.common.tradefed.result.SubPlanCreator;
+import com.android.compatibility.common.tradefed.result.SubPlanHelper;
 import com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker;
 import com.android.compatibility.common.tradefed.util.OptionHelper;
+import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
+import com.android.compatibility.common.tradefed.util.RetryType;
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.ResultHandler;
 import com.android.compatibility.common.util.TestFilter;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.ArgsOptionParser;
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
@@ -153,10 +154,6 @@
             importance = Importance.ALWAYS)
     private List<String> mTestArgs = new ArrayList<>();
 
-    public enum RetryType {
-        FAILED, NOT_EXECUTED;
-    }
-
     @Option(name = RETRY_OPTION,
             shortName = 'r',
             description = "retry a previous session's failed and not executed tests.",
@@ -671,113 +668,47 @@
      */
     void setupFilters() throws DeviceNotAvailableException {
         if (mRetrySessionId != null) {
-            // Track --module/-m and --test/-t options to ensure we don't overwrite non-null
-            // values on retry
-            String newModuleName = mModuleName;
-            String newTestName = mTestName;
-
             // Load the invocation result
             IInvocationResult result = null;
-            try {
-                result = ResultHandler.findResult(mBuildHelper.getResultsDir(), mRetrySessionId);
-            } catch (FileNotFoundException e) {
-                throw new RuntimeException(e);
-            }
-            if (result == null) {
-                throw new IllegalArgumentException(String.format(
-                        "Could not find session with id %d", mRetrySessionId));
-            }
-
-            String oldBuildFingerprint = result.getBuildFingerprint();
-            String currentBuildFingerprint = mDevice.getProperty("ro.build.fingerprint");
-            if (oldBuildFingerprint.equals(currentBuildFingerprint)) {
-                CLog.logAndDisplay(LogLevel.INFO, "Retrying session from: %s",
-                        CompatibilityBuildHelper.getDirSuffix(result.getStartTime()));
-            } else {
-                throw new IllegalArgumentException(String.format(
-                        "Device build fingerprint must match %s to retry session %d",
-                        oldBuildFingerprint, mRetrySessionId));
-            }
-
-            String retryCommandLineArgs = result.getCommandLineArgs();
-            if (retryCommandLineArgs != null) {
-                try {
-                    // parse the command-line string from the result file and set options
-                    ArgsOptionParser parser = new ArgsOptionParser(this);
-                    parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, this));
-                } catch (ConfigurationException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-
-            if ((mModuleName != null && mModuleName != newModuleName)
-                    || (mTestName != null && mTestName != newTestName)) {
-                // These options cannot be changed on retry if non-null for the previous session
-                CLog.w("Cannot override non-null value(s) from session %d for option(s) \"%s\""
-                        + " or \"%s\" on retry", mRetrySessionId, MODULE_OPTION, TEST_OPTION);
-            }
-
-            SubPlanCreator retryPlanCreator = new SubPlanCreator();
-            retryPlanCreator.setResult(result);
-            if (RetryType.FAILED.equals(mRetryType)) {
-                // retry only failed tests
-                retryPlanCreator.addResultType(SubPlanCreator.FAILED);
-            } else if (RetryType.NOT_EXECUTED.equals(mRetryType)){
-                // retry only not executed tests
-                retryPlanCreator.addResultType(SubPlanCreator.NOT_EXECUTED);
-            } else {
-                // retry both failed and not executed tests
-                retryPlanCreator.addResultType(SubPlanCreator.FAILED);
-                retryPlanCreator.addResultType(SubPlanCreator.NOT_EXECUTED);
-            }
-            try {
-                ISubPlan retryPlan = retryPlanCreator.createSubPlan(mBuildHelper);
-                mIncludeFilters.addAll(retryPlan.getIncludeFilters());
-                mExcludeFilters.addAll(retryPlan.getExcludeFilters());
-            } catch (ConfigurationException e) {
-                throw new RuntimeException ("Failed to create subplan for retry", e);
-            }
-        }
-        if (mSubPlan != null) {
-            try {
-                File subPlanFile = new File(mBuildHelper.getSubPlansDir(), mSubPlan + ".xml");
-                if (!subPlanFile.exists()) {
-                    throw new IllegalArgumentException(
-                            String.format("Could not retrieve subplan \"%s\"", mSubPlan));
-                }
-                InputStream subPlanInputStream = new FileInputStream(subPlanFile);
-                ISubPlan subPlan = new SubPlan();
-                subPlan.parse(subPlanInputStream);
+            RetryFilterHelper helper = new RetryFilterHelper(mBuildHelper, mRetrySessionId);
+            helper.validateBuildFingerprint(mDevice);
+            helper.setAllOptionsFrom(this);
+            helper.setCommandLineOptionsFor(this);
+            helper.populateRetryFilters();
+            mIncludeFilters = helper.getIncludeFilters();
+            mExcludeFilters = helper.getExcludeFilters();
+            helper.tearDown();
+        } else {
+            if (mSubPlan != null) {
+                ISubPlan subPlan = SubPlanHelper.getSubPlanByName(mBuildHelper, mSubPlan);
                 mIncludeFilters.addAll(subPlan.getIncludeFilters());
                 mExcludeFilters.addAll(subPlan.getExcludeFilters());
-            } catch (FileNotFoundException | ParseException e) {
-                throw new RuntimeException(
-                        String.format("Unable to find or parse subplan %s", mSubPlan), e);
             }
-        }
-        if (mModuleName != null) {
-            try {
-                List<String> modules = ModuleRepo.getModuleNamesMatching(
-                        mBuildHelper.getTestsDir(), mModuleName);
-                if (modules.size() == 0) {
-                    throw new IllegalArgumentException(
-                            String.format("No modules found matching %s", mModuleName));
-                } else if (modules.size() > 1) {
-                    throw new IllegalArgumentException(String.format(
-                            "Multiple modules found matching %s:\n%s\nWhich one did you mean?\n",
-                            mModuleName, ArrayUtil.join("\n", modules)));
-                } else {
-                    String module = modules.get(0);
-                    cleanFilters(mIncludeFilters, module);
-                    cleanFilters(mExcludeFilters, module);
-                    mIncludeFilters.add(new TestFilter(mAbiName, module, mTestName).toString());
+            if (mModuleName != null) {
+                try {
+                    List<String> modules = ModuleRepo.getModuleNamesMatching(
+                            mBuildHelper.getTestsDir(), mModuleName);
+                    if (modules.size() == 0) {
+                        throw new IllegalArgumentException(
+                                String.format("No modules found matching %s", mModuleName));
+                    } else if (modules.size() > 1) {
+                        throw new IllegalArgumentException(String.format("Multiple modules found"
+                                + " matching %s:\n%s\nWhich one did you mean?\n",
+                                mModuleName, ArrayUtil.join("\n", modules)));
+                    } else {
+                        String module = modules.get(0);
+                        cleanFilters(mIncludeFilters, module);
+                        cleanFilters(mExcludeFilters, module);
+                        mIncludeFilters.add(
+                                new TestFilter(mAbiName, module, mTestName).toString());
+                    }
+                } catch (FileNotFoundException e) {
+                    throw new RuntimeException(e);
                 }
-            } catch (FileNotFoundException e) {
-                throw new RuntimeException(e);
+            } else if (mTestName != null) {
+                throw new IllegalArgumentException(
+                        "Test name given without module name. Add --module <module-name>");
             }
-        } else if (mTestName != null) {
-            throw new IllegalArgumentException(
-                    "Test name given without module name. Add --module <module-name>");
         }
     }
 
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
new file mode 100644
index 0000000..9a68089
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.tradefed.util;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.result.SubPlanHelper;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
+import com.android.compatibility.common.tradefed.testtype.ISubPlan;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.LightInvocationResult;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.config.ArgsOptionParser;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.ArrayUtil;
+
+import java.io.FileNotFoundException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper for generating --include-filter and --exclude-filter values on compatibility retry.
+ */
+public class RetryFilterHelper {
+
+    @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+            description = "the subplan to run",
+            importance = Importance.IF_UNSET)
+    protected String mSubPlan;
+
+    @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+            description = "the include module filters to apply.",
+            importance = Importance.ALWAYS)
+    protected Set<String> mIncludeFilters = new HashSet<>();
+
+    @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+            description = "the exclude module filters to apply.",
+            importance = Importance.ALWAYS)
+    protected Set<String> mExcludeFilters = new HashSet<>();
+
+    @Option(name = CompatibilityTest.ABI_OPTION,
+            shortName = 'a',
+            description = "the abi to test.",
+            importance = Importance.IF_UNSET)
+    protected String mAbiName = null;
+
+    @Option(name = CompatibilityTest.MODULE_OPTION,
+            shortName = 'm',
+            description = "the test module to run.",
+            importance = Importance.IF_UNSET)
+    protected String mModuleName = null;
+
+    @Option(name = CompatibilityTest.TEST_OPTION,
+            shortName = CompatibilityTest.TEST_OPTION_SHORT_NAME,
+            description = "the test run.",
+            importance = Importance.IF_UNSET)
+    protected String mTestName = null;
+
+    @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
+            description = "used with " + CompatibilityTest.RETRY_OPTION + ", retry tests"
+            + " of a certain status. Possible values include \"failed\" and \"not_executed\".",
+            importance = Importance.IF_UNSET)
+    protected RetryType mRetryType = null;
+
+    /* Instance variables handy for retreiving the result to be retried */
+    private CompatibilityBuildHelper mBuild = null;
+    private int mSessionId;
+
+    /* Sets to be populated by retry logic and returned by getter methods */
+    private Set<String> mRetryIncludes;
+    private Set<String> mRetryExcludes;
+
+    /**
+     * Constructor for a {@link RetryFilterHelper}. Requires a CompatibilityBuildHelper for
+     * retrieving previous sessions and the ID of the session to retry.
+     */
+    public RetryFilterHelper(CompatibilityBuildHelper build, int sessionId) {
+        mBuild = build;
+        mSessionId = sessionId;
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the device build fingerprint doesn't match
+     * the fingerprint recorded in the previous session's result.
+     */
+    public void validateBuildFingerprint(ITestDevice device) throws DeviceNotAvailableException {
+        String oldBuildFingerprint = new LightInvocationResult(getResult()).getBuildFingerprint();
+        String currentBuildFingerprint = device.getProperty("ro.build.fingerprint");
+        if (!oldBuildFingerprint.equals(currentBuildFingerprint)) {
+            throw new IllegalArgumentException(String.format(
+                    "Device build fingerprint must match %s to retry session %d",
+                    oldBuildFingerprint, mSessionId));
+        }
+    }
+
+    /**
+     * Copy all applicable options from an input object to this instance of RetryFilterHelper.
+     */
+    public void setAllOptionsFrom(Object obj) {
+        clearOptions(); // Remove existing options first
+        OptionCopier.copyOptionsNoThrow(obj, this);
+    }
+
+    /**
+     * Set a single option on this instance of RetryFilterHelper
+     * @throws {@link ConfigurationException} if the option cannot be set.
+     */
+    public void setOption(String option, String value) throws ConfigurationException {
+        OptionSetter setter = new OptionSetter(this);
+        setter.setOptionValue(option, value);
+    }
+
+    /**
+     * Clear all option values of this RetryFilterHelper.
+     */
+    public void clearOptions() {
+        mSubPlan = null;
+        mIncludeFilters = new HashSet<>();
+        mExcludeFilters = new HashSet<>();
+        mModuleName = null;
+        mTestName = null;
+        mRetryType = null;
+        mAbiName = null;
+    }
+
+    /**
+     * Using command-line arguments from the previous session's result, set the input object's
+     * option values to the values applied in the previous session.
+     */
+    public void setCommandLineOptionsFor(Object obj) {
+        // only need light version to retrieve command-line args
+        IInvocationResult result = new LightInvocationResult(getResult());
+        String retryCommandLineArgs = result.getCommandLineArgs();
+        if (retryCommandLineArgs != null) {
+            try {
+                // parse the command-line string from the result file and set options
+                ArgsOptionParser parser = new ArgsOptionParser(obj);
+                parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, obj));
+            } catch (ConfigurationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve an instance of the result to retry using the instance variables referencing
+     * the build and the desired session ID. While it is faster to load this result once and
+     * store it as an instance variable, {@link IInvocationResult} objects are large, and
+     * memory is of greater concern.
+     */
+    public IInvocationResult getResult() {
+        IInvocationResult result = null;
+        try {
+            result = ResultHandler.findResult(mBuild.getResultsDir(), mSessionId);
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        if (result == null) {
+            throw new IllegalArgumentException(String.format(
+                    "Could not find session with id %d", mSessionId));
+        }
+        return result;
+    }
+
+    /**
+     * Populate mRetryIncludes and mRetryExcludes based on the options and the result set for
+     * this instance of RetryFilterHelper.
+     */
+    public void populateRetryFilters() {
+        mRetryIncludes = new HashSet<>(mIncludeFilters); // reset for each population
+        mRetryExcludes = new HashSet<>(mExcludeFilters); // reset for each population
+        if (RetryType.CUSTOM.equals(mRetryType)) {
+            Set<String> customIncludes = new HashSet<>(mIncludeFilters);
+            Set<String> customExcludes = new HashSet<>(mExcludeFilters);
+            if (mSubPlan != null) {
+                ISubPlan retrySubPlan = SubPlanHelper.getSubPlanByName(mBuild, mSubPlan);
+                customIncludes.addAll(retrySubPlan.getIncludeFilters());
+                customExcludes.addAll(retrySubPlan.getExcludeFilters());
+            }
+            // If includes were added, only use those includes. Also use excludes added directly
+            // or by subplan. Otherwise, default to normal retry.
+            if (!customIncludes.isEmpty()) {
+                mRetryIncludes.clear();
+                mRetryIncludes.addAll(customIncludes);
+                mRetryExcludes.addAll(customExcludes);
+                return;
+            }
+        }
+        // remove any extra filtering options
+        // TODO(aaronholden) remove non-plan includes (e.g. those in cts-vendor-interface)
+        // TODO(aaronholden) remove non-known-failure excludes
+        mModuleName = null;
+        mTestName = null;
+        mSubPlan = null;
+        populateFiltersBySubPlan();
+        populatePreviousSessionFilters();
+    }
+
+    /* Generation of filters based on previous sessions is implemented thoroughly in SubPlanHelper,
+     * and retry filter generation is just a subset of the use cases for the subplan retry logic.
+     * Use retry type to determine which result types SubPlanHelper targets. */
+    private void populateFiltersBySubPlan() {
+        SubPlanHelper retryPlanCreator = new SubPlanHelper();
+        retryPlanCreator.setResult(getResult());
+        if (RetryType.FAILED.equals(mRetryType)) {
+            // retry only failed tests
+            retryPlanCreator.addResultType(SubPlanHelper.FAILED);
+        } else if (RetryType.NOT_EXECUTED.equals(mRetryType)){
+            // retry only not executed tests
+            retryPlanCreator.addResultType(SubPlanHelper.NOT_EXECUTED);
+        } else {
+            // retry both failed and not executed tests
+            retryPlanCreator.addResultType(SubPlanHelper.FAILED);
+            retryPlanCreator.addResultType(SubPlanHelper.NOT_EXECUTED);
+        }
+        try {
+            ISubPlan retryPlan = retryPlanCreator.createSubPlan(mBuild);
+            mRetryIncludes.addAll(retryPlan.getIncludeFilters());
+            mRetryExcludes.addAll(retryPlan.getExcludeFilters());
+        } catch (ConfigurationException e) {
+            throw new RuntimeException ("Failed to create subplan for retry", e);
+        }
+    }
+
+    /* Retrieves the options set via command-line on the previous session, and generates/adds
+     * filters accordingly */
+    private void populatePreviousSessionFilters() {
+        // Temporarily store options from this instance in another instance
+        RetryFilterHelper tmpHelper = new RetryFilterHelper(mBuild, mSessionId);
+        tmpHelper.setAllOptionsFrom(this);
+        // Copy command-line args from previous session to this RetryFilterHelper's options
+        setCommandLineOptionsFor(this);
+
+        mRetryIncludes.addAll(mIncludeFilters);
+        mRetryExcludes.addAll(mExcludeFilters);
+        if (mSubPlan != null) {
+            ISubPlan retrySubPlan = SubPlanHelper.getSubPlanByName(mBuild, mSubPlan);
+            mRetryIncludes.addAll(retrySubPlan.getIncludeFilters());
+            mRetryExcludes.addAll(retrySubPlan.getExcludeFilters());
+        }
+        if (mModuleName != null) {
+            try {
+                List<String> modules = ModuleRepo.getModuleNamesMatching(
+                        mBuild.getTestsDir(), mModuleName);
+                if (modules.size() == 0) {
+                    throw new IllegalArgumentException(
+                            String.format("No modules found matching %s", mModuleName));
+                } else if (modules.size() > 1) {
+                    throw new IllegalArgumentException(String.format(
+                            "Multiple modules found matching %s:\n%s\nWhich one did you mean?\n",
+                            mModuleName, ArrayUtil.join("\n", modules)));
+                } else {
+                    String module = modules.get(0);
+                    cleanFilters(mRetryIncludes, module);
+                    cleanFilters(mRetryExcludes, module);
+                    mRetryIncludes.add(new TestFilter(mAbiName, module, mTestName).toString());
+                }
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        } else if (mTestName != null) {
+            throw new IllegalArgumentException(
+                "Test name given without module name. Add --module <module-name>");
+        }
+
+        // Copy options for current session back to this instance
+        setAllOptionsFrom(tmpHelper);
+    }
+
+    /* Helper method designed to remove filters in a list not applicable to the given module */
+    private static void cleanFilters(Set<String> filters, String module) {
+        Set<String> cleanedFilters = new HashSet<String>();
+        for (String filter : filters) {
+            if (module.equals(TestFilter.createFrom(filter).getName())) {
+                cleanedFilters.add(filter); // Module name matches, filter passes
+            }
+        }
+        filters.clear();
+        filters.addAll(cleanedFilters);
+    }
+
+    /** Retrieve include filters to be applied on retry */
+    public Set<String> getIncludeFilters() {
+        return new HashSet<>(mRetryIncludes);
+    }
+
+    /** Retrieve exclude filters to be applied on retry */
+    public Set<String> getExcludeFilters() {
+        return new HashSet<>(mRetryExcludes);
+    }
+
+    /** Clears retry filters and internal storage of options, except buildInfo and session ID */
+    public void tearDown() {
+        clearOptions();
+        mRetryIncludes = null;
+        mRetryExcludes = null;
+        // keep references to buildInfo and session ID
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryType.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryType.java
new file mode 100644
index 0000000..b5d3cd5
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.tradefed.util;
+
+/**
+ * Enum for --retry-type option value in compatibility testing.
+ */
+public enum RetryType {
+    FAILED,
+    NOT_EXECUTED,
+    CUSTOM;
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index bb96ed2..b6861db 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -25,7 +25,7 @@
 import com.android.compatibility.common.tradefed.result.ConsoleReporterTest;
 import com.android.compatibility.common.tradefed.result.MetadataReporterTest;
 import com.android.compatibility.common.tradefed.result.ResultReporterTest;
-import com.android.compatibility.common.tradefed.result.SubPlanCreatorTest;
+import com.android.compatibility.common.tradefed.result.SubPlanHelperTest;
 import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
 import com.android.compatibility.common.tradefed.targetprep.SettingsPreparerTest;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBaseTest;
@@ -36,6 +36,7 @@
 import com.android.compatibility.common.tradefed.testtype.SubPlanTest;
 import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
 import com.android.compatibility.common.tradefed.util.OptionHelperTest;
+import com.android.compatibility.common.tradefed.util.RetryFilterHelperTest;
 
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -67,7 +68,7 @@
     ConsoleReporterTest.class,
     MetadataReporterTest.class,
     ResultReporterTest.class,
-    SubPlanCreatorTest.class,
+    SubPlanHelperTest.class,
 
     // targetprep
     PropertyCheckTest.class,
@@ -84,6 +85,7 @@
     // util
     CollectorUtilTest.class,
     OptionHelperTest.class,
+    RetryFilterHelperTest.class,
 })
 public class UnitTests {
     // empty on purpose
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanCreatorTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
similarity index 96%
rename from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanCreatorTest.java
rename to common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
index e3240c1..a19eb30 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanCreatorTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
@@ -39,7 +39,7 @@
 import java.util.Arrays;
 import java.util.Set;
 
-public class SubPlanCreatorTest extends TestCase {
+public class SubPlanHelperTest extends TestCase {
 
     // Values used to populate mock results
     private static final String SUITE_NAME = "CTS";
@@ -75,7 +75,7 @@
     private static final String SP_RESULT_TYPE_NOT_EXECUTED = "not_executed";
 
     private CompatibilityBuildHelper mBuildHelper;
-    private SubPlanCreator mSubPlanCreator;
+    private SubPlanHelper mSubPlanHelper;
 
     private File mResultsDir = null;
     private File mResultDir = null;
@@ -89,8 +89,8 @@
         mBuildHelper = new SpctMockCompatibilityBuildHelper(new BuildInfo("0", "", ""));
         populateResults();
 
-        mSubPlanCreator = new SubPlanCreator();
-        ArgsOptionParser optionParser = new ArgsOptionParser(mSubPlanCreator);
+        mSubPlanHelper = new SubPlanHelper();
+        ArgsOptionParser optionParser = new ArgsOptionParser(mSubPlanHelper);
         optionParser.parse(Arrays.asList(
             "-n", SP_NAME,
             "--session", SP_SESSION,
@@ -109,7 +109,7 @@
     }
 
     public void testCreateSubPlan() throws Exception {
-        ISubPlan plan = mSubPlanCreator.createSubPlan(mBuildHelper);
+        ISubPlan plan = mSubPlanHelper.createSubPlan(mBuildHelper);
         Set<String> planIncludes = plan.getIncludeFilters();
         Set<String> planExcludes = plan.getExcludeFilters();
         TestFilter mf1 = new TestFilter(ABI, NAME_A, null);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/RetryFilterHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/RetryFilterHelperTest.java
new file mode 100644
index 0000000..05d35ec
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/RetryFilterHelperTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.tradefed.util;
+
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.tradefed.config.OptionSetter;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link RetryFilterHelper}
+ */
+public class RetryFilterHelperTest extends TestCase {
+
+    private static final String TEST_STRING = "abcd";
+    private static final RetryType TEST_RETRY_TYPE = RetryType.FAILED;
+
+    public void testSetAllOptionsFrom() throws Exception {
+        RetryFilterHelper helper = new RetryFilterHelper(null, 0);
+        RetryFilterHelper otherObj = new RetryFilterHelper(null, 0);
+        OptionSetter otherObjSetter = new OptionSetter(otherObj);
+        otherObjSetter.setOptionValue(CompatibilityTest.SUBPLAN_OPTION, TEST_STRING);
+        helper.setAllOptionsFrom(otherObj);
+        assertEquals(TEST_STRING, helper.mSubPlan);
+    }
+
+    public void testClearOptions() throws Exception {
+        RetryFilterHelper helper = new RetryFilterHelper(null, 0);
+        OptionSetter setter = new OptionSetter(helper);
+        setter.setOptionValue(CompatibilityTest.SUBPLAN_OPTION, TEST_STRING);
+        setter.setOptionValue(CompatibilityTest.INCLUDE_FILTER_OPTION, TEST_STRING);
+        setter.setOptionValue(CompatibilityTest.EXCLUDE_FILTER_OPTION, TEST_STRING);
+        setter.setOptionValue(CompatibilityTest.ABI_OPTION, TEST_STRING);
+        setter.setOptionValue(CompatibilityTest.MODULE_OPTION, TEST_STRING);
+        setter.setOptionValue(CompatibilityTest.TEST_OPTION, TEST_STRING);
+        setter.setOptionValue(CompatibilityTest.TEST_OPTION, TEST_RETRY_TYPE.name());
+        helper.clearOptions();
+        assertTrue(helper.mSubPlan == null);
+        assertTrue(helper.mIncludeFilters.isEmpty());
+        assertTrue(helper.mExcludeFilters.isEmpty());
+        assertTrue(helper.mAbiName == null);
+        assertTrue(helper.mModuleName == null);
+        assertTrue(helper.mTestName == null);
+        assertTrue(helper.mRetryType == null);
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/VendorInterfaceTest.java b/common/util/src/com/android/compatibility/common/util/VendorInterfaceTest.java
index 5a448f1..5bcaf0c 100644
--- a/common/util/src/com/android/compatibility/common/util/VendorInterfaceTest.java
+++ b/common/util/src/com/android/compatibility/common/util/VendorInterfaceTest.java
@@ -26,7 +26,8 @@
  * AOSP requirement.
  * <p>
  * Test classes and test cases marked with this annotation will be included in the
- * cts-vendor-interface plan by default.
+ * cts-vendor-interface plan
+ * by default.
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.TYPE})
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
index ab37090..55fc792 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.delegate;
 
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -86,22 +87,16 @@
     public void testCannotAccessApis() {
         assertFalse("DelegateApp should not be an app restrictions delegate",
                 amIAppRestrictionsDelegate());
-        try {
-            mDpm.setApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG, null);
-            fail("Expected SecurityException not thrown");
-        } catch (SecurityException expected) {
-            MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
-                    expected.getMessage());
-        }
-        try {
-            mDpm.getApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG);
-            fail("Expected SecurityException not thrown");
-        } catch (SecurityException expected) {
-            MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
-                    expected.getMessage());
-        }
+
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.setApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG, null);
+                });
+
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.getApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG);
+                });
     }
 
     public void testCanAccessApis() throws InterruptedException {
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
index 65ebd64..f706b85 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.delegate;
 
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
@@ -45,14 +46,11 @@
     public void testCannotAccessApis() {
         assertFalse("DelegateApp should not be a block uninstall delegate",
             amIBlockUninstallDelegate());
-        try {
-            mDpm.setUninstallBlocked(null, TEST_APP_PKG, true);
-            fail("Expected SecurityException not thrown");
-        } catch (SecurityException expected) {
-            MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-block-uninstall.",
-                    expected.getMessage());
-        }
+
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.setUninstallBlocked(null, TEST_APP_PKG, true);
+                });
     }
 
     public void testCanAccessApis() {
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
index d8d0ab3..933e257 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.delegate;
 
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -121,23 +122,16 @@
 
     public void testCannotAccessApis() {
         assertFalse(amICertInstallDelegate());
-        try {
-            mDpm.installCaCert(null, null);
-            fail("Expected SecurityException not thrown");
-        } catch (SecurityException expected) {
-            MoreAsserts.assertContainsRegex(
-                    "Neither user \\d+ nor current process has "
-                    + "android.permission.MANAGE_CA_CERTIFICATES",
-                    expected.getMessage());
-        }
-        try {
-            mDpm.removeKeyPair(null, "alias");
-            fail("Expected SecurityException not thrown");
-        } catch (SecurityException expected) {
-            MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-cert-install.",
-                    expected.getMessage());
-        }
+
+        assertExpectException(SecurityException.class,
+                "Neither user \\d+ nor current process has", () -> {
+                    mDpm.installCaCert(null, null);
+                });
+
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.removeKeyPair(null, "alias");
+                });
     }
 
     public void testCanAccessApis() throws Exception {
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
new file mode 100644
index 0000000..b162f86
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.delegate;
+
+import android.test.MoreAsserts;
+import junit.framework.Assert;
+
+/**
+ * Utils class for delegation tests.
+ */
+public class DelegateTestUtils {
+
+    @FunctionalInterface
+    public interface ExceptionRunnable {
+        void run() throws Exception;
+    }
+
+    public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
+            String expectedExceptionMessageRegex, ExceptionRunnable r) {
+        try {
+            r.run();
+        } catch (Throwable e) {
+            Assert.assertTrue("Expected " + expectedExceptionType.getName() + " but caught " + e,
+                expectedExceptionType.isAssignableFrom(e.getClass()));
+            if (expectedExceptionMessageRegex != null) {
+                MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
+            }
+            return; // Pass
+        }
+        Assert.fail("Expected " + expectedExceptionType.getName() + " was not thrown");
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
new file mode 100644
index 0000000..246f936
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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.delegate;
+
+import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.test.InstrumentationTestCase;
+
+import java.util.List;
+
+/**
+ * Test that an app given the {@link DevicePolicyManager#DELEGATION_PERMISSION_GRANT} scope via
+ * {@link DevicePolicyManager#setDelegatedScopes} can grant permissions and check permission grant
+ * state.
+ */
+public class EnableSystemAppDelegateTest extends InstrumentationTestCase {
+
+    private static final String TEST_APP_PKG = "com.android.cts.launcherapps.simpleapp";
+
+    private DevicePolicyManager mDpm;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDpm = getInstrumentation().getContext().getSystemService(DevicePolicyManager.class);
+    }
+
+    public void testCannotAccessApis() {
+        assertFalse("DelegateApp should not be an enable system app delegate",
+            amIEnableSystemAppDelegate());
+
+        // Exercise enableSystemApp(String).
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.enableSystemApp(null, TEST_APP_PKG);
+                });
+
+        // Exercise enableSystemApp(Intent).
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.enableSystemApp(null, new Intent().setPackage(TEST_APP_PKG));
+                });
+    }
+
+    public void testCanAccessApis() {
+        assertTrue("DelegateApp is not an enable system app delegate",
+            amIEnableSystemAppDelegate());
+
+        // Exercise enableSystemApp(String).
+        assertExpectException(IllegalArgumentException.class,
+                "Only system apps can be enabled this way", () -> {
+                    mDpm.enableSystemApp(null, TEST_APP_PKG);
+                });
+
+        // Exercise enableSystemApp(Intent).
+        mDpm.enableSystemApp(null, new Intent());
+    }
+
+    private boolean amIEnableSystemAppDelegate() {
+        final String packageName = getInstrumentation().getContext().getPackageName();
+        final List<String> scopes = mDpm.getDelegatedScopes(null, packageName);
+        return scopes.contains(DELEGATION_ENABLE_SYSTEM_APP);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
index 24bee4f..c403ffb 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
@@ -18,6 +18,8 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
 
 import android.app.admin.DevicePolicyManager;
@@ -47,7 +49,10 @@
     private static final String EXPECTED_DELEGATION_SCOPES[] = {
         DELEGATION_APP_RESTRICTIONS,
         DELEGATION_BLOCK_UNINSTALL,
-        DELEGATION_CERT_INSTALL
+        DELEGATION_CERT_INSTALL,
+        DELEGATION_PERMISSION_GRANT,
+        DELEGATION_PACKAGE_ACCESS,
+        DELEGATION_ENABLE_SYSTEM_APP
     };
 
     @Override
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
new file mode 100644
index 0000000..86f2639
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.delegate;
+
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.test.InstrumentationTestCase;
+import android.test.MoreAsserts;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test that an app given the {@link DevicePolicyManager#DELEGATION_PACKAGE_ACCESS} scope via
+ * {@link DevicePolicyManager#setDelegatedScopes} can manage package hide and suspend status.
+ */
+public class PackageAccessDelegateTest extends InstrumentationTestCase {
+
+    private static final String TEST_APP_PKG = "com.android.cts.launcherapps.simpleapp";
+
+    private DevicePolicyManager mDpm;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        Context context = getInstrumentation().getContext();
+        mDpm = context.getSystemService(DevicePolicyManager.class);
+    }
+
+    public void testCannotAccessApis() throws NameNotFoundException {
+        assertFalse("DelegateApp should not be a package access delegate",
+            amIPackageAccessDelegate());
+
+        // Exercise isApplicationHidden.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.isApplicationHidden(null, TEST_APP_PKG);
+                });
+
+        // Exercise setApplicationHidden.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.setApplicationHidden(null, TEST_APP_PKG, true /* hide */);
+                });
+
+        // Exercise isPackageSuspended.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.isPackageSuspended(null, TEST_APP_PKG);
+                });
+
+        // Exercise setPackagesSuspended.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.setPackagesSuspended(null, new String[] {TEST_APP_PKG}, true /* suspend */);
+                });
+    }
+
+    public void testCanAccessApis() throws NameNotFoundException {
+        assertTrue("DelegateApp is not a package access delegate", amIPackageAccessDelegate());
+
+        // Exercise isApplicationHidden.
+        assertFalse("Package should not be hidden", mDpm.isApplicationHidden(null, TEST_APP_PKG));
+
+        // Exercise setApplicationHidden.
+        assertTrue("Package not hidden successfully",
+                mDpm.setApplicationHidden(null, TEST_APP_PKG, true /* hide */));
+        assertTrue("Package should be hidden", mDpm.isApplicationHidden(null, TEST_APP_PKG));
+
+        // Exercise isPackageSuspended.
+        assertFalse("Package should not be suspended", mDpm.isPackageSuspended(null, TEST_APP_PKG));
+
+        // Exercise setPackagesSuspended.
+        String[] suspended = mDpm.setPackagesSuspended(null, new String[] {TEST_APP_PKG},
+                true /* suspend */);
+        assertTrue("Package not suspended successfully", suspended.length == 0);
+        assertTrue("Package should be suspended", mDpm.isPackageSuspended(null, TEST_APP_PKG));
+    }
+
+    private boolean amIPackageAccessDelegate() {
+        final String packageName = getInstrumentation().getContext().getPackageName();
+        final List<String> scopes = mDpm.getDelegatedScopes(null, packageName);
+        return scopes.contains(DELEGATION_PACKAGE_ACCESS);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
new file mode 100644
index 0000000..81b74a2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.delegate;
+
+import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+import android.test.MoreAsserts;
+
+import java.util.List;
+
+/**
+ * Test that an app given the {@link DevicePolicyManager#DELEGATION_PERMISSION_GRANT} scope via
+ * {@link DevicePolicyManager#setDelegatedScopes} can grant permissions and check permission grant
+ * state.
+ */
+public class PermissionGrantDelegateTest extends InstrumentationTestCase {
+
+    private static final String TEST_APP_PKG = "com.android.cts.launcherapps.simpleapp";
+    private static final String TEST_PERMISSION = "android.permission.READ_CONTACTS";
+
+    private DevicePolicyManager mDpm;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        Context context = getInstrumentation().getContext();
+        mDpm = context.getSystemService(DevicePolicyManager.class);
+    }
+
+    public void testCannotAccessApis() {
+        assertFalse("DelegateApp should not be a permisssion grant delegate",
+            amIPermissionGrantDelegate());
+
+        // Exercise setPermissionPolicy.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.setPermissionPolicy(null, PERMISSION_POLICY_AUTO_GRANT);
+                });
+        assertFalse("Permission policy should not have been set",
+                PERMISSION_POLICY_AUTO_GRANT == mDpm.getPermissionPolicy(null));
+
+        // Exercise setPermissionGrantState.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.setPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION,
+                            PERMISSION_GRANT_STATE_GRANTED);
+                });
+
+        // Exercise getPermissionGrantState.
+        assertExpectException(SecurityException.class,
+                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                    mDpm.getPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION);
+                });
+    }
+
+    public void testCanAccessApis() {
+        assertTrue("DelegateApp is not a permission grant delegate",
+            amIPermissionGrantDelegate());
+
+        // Exercise setPermissionPolicy.
+        mDpm.setPermissionPolicy(null, PERMISSION_POLICY_AUTO_DENY);
+        assertTrue("Permission policy was not set",
+                PERMISSION_POLICY_AUTO_DENY == mDpm.getPermissionPolicy(null));
+
+        // Exercise setPermissionGrantState.
+        assertTrue("Permission grant state was not set successfully",
+                mDpm.setPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION,
+                    PERMISSION_GRANT_STATE_DENIED));
+
+        // Exercise getPermissionGrantState.
+        assertEquals("Permission grant state is not denied", PERMISSION_GRANT_STATE_DENIED,
+                mDpm.getPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION));
+    }
+
+    private boolean amIPermissionGrantDelegate() {
+        final String packageName = getInstrumentation().getContext().getPackageName();
+        final List<String> scopes = mDpm.getDelegatedScopes(null, packageName);
+        return scopes.contains(DELEGATION_PERMISSION_GRANT);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index da902d3..be4d31e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -67,6 +67,9 @@
     private static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
     private static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
     private static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+    private static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
+    private static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+    private static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
 
     private static final String TEST_APP_APK = "CtsSimpleApp.apk";
     private static final String TEST_APP_PKG = "com.android.cts.launcherapps.simpleapp";
@@ -183,7 +186,10 @@
         final String delegationTests[] = {
             ".AppRestrictionsDelegateTest",
             ".CertInstallDelegateTest",
-            ".BlockUninstallDelegateTest"
+            ".BlockUninstallDelegateTest",
+            ".PermissionGrantDelegateTest",
+            ".PackageAccessDelegateTest",
+            ".EnableSystemAppDelegateTest"
         };
 
         // Set a device lockscreen password (precondition for installing private key pairs).
@@ -202,7 +208,10 @@
             setDelegatedScopes(DELEGATE_APP_PKG, Arrays.asList(
                     DELEGATION_APP_RESTRICTIONS,
                     DELEGATION_CERT_INSTALL,
-                    DELEGATION_BLOCK_UNINSTALL));
+                    DELEGATION_BLOCK_UNINSTALL,
+                    DELEGATION_PERMISSION_GRANT,
+                    DELEGATION_PACKAGE_ACCESS,
+                    DELEGATION_ENABLE_SYSTEM_APP));
             runDeviceTestsAsUser(DELEGATE_APP_PKG, ".GeneralDelegateTest", mUserId);
             executeDelegationTests(delegationTests, true /* positive result */);
 
@@ -214,7 +223,7 @@
             executeDeviceTestClass(".DelegationTest");
 
         } finally {
-            // Clear lockscreen password previously set for installing private key pairs (DO only).
+            // Clear lockscreen password previously set for installing private key pairs.
             changeUserCredential(null, "1234", mPrimaryUserId);
             // Remove any remaining delegations.
             setDelegatedScopes(DELEGATE_APP_PKG, null);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index 23650a6..b47cb5b 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -18,6 +18,7 @@
 import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Debug;
+import android.os.Message;
 import android.os.Parcelable;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -25,7 +26,10 @@
 import android.text.style.ClickableSpan;
 import android.text.style.URLSpan;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityRequestPreparer;
 import android.widget.EditText;
 import android.widget.TextView;
 
@@ -34,10 +38,21 @@
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 /**
  * Test cases for actions taken on text views.
@@ -193,25 +208,10 @@
                 textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
         assertNull("Text locations should not be populated by default",
                 text.getExtras().get(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
-        Bundle getTextArgs = new Bundle();
-        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
-        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
-                text.getText().length());
+        final Bundle getTextArgs = getTextLocationArguments(text);
         assertTrue("Refresh failed", text.refreshWithExtraData(
                 AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
-        final Parcelable[] parcelables = text.getExtras()
-                .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
-        final RectF[] locations = Arrays.copyOf(parcelables, parcelables.length, RectF[].class);
-        assertEquals(text.getText().length(), locations.length);
-        // The text should all be on one line, running left to right
-        for (int i = 0; i < locations.length; i++) {
-            assertEquals(locations[0].top, locations[i].top);
-            assertEquals(locations[0].bottom, locations[i].bottom);
-            assertTrue(locations[i].right > locations[i].left);
-            if (i > 0) {
-                assertTrue(locations[i].left > locations[i-1].left);
-            }
-        }
+        assertNodeContainsTextLocationInfoOnOneLineLTR(text);
     }
 
     public void testTextLocations_textOutsideOfViewBounds_locationsShouldBeNull() {
@@ -223,12 +223,9 @@
         List<String> textAvailableExtraData = text.getAvailableExtraData();
         assertTrue("Text view should offer text location to accessibility",
                 textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
-        Bundle getTextArgs = new Bundle();
-        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
-        getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
-                text.getText().length());
+        final Bundle getTextArgs = getTextLocationArguments(text);
         assertTrue("Refresh failed", text.refreshWithExtraData(
-                AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+                EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
         Parcelable[] parcelables = text.getExtras()
                 .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
         final RectF[] locationsBeforeScroll = Arrays.copyOf(
@@ -255,7 +252,7 @@
         getInstrumentation().runOnMainSync(() -> editText.scrollTo(0, (int) oneLineDownY + 1));
 
         assertTrue("Refresh failed", text.refreshWithExtraData(
-                AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+                EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
         parcelables = text.getExtras()
                 .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
         final RectF[] locationsAfterScroll = Arrays.copyOf(
@@ -266,6 +263,132 @@
         assertNotNull(locationsAfterScroll[firstNullRectIndex]);
     }
 
+    public void testTextLocations_withRequestPreparer_shouldHoldOffUntilReady() {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+
+        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final List<String> textAvailableExtraData = text.getAvailableExtraData();
+        final Bundle getTextArgs = getTextLocationArguments(text);
+
+        // Register a request preparer that will capture the message indicating that preparation
+        // is complete
+        final AtomicReference<Message> messageRefForPrepare = new AtomicReference<>(null);
+        // Use mockito's asynchronous signaling
+        Runnable mockRunnableForPrepare = mock(Runnable.class);
+
+        AccessibilityManager a11yManager =
+                getActivity().getSystemService(AccessibilityManager.class);
+        AccessibilityRequestPreparer requestPreparer = new AccessibilityRequestPreparer(
+                textView, AccessibilityRequestPreparer.REQUEST_TYPE_EXTRA_DATA) {
+            @Override
+            public void onPrepareExtraData(int virtualViewId,
+                    String extraDataKey, Bundle args, Message preparationFinishedMessage) {
+                assertEquals(AccessibilityNodeProvider.HOST_VIEW_ID, virtualViewId);
+                assertEquals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, extraDataKey);
+                assertEquals(0, args.getInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX));
+                assertEquals(text.getText().length(),
+                        args.getInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH));
+                messageRefForPrepare.set(preparationFinishedMessage);
+                mockRunnableForPrepare.run();
+            }
+        };
+        a11yManager.addAccessibilityRequestPreparer(requestPreparer);
+        verify(mockRunnableForPrepare, times(0)).run();
+
+        // Make the extra data request in another thread
+        Runnable mockRunnableForData = mock(Runnable.class);
+        new Thread() {
+            @Override
+            public void run() {
+                assertTrue("Refresh failed", text.refreshWithExtraData(
+                        EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+                mockRunnableForData.run();
+            }
+        }.start();
+
+        // The extra data request should trigger the request preparer
+        verify(mockRunnableForPrepare, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        // Verify that the request for extra data didn't return. This is a bit racy, as we may still
+        // not catch it if it does return prematurely, but it does provide some protection.
+        getInstrumentation().waitForIdleSync();
+        verify(mockRunnableForData, times(0)).run();
+
+        // Declare preparation for the request complete, and verify that it runs to completion
+        messageRefForPrepare.get().sendToTarget();
+        verify(mockRunnableForData, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        assertNodeContainsTextLocationInfoOnOneLineLTR(text);
+        a11yManager.removeAccessibilityRequestPreparer(requestPreparer);
+    }
+
+    public void testTextLocations_withUnresponsiveRequestPreparer_shouldTimeout() {
+        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+
+        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final List<String> textAvailableExtraData = text.getAvailableExtraData();
+        final Bundle getTextArgs = getTextLocationArguments(text);
+
+        // Use mockito's asynchronous signaling
+        Runnable mockRunnableForPrepare = mock(Runnable.class);
+
+        AccessibilityManager a11yManager =
+                getActivity().getSystemService(AccessibilityManager.class);
+        AccessibilityRequestPreparer requestPreparer = new AccessibilityRequestPreparer(
+                textView, AccessibilityRequestPreparer.REQUEST_TYPE_EXTRA_DATA) {
+            @Override
+            public void onPrepareExtraData(int virtualViewId,
+                    String extraDataKey, Bundle args, Message preparationFinishedMessage) {
+                mockRunnableForPrepare.run();
+            }
+        };
+        a11yManager.addAccessibilityRequestPreparer(requestPreparer);
+        verify(mockRunnableForPrepare, times(0)).run();
+
+        // Make the extra data request in another thread
+        Runnable mockRunnableForData = mock(Runnable.class);
+        new Thread() {
+            @Override
+            public void run() {
+                assertTrue("Refresh failed", text.refreshWithExtraData(
+                        EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+                mockRunnableForData.run();
+            }
+        }.start();
+
+        // The extra data request should trigger the request preparer
+        verify(mockRunnableForPrepare, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+
+        // Declare preparation for the request complete, and verify that it runs to completion
+        verify(mockRunnableForData, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        a11yManager.removeAccessibilityRequestPreparer(requestPreparer);
+    }
+
+    private Bundle getTextLocationArguments(AccessibilityNodeInfo info) {
+        Bundle args = new Bundle();
+        args.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
+        args.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, info.getText().length());
+        return args;
+    }
+
+    private void assertNodeContainsTextLocationInfoOnOneLineLTR(AccessibilityNodeInfo info) {
+        final Parcelable[] parcelables = info.getExtras()
+                .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+        final RectF[] locations = Arrays.copyOf(parcelables, parcelables.length, RectF[].class);
+        assertEquals(info.getText().length(), locations.length);
+        // The text should all be on one line, running left to right
+        for (int i = 0; i < locations.length; i++) {
+            assertEquals(locations[0].top, locations[i].top);
+            assertEquals(locations[0].bottom, locations[i].bottom);
+            assertTrue(locations[i].right > locations[i].left);
+            if (i > 0) {
+                assertTrue(locations[i].left > locations[i-1].left);
+            }
+        }
+    }
+
     private void onClickCallback() {
         synchronized (mClickableSpanCallbackLock) {
             mClickableSpanCalled.set(true);
diff --git a/tests/app/src/android/app/cts/TimePickerDialogTest.java b/tests/app/src/android/app/cts/TimePickerDialogTest.java
index 3da25ab..47a666c 100644
--- a/tests/app/src/android/app/cts/TimePickerDialogTest.java
+++ b/tests/app/src/android/app/cts/TimePickerDialogTest.java
@@ -16,21 +16,31 @@
 
 package android.app.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlertDialog;
 import android.app.TimePickerDialog;
 import android.app.TimePickerDialog.OnTimeSetListener;
-import android.app.stubs.DialogStubActivity;
+import android.app.stubs.R;
 import android.content.Context;
 import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
 import android.widget.TimePicker;
 
-import android.app.stubs.R;
-
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 /**
  * Test {@link TimePickerDialog}.
  */
-public class TimePickerDialogTest extends ActivityInstrumentationTestCase2<DialogStubActivity> {
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TimePickerDialogTest {
     private static final String HOUR = "hour";
     private static final String MINUTE = "minute";
     private static final String IS_24_HOUR = "is24hour";
@@ -44,17 +54,10 @@
     private OnTimeSetListener mOnTimeSetListener;
 
     private Context mContext;
-    private DialogStubActivity mActivity;
 
-    public TimePickerDialogTest() {
-        super("android.app.stubs", DialogStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContext = getInstrumentation().getContext();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mOnTimeSetListener = new OnTimeSetListener(){
             public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                 mCallbackHour = hourOfDay;
@@ -64,9 +67,31 @@
     }
 
     @UiThreadTest
+    @Test
+    public void testConstructor() {
+        new TimePickerDialog(mContext, null, 1, 1, false);
+
+        new TimePickerDialog(mContext, null, 1, 1, true);
+
+        new TimePickerDialog(mContext, AlertDialog.THEME_TRADITIONAL, null, 1, 1, false);
+
+        new TimePickerDialog(mContext, AlertDialog.THEME_HOLO_DARK, null, 1, 1, false);
+
+        new TimePickerDialog(mContext,
+                android.R.style.Theme_Material_Dialog_Alert, null, 1, 1, false);
+    }
+
+    @UiThreadTest
+    @Test(expected = NullPointerException.class)
+    public void testConstructorWithNullContext() {
+        new TimePickerDialog(null, null, 0, 0, false);
+    }
+
+    @UiThreadTest
+    @Test
     public void testSaveInstanceState() {
         TimePickerDialog tD = new TimePickerDialog(
-            mContext, mOnTimeSetListener, TARGET_HOUR, TARGET_MINUTE, true);
+                mContext, mOnTimeSetListener, TARGET_HOUR, TARGET_MINUTE, true);
 
         Bundle b = tD.onSaveInstanceState();
 
@@ -87,6 +112,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testOnClick() {
         TimePickerDialog timePickerDialog = buildDialog();
         timePickerDialog.onClick(null, TimePickerDialog.BUTTON_POSITIVE);
@@ -95,21 +121,16 @@
         assertEquals(TARGET_MINUTE, mCallbackMinute);
     }
 
+    @UiThreadTest
+    @Test
     public void testOnTimeChanged() throws Throwable {
         final int minute = 34;
-        startDialogActivity(DialogStubActivity.TEST_TIMEPICKERDIALOG);
-        final TimePickerDialog d = (TimePickerDialog) mActivity.getDialog();
-
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                d.onTimeChanged(null, TARGET_HOUR, minute);
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-
+        final TimePickerDialog d = buildDialog();
+        d.onTimeChanged(null, TARGET_HOUR, minute);
     }
 
     @UiThreadTest
+    @Test
     public void testUpdateTime() {
         TimePickerDialog timePickerDialog = buildDialog();
         int minute = 18;
@@ -123,6 +144,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testOnRestoreInstanceState() {
         int minute = 27;
         Bundle b1 = new Bundle();
@@ -141,10 +163,6 @@
         assertFalse(b2.getBoolean(IS_24_HOUR));
     }
 
-    private void startDialogActivity(int dialogNumber) {
-        mActivity = DialogStubActivity.startDialogActivity(this, dialogNumber);
-    }
-
     private TimePickerDialog buildDialog() {
         return new TimePickerDialog(
                 mContext, mOnTimeSetListener, TARGET_HOUR, TARGET_MINUTE, true);
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index b88a80f..1cf0f50 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -66,7 +66,7 @@
     private static final String TAG = "PerformanceTest";
     private static final String REPORT_LOG_NAME = "CtsCameraTestCases";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-    private static final int NUM_TEST_LOOPS = 5;
+    private static final int NUM_TEST_LOOPS = 10;
     private static final int NUM_MAX_IMAGES = 4;
     private static final int NUM_RESULTS_WAIT = 30;
     private static final int[] REPROCESS_FORMATS = {ImageFormat.YUV_420_888, ImageFormat.PRIVATE};
@@ -205,6 +205,39 @@
             }
             counter++;
             mReportLog.submit(getInstrumentation());
+
+            if (VERBOSE) {
+                Log.v(TAG, "Camera " + id + " device open times(ms): "
+                        + Arrays.toString(cameraOpenTimes)
+                        + ". Average(ms): " + Stat.getAverage(cameraOpenTimes)
+                        + ". Min(ms): " + Stat.getMin(cameraOpenTimes)
+                        + ". Max(ms): " + Stat.getMax(cameraOpenTimes));
+                Log.v(TAG, "Camera " + id + " configure stream times(ms): "
+                        + Arrays.toString(configureStreamTimes)
+                        + ". Average(ms): " + Stat.getAverage(configureStreamTimes)
+                        + ". Min(ms): " + Stat.getMin(configureStreamTimes)
+                        + ". Max(ms): " + Stat.getMax(configureStreamTimes));
+                Log.v(TAG, "Camera " + id + " start preview times(ms): "
+                        + Arrays.toString(startPreviewTimes)
+                        + ". Average(ms): " + Stat.getAverage(startPreviewTimes)
+                        + ". Min(ms): " + Stat.getMin(startPreviewTimes)
+                        + ". Max(ms): " + Stat.getMax(startPreviewTimes));
+                Log.v(TAG, "Camera " + id + " stop preview times(ms): "
+                        + Arrays.toString(stopPreviewTimes)
+                        + ". Average(ms): " + Stat.getAverage(stopPreviewTimes)
+                        + ". nMin(ms): " + Stat.getMin(stopPreviewTimes)
+                        + ". nMax(ms): " + Stat.getMax(stopPreviewTimes));
+                Log.v(TAG, "Camera " + id + " device close times(ms): "
+                        + Arrays.toString(cameraCloseTimes)
+                        + ". Average(ms): " + Stat.getAverage(cameraCloseTimes)
+                        + ". Min(ms): " + Stat.getMin(cameraCloseTimes)
+                        + ". Max(ms): " + Stat.getMax(cameraCloseTimes));
+                Log.v(TAG, "Camera " + id + " camera launch times(ms): "
+                        + Arrays.toString(cameraLaunchTimes)
+                        + ". Average(ms): " + Stat.getAverage(cameraLaunchTimes)
+                        + ". Min(ms): " + Stat.getMin(cameraLaunchTimes)
+                        + ". Max(ms): " + Stat.getMax(cameraLaunchTimes));
+            }
         }
         if (mCameraIds.length != 0) {
             String streamName = "test_camera_launch_average";
@@ -1085,7 +1118,7 @@
 
             if (imageAvailable.block(timeout)) {
                 imageAvailable.close();
-                imageReceived = false;
+                imageReceived = true;
             } else {
                 throw new TimeoutRuntimeException("Unable to get the first image after "
                         + CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS + "ms");
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DefaultFocusHighlightTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DefaultFocusHighlightTest.java
new file mode 100644
index 0000000..4849e71
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DefaultFocusHighlightTest.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2017 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.graphics.drawable.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.NinePatch;
+import android.graphics.Picture;
+import android.graphics.cts.R;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.graphics.drawable.DrawableContainer.DrawableContainerState;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LevelListDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.graphics.drawable.PictureDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.StateSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class DefaultFocusHighlightTest {
+
+    // The target context.
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    private static final int A_COLOR = 0x920424;
+    private static final int[] NO_STATE_FOCUSED = new int[] { android.R.attr.state_enabled };
+    private static final int[] ONLY_STATE_FOCUSED = new int[] { android.R.attr.state_focused };
+    private static final int[] STATE_FOCUSED_WITH_POS =
+            new int[] { android.R.attr.state_focused, android.R.attr.state_hovered };
+    private static final int[] STATE_FOCUSED_WITH_NEG =
+            new int[] { android.R.attr.state_focused,  -android.R.attr.state_hovered };
+    private static final int[] STATE_FOCUSED_WITH_ENABLED =
+            new int[] { android.R.attr.state_focused, android.R.attr.state_enabled };
+
+    final static int[] FOCUSED_STATE =
+            new int[] { android.R.attr.state_focused, android.R.attr.state_enabled };
+
+    @UiThreadTest
+    @Test
+    public void testStateListDrawable() {
+        Drawable d;
+        // Empty state spec
+        d = DrawableFactory.createStateListDrawable(
+                new int[][] {}
+            );
+        d.setState(FOCUSED_STATE);
+        assertFalse(d.hasFocusStateSpecified());
+
+        // Wild card
+        d = DrawableFactory.createStateListDrawable(
+                new int[][] { StateSet.WILD_CARD }
+            );
+        d.setState(FOCUSED_STATE);
+        assertFalse(d.hasFocusStateSpecified());
+
+        // No state spec of state_focused=true
+        d = DrawableFactory.createStateListDrawable(
+                new int[][] { NO_STATE_FOCUSED }
+            );
+        d.setState(FOCUSED_STATE);
+        assertFalse(d.hasFocusStateSpecified());
+
+        // One state spec of only state_focused=true
+        d = DrawableFactory.createStateListDrawable(
+                new int[][] { ONLY_STATE_FOCUSED }
+            );
+        d.setState(FOCUSED_STATE);
+        assertTrue(d.hasFocusStateSpecified());
+
+        // One state spec of state_focused=true and something=true, but no state spec of
+        // state_focused=true and something=false (something is not enabled)
+        d = DrawableFactory.createStateListDrawable(
+                new int[][] { STATE_FOCUSED_WITH_POS }
+            );
+        d.setState(FOCUSED_STATE);
+        assertTrue(d.hasFocusStateSpecified());
+
+        // One state spec of state_focused=true and something=true, and one spec of
+        // state_focused=true and something=false (something is not enabled)
+        d = DrawableFactory.createStateListDrawable(
+            new int[][] { STATE_FOCUSED_WITH_POS, STATE_FOCUSED_WITH_NEG }
+        );
+        d.setState(FOCUSED_STATE);
+        assertTrue(d.hasFocusStateSpecified());
+
+        // One state spec of state_focused=true and enabled=true
+        d = DrawableFactory.createStateListDrawable(
+            new int[][] { STATE_FOCUSED_WITH_ENABLED }
+        );
+        d.setState(FOCUSED_STATE);
+        assertTrue(d.hasFocusStateSpecified());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testRippleDrawable() {
+        Drawable d = DrawableFactory.createRippleDrawable();
+        d.setState(FOCUSED_STATE);
+        assertTrue(d.hasFocusStateSpecified());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPictureDrawable() {
+        Drawable d = DrawableFactory.createPictureDrawable(null);
+        d.setState(FOCUSED_STATE);
+        assertFalse(d.hasFocusStateSpecified());
+
+        d = DrawableFactory.createPictureDrawable(new Picture());
+        d.setState(FOCUSED_STATE);
+        assertFalse(d.hasFocusStateSpecified());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testColorStateListHandledDrawable() {
+        final Drawable[] drawables = new Drawable[] {
+            DrawableFactory.createShapeDrawable(),
+            DrawableFactory.createPaintDrawable(),
+            DrawableFactory.createBitmapDrawable(mContext),
+            DrawableFactory.createColorDrawable(),
+            DrawableFactory.createGradientDrawable(),
+            DrawableFactory.createNinePatchDrawable(mContext),
+        };
+        final ColorStateList[] stateLists = new ColorStateList[] {
+            // Empty state spec
+            new ColorStateList(
+                new int[][] {  },
+                new int[] {  }),
+            // Wild card
+            new ColorStateList(
+                new int[][] { StateSet.WILD_CARD },
+                new int[] { A_COLOR }),
+            // No state spec of state_focused=true
+            new ColorStateList(
+                new int[][] { NO_STATE_FOCUSED },
+                new int[] { A_COLOR }),
+            // One state spec of only state_focused=true
+            new ColorStateList(
+                new int[][] { ONLY_STATE_FOCUSED },
+                new int[] { A_COLOR }),
+            // One state spec of state_focused=true and something=true,
+            // but no state spec of state_focused=true and something=false
+            new ColorStateList(
+                new int[][] { STATE_FOCUSED_WITH_POS },
+                new int[] { A_COLOR }),
+            // One state spec of state_focused=true and something=true,
+            // and one spec of state_focused=true and something=false
+            new ColorStateList(
+                new int[][] { STATE_FOCUSED_WITH_POS, STATE_FOCUSED_WITH_NEG },
+                new int[] { A_COLOR, A_COLOR }),
+        };
+        final boolean[] expectedResults = new boolean[] {
+            // Empty state spec
+            false,
+            // Wild card
+            false,
+            // No state spec of state_focused=true
+            false,
+            // One state spec of only state_focused=true
+            true,
+            // One state spec of state_focused=true and something=true,
+            // but no state spec of state_focused=true and something=false
+            true,
+            // One state spec of state_focused=true and something=true,
+            // and one spec of state_focused=true and something=false
+            true
+        };
+        assertEquals(stateLists.length, expectedResults.length);
+        for (Drawable drawable : drawables) {
+            // No ColorStateList set
+            String drawableName = drawable.getClass().toString();
+            String errorMsg = "[" + drawableName + "] Testing no ColorStateList failed.";
+            drawable.setState(FOCUSED_STATE);
+            assertFalse(errorMsg, drawable.hasFocusStateSpecified());
+            // With ColorStateList set
+            for (int i = 0; i < stateLists.length; i++) {
+                ColorStateList stateList = stateLists[i];
+                boolean expectedResult = expectedResults[i];
+                drawable.setTintList(stateList);
+                errorMsg = "[" + drawableName + "] Testing ColorStateList No." + i + " failed.";
+
+                drawable.setState(FOCUSED_STATE);
+                if (expectedResult) {
+                    assertTrue(errorMsg, drawable.hasFocusStateSpecified());
+                } else {
+                    assertFalse(errorMsg, drawable.hasFocusStateSpecified());
+                }
+            }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDrawableContainer() {
+        MockDrawableContainer container;
+        DrawableContainerState containerState;
+
+        // Empty
+        container = new MockDrawableContainer();
+        containerState = (DrawableContainerState) new LevelListDrawable().getConstantState();
+        assertNotNull(containerState);
+        container.setConstantState(containerState);
+        container.setState(FOCUSED_STATE);
+        assertFalse(container.hasFocusStateSpecified());
+
+        // No drawable of state_focused=true
+        container = new MockDrawableContainer();
+        containerState = (DrawableContainerState) new LevelListDrawable().getConstantState();
+        assertNotNull(containerState);
+        container.setConstantState(containerState);
+        containerState.addChild(DrawableFactory.createPaintDrawable());
+        containerState.addChild(DrawableFactory.createBitmapDrawable(mContext));
+        containerState.addChild(DrawableFactory.createColorDrawable());
+        container.selectDrawable(0);
+        container.setState(FOCUSED_STATE);
+        assertFalse(container.hasFocusStateSpecified());
+        container.selectDrawable(1);
+        container.setState(FOCUSED_STATE);
+        assertFalse(container.hasFocusStateSpecified());
+        container.selectDrawable(2);
+        container.setState(FOCUSED_STATE);
+        assertFalse(container.hasFocusStateSpecified());
+
+        // Only drawables of state_focused=true
+        container = new MockDrawableContainer();
+        containerState = (DrawableContainerState) new LevelListDrawable().getConstantState();
+        assertNotNull(containerState);
+        container.setConstantState(containerState);
+        containerState.addChild(DrawableFactory.createRippleDrawable());
+        containerState.addChild(
+            DrawableFactory.createStateListDrawable(
+                new int[][] { STATE_FOCUSED_WITH_POS, STATE_FOCUSED_WITH_NEG }
+            )
+        );
+        container.selectDrawable(0);
+        container.setState(FOCUSED_STATE);
+        assertTrue(container.hasFocusStateSpecified());
+        container.selectDrawable(1);
+        container.setState(FOCUSED_STATE);
+        assertTrue(container.hasFocusStateSpecified());
+
+        // Both drawables of state_focused=true and state_focused=false
+        containerState.addChild(DrawableFactory.createColorDrawable());
+        container.selectDrawable(2);
+        container.setState(FOCUSED_STATE);
+        assertFalse(container.hasFocusStateSpecified());
+        container.selectDrawable(1);
+        container.setState(FOCUSED_STATE);
+        assertTrue(container.hasFocusStateSpecified());
+        container.selectDrawable(0);
+        container.setState(FOCUSED_STATE);
+        assertTrue(container.hasFocusStateSpecified());
+    }
+
+    static class DrawableFactory {
+        static ShapeDrawable createShapeDrawable() {
+            return new ShapeDrawable(new RectShape());
+        }
+        static PaintDrawable createPaintDrawable() {
+            PaintDrawable paintDrawable = new PaintDrawable();
+            paintDrawable.setCornerRadius(1.5f);
+            return paintDrawable;
+        }
+        static BitmapDrawable createBitmapDrawable(Context context) {
+            Bitmap bitmap = Bitmap.createBitmap(200, 300, Config.ARGB_8888);
+            BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap);
+            return bitmapDrawable;
+        }
+        static ColorDrawable createColorDrawable() {
+            return new ColorDrawable(A_COLOR);
+        }
+        static GradientDrawable createGradientDrawable() {
+            GradientDrawable gradientDrawable = new GradientDrawable();
+            gradientDrawable.setColor(A_COLOR);
+            gradientDrawable.setCornerRadius(10f);
+            return gradientDrawable;
+        }
+        static NinePatchDrawable createNinePatchDrawable(Context context) {
+            Resources res = context.getResources();
+            Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.ninepatch_0);
+            NinePatch np = new NinePatch(bitmap, bitmap.getNinePatchChunk(), null);
+            NinePatchDrawable ninePatchDrawable = new NinePatchDrawable(res, np);
+            return ninePatchDrawable;
+        }
+        static RippleDrawable createRippleDrawable() {
+            RippleDrawable rippleDrawable =
+                    new RippleDrawable(ColorStateList.valueOf(A_COLOR), null, null);
+            return rippleDrawable;
+        }
+        static PictureDrawable createPictureDrawable(Picture picture) {
+            PictureDrawable pictureDrawable = new PictureDrawable(picture);
+            return pictureDrawable;
+        }
+        static StateListDrawable createStateListDrawable(int[][] stateList) {
+            StateListDrawable drawable = new StateListDrawable();
+            ColorDrawable colorDrawable = DrawableFactory.createColorDrawable();
+            for (int i = 0; i < stateList.length; i++) {
+                drawable.addState(stateList[i], colorDrawable);
+            }
+            return drawable;
+        }
+    }
+
+    // We're calling protected methods in DrawableContainer.
+    // So we have to extend it here to make it accessible.
+    private class MockDrawableContainer extends DrawableContainer {
+        @Override
+        protected void setConstantState(DrawableContainerState state) {
+            super.setConstantState(state);
+        }
+    }
+
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 8bfd79b..f01b833 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -56,6 +56,7 @@
 import android.os.SystemProperties;
 import android.security.KeyStoreException;
 import android.security.keystore.AttestationUtils;
+import android.security.keystore.DeviceIdAttestationException;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.test.AndroidTestCase;
@@ -357,9 +358,9 @@
     }
 
     public void testDeviceIdAttestation() throws Exception {
-        testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_SERIAL);
-        testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_IMEI);
-        testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_MEID);
+        testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_SERIAL, null);
+        testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_IMEI, "Unable to retrieve IMEI");
+        testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_MEID, "Unable to retrieve MEID");
     }
 
     @SuppressWarnings("deprecation")
@@ -855,13 +856,23 @@
         }
     }
 
-    private void testDeviceIdAttestationFailure(int idType) throws Exception {
+    private void testDeviceIdAttestationFailure(int idType,
+            String acceptableDeviceIdAttestationFailureMessage) throws Exception {
         try {
             AttestationUtils.attestDeviceIds(getContext(), new int[] {idType}, "123".getBytes());
             fail("Attestation should have failed.");
         } catch (SecurityException e) {
-            // Attestation is expected to fail with a SecurityException as we do not hold
+            // Attestation is expected to fail. If the device has the device ID type we are trying
+            // to attest, it should fail with a SecurityException as we do not hold
             // READ_PRIVILEGED_PHONE_STATE permission.
+        } catch (DeviceIdAttestationException e) {
+            // Attestation is expected to fail. If the device does not have the device ID type we
+            // are trying to attest (e.g. no IMEI on devices without a radio), it should fail with
+            // a corresponding DeviceIdAttestationException.
+            if (acceptableDeviceIdAttestationFailureMessage == null ||
+                    !acceptableDeviceIdAttestationFailureMessage.equals(e.getMessage())) {
+                throw e;
+            }
         }
     }
 }
diff --git a/tests/tests/security/src/android/security/cts/AslrTest.java b/tests/tests/security/src/android/security/cts/AslrTest.java
index 378aa0f..67bb48f 100644
--- a/tests/tests/security/src/android/security/cts/AslrTest.java
+++ b/tests/tests/security/src/android/security/cts/AslrTest.java
@@ -52,7 +52,7 @@
         BufferedReader reader = new BufferedReader(
                 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())));
 
-        Pattern p = Pattern.compile("^([a-f0-9]+)\\-.+\\[" + mappingName + "\\]$");
+        Pattern p = Pattern.compile("^([a-f0-9]+).*" + mappingName + ".*");
         String line;
 
         while ((line = reader.readLine()) != null) {
@@ -97,7 +97,8 @@
     }
 
     public void testRandomization() throws Exception {
-        testMappingEntropy("stack");
+        testMappingEntropy("\\[stack\\]");
+        testMappingEntropy("/system/bin/");
     }
 
     public void testOneExecutableIsPie() throws IOException {
diff --git a/tests/tests/telephony/AndroidTest.xml b/tests/tests/telephony/AndroidTest.xml
index 2e6011b..2e225b6 100644
--- a/tests/tests/telephony/AndroidTest.xml
+++ b/tests/tests/telephony/AndroidTest.xml
@@ -14,6 +14,10 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Telephony test cases">
+    <target_preparer class="android.telephony.cts.preconditions.TelephonyPreparer">
+        <option name="apk" value="CtsTelephonyPreparerApp.apk" />
+        <option name="package" value="android.telephony.cts.preconditions.app" />
+    </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
         <option name="token" value="sim-card" />
     </target_preparer>
diff --git a/tests/tests/text/src/android/text/method/cts/PasswordTransformationMethodTest.java b/tests/tests/text/src/android/text/method/cts/PasswordTransformationMethodTest.java
index 3ed0ad4..bb4ffb6 100644
--- a/tests/tests/text/src/android/text/method/cts/PasswordTransformationMethodTest.java
+++ b/tests/tests/text/src/android/text/method/cts/PasswordTransformationMethodTest.java
@@ -17,13 +17,11 @@
 package android.text.method.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.reset;
@@ -221,23 +219,6 @@
         assertSame(method0, method1);
     }
 
-    @Test
-    public void testOnFocusChanged() {
-        // lose focus
-        reset(mMethod);
-        assertTrue(mEditText.isFocused());
-        CtsKeyEventUtil.sendKeys(mInstrumentation, mEditText, "DPAD_DOWN");
-        assertFalse(mEditText.isFocused());
-        verify(mMethod, atLeastOnce()).onFocusChanged(any(), any(), anyBoolean(), anyInt(), any());
-
-        // gain focus
-        reset(mMethod);
-        assertFalse(mEditText.isFocused());
-        CtsKeyEventUtil.sendKeys(mInstrumentation, mEditText, "DPAD_UP");
-        assertTrue(mEditText.isFocused());
-        verify(mMethod, atLeastOnce()).onFocusChanged(any(), any(), anyBoolean(), anyInt(), any());
-    }
-
     private void savePasswordPref() {
         try {
             mPasswordPrefBackUp = System.getInt(mActivity.getContentResolver(),
diff --git a/tests/tests/text/src/android/text/method/cts/TransformationMethodTest.java b/tests/tests/text/src/android/text/method/cts/TransformationMethodTest.java
new file mode 100644
index 0000000..d67fc59
--- /dev/null
+++ b/tests/tests/text/src/android/text/method/cts/TransformationMethodTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 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.text.method.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.method.TransformationMethod;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that {@link TransformationMethod} interface gets called.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TransformationMethodTest {
+    private static final int EDIT_TXT_ID = 1;
+
+    private Instrumentation mInstrumentation;
+    private CtsActivity mActivity;
+    private TransformationMethod mMethod;
+    private EditText mEditText;
+    private Button mButton;
+
+    @Rule
+    public ActivityTestRule<CtsActivity> mActivityRule = new ActivityTestRule<>(CtsActivity.class);
+
+    @Before
+    public void setup() throws Throwable {
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(1000, mActivity::hasWindowFocus);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mMethod = mock(TransformationMethod.class);
+        when(mMethod.getTransformation(any(), any())).then(returnsFirstArg());
+
+        mActivityRule.runOnUiThread(() -> {
+            mEditText = new EditTextNoIme(mActivity);
+            mEditText.setId(EDIT_TXT_ID);
+            mEditText.setTransformationMethod(mMethod);
+            mButton = new Button(mActivity);
+            mButton.setFocusableInTouchMode(true);
+            LinearLayout layout = new LinearLayout(mActivity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.addView(mEditText, new LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.WRAP_CONTENT));
+            layout.addView(mButton, new LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.WRAP_CONTENT));
+            mActivity.setContentView(layout);
+            mEditText.requestFocus();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(mEditText.isFocused());
+    }
+
+    @Test
+    public void testGetTransformation() throws Throwable {
+        reset(mMethod);
+        when(mMethod.getTransformation(any(), any())).then(returnsFirstArg());
+        mActivityRule.runOnUiThread(() -> mEditText.setText("some text"));
+        mInstrumentation.waitForIdleSync();
+        verify(mMethod, atLeastOnce()).getTransformation(any(), any());
+    }
+
+    @Test
+    public void testOnFocusChanged() throws Throwable {
+        // lose focus
+        reset(mMethod);
+        assertTrue(mEditText.isFocused());
+        mActivityRule.runOnUiThread(() -> mButton.requestFocus());
+        mInstrumentation.waitForIdleSync();
+        verify(mMethod, atLeastOnce()).onFocusChanged(any(), any(), anyBoolean(), anyInt(), any());
+
+        // gain focus
+        reset(mMethod);
+        assertFalse(mEditText.isFocused());
+        mActivityRule.runOnUiThread(() -> mEditText.requestFocus());
+        mInstrumentation.waitForIdleSync();
+        assertTrue(mEditText.isFocused());
+        verify(mMethod, atLeastOnce()).onFocusChanged(any(), any(), anyBoolean(), anyInt(), any());
+    }
+}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 8afec2c..5f50e9c 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -331,6 +331,13 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity android:name="android.view.cts.DefaultFocusHighlightCtsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/res/layout/default_focus_highlight_layout.xml b/tests/tests/view/res/layout/default_focus_highlight_layout.xml
new file mode 100644
index 0000000..827de43
--- /dev/null
+++ b/tests/tests/view/res/layout/default_focus_highlight_layout.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 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="match_parent"
+    android:orientation="vertical">
+
+    <View
+        android:id="@+id/view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/id_ok"/>
+
+    <ListView
+        android:id="@+id/listview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/id_ok"/>
+
+    <EditText
+        android:id="@+id/edittext"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/id_ok"
+        android:inputType="text"/>
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/id_ok"/>
+
+    <LinearLayout
+        android:id="@+id/linearlayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+    </LinearLayout>
+
+    <View
+        android:id="@+id/view_to_inflate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:defaultFocusHighlightEnabled="false"
+        android:text="@string/id_ok"/>
+
+    <ListView
+        android:id="@+id/listview_to_inflate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:defaultFocusHighlightEnabled="true"
+        android:text="@string/id_ok"/>
+
+    <EditText
+        android:id="@+id/edittext_to_inflate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/id_ok"
+        android:defaultFocusHighlightEnabled="true"
+        android:inputType="text"/>
+
+    <Button
+        android:id="@+id/button_to_inflate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:defaultFocusHighlightEnabled="false"
+        android:text="@string/id_ok"/>
+
+    <LinearLayout
+        android:id="@+id/linearlayout_to_inflate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:defaultFocusHighlightEnabled="false"
+        android:orientation="vertical">
+    </LinearLayout>
+
+</LinearLayout>
+
diff --git a/tests/tests/view/src/android/view/cts/DefaultFocusHighlightCtsActivity.java b/tests/tests/view/src/android/view/cts/DefaultFocusHighlightCtsActivity.java
new file mode 100644
index 0000000..5211938
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/DefaultFocusHighlightCtsActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.view.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A simple activity to test "Default Focus Highlight"
+ */
+public class DefaultFocusHighlightCtsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.default_focus_highlight_layout);
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/View_DefaultFocusHighlightTest.java b/tests/tests/view/src/android/view/cts/View_DefaultFocusHighlightTest.java
new file mode 100644
index 0000000..5e362cf
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/View_DefaultFocusHighlightTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 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.view.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class View_DefaultFocusHighlightTest {
+
+    @Rule
+    public ActivityTestRule<DefaultFocusHighlightCtsActivity> mActivityRule =
+            new ActivityTestRule<>(DefaultFocusHighlightCtsActivity.class);
+
+    @UiThreadTest
+    @Test
+    public void testSettersAndGetters() {
+        Activity activity = mActivityRule.getActivity();
+
+        View view = activity.findViewById(R.id.view);
+        ListView listView = (ListView) activity.findViewById(R.id.listview);
+        EditText editText = (EditText) activity.findViewById(R.id.edittext);
+        Button button = (Button) activity.findViewById(R.id.button);
+        LinearLayout linearLayout = (LinearLayout) activity.findViewById(R.id.linearlayout);
+
+        assertTrue(view.getDefaultFocusHighlightEnabled());
+        assertFalse(listView.getDefaultFocusHighlightEnabled());
+        assertFalse(editText.getDefaultFocusHighlightEnabled());
+        assertTrue(button.getDefaultFocusHighlightEnabled());
+        assertTrue(linearLayout.getDefaultFocusHighlightEnabled());
+
+        view.setDefaultFocusHighlightEnabled(false);
+        listView.setDefaultFocusHighlightEnabled(true);
+        editText.setDefaultFocusHighlightEnabled(true);
+        button.setDefaultFocusHighlightEnabled(false);
+        linearLayout.setDefaultFocusHighlightEnabled(false);
+
+        assertFalse(view.getDefaultFocusHighlightEnabled());
+        assertTrue(listView.getDefaultFocusHighlightEnabled());
+        assertTrue(editText.getDefaultFocusHighlightEnabled());
+        assertFalse(button.getDefaultFocusHighlightEnabled());
+        assertFalse(linearLayout.getDefaultFocusHighlightEnabled());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testInflating() {
+        Activity activity = mActivityRule.getActivity();
+
+        View view = activity.findViewById(R.id.view_to_inflate);
+        ListView listView = (ListView) activity.findViewById(R.id.listview_to_inflate);
+        EditText editText = (EditText) activity.findViewById(R.id.edittext_to_inflate);
+        Button button = (Button) activity.findViewById(R.id.button_to_inflate);
+        LinearLayout linearLayout = (LinearLayout) activity.findViewById(
+                R.id.linearlayout_to_inflate);
+
+        assertFalse(view.getDefaultFocusHighlightEnabled());
+        assertTrue(listView.getDefaultFocusHighlightEnabled());
+        assertTrue(editText.getDefaultFocusHighlightEnabled());
+        assertFalse(button.getDefaultFocusHighlightEnabled());
+        assertFalse(linearLayout.getDefaultFocusHighlightEnabled());
+    }
+}