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());
+ }
+}