Merge "Fixed compareBitmaps(Bitmap bmp1, Bitmap bmp2) method." into nyc-dev
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 79e8a36..41f7b48 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -46,7 +46,7 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
LOCAL_DEX_PREOPT := false
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 c9ee92a..bae1bfa 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
@@ -35,7 +35,6 @@
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ILogSaver;
import com.android.tradefed.result.ILogSaverListener;
import com.android.tradefed.result.ITestInvocationListener;
@@ -358,12 +357,12 @@
}
// Save the full results folder.
if (zippedResults != null) {
- FileInputStreamSource fiss = null;
+ FileInputStream zipResultStream = null;
try {
- fiss = new FileInputStreamSource(zippedResults);
- testLog("results", LogDataType.ZIP, fiss);
+ zipResultStream = new FileInputStream(zippedResults);
+ mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
} finally {
- StreamUtil.cancel(fiss);
+ StreamUtil.close(zipResultStream);
}
}
}
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 8e435d8..655add8 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -53,6 +53,7 @@
private static final String BUILD_PRODUCT = "build_product";
private static final String BUILD_TAG = "Build";
private static final String CASE_TAG = "TestCase";
+ private static final String DETAIL_TAG = "Detail";
private static final String DEVICES_ATTR = "devices";
private static final String END_DISPLAY_TIME_ATTR = "end_display";
private static final String END_TIME_ATTR = "end";
@@ -171,7 +172,11 @@
parser.nextTag();
} else {
test.setReportLog(ReportLog.parse(parser));
- parser.nextTag();
+ // Details are optional; parser is at next tag if report log
+ // does not have details.
+ if (parser.getName().equals(DETAIL_TAG)) {
+ parser.nextTag();
+ }
}
}
parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-2048.apk b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-2048.apk
index 90ab764..51ed3ff 100644
--- a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-2048.apk
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-2048.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-4096.apk b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-4096.apk
index 101602a..acf5cb6 100644
--- a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-4096.apk
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-4096.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index a2ef1f3..ad00adb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -36,6 +36,10 @@
* Tests that verify installing of various split APKs from host side.
*/
public class SplitTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+ static final String PKG_NO_RESTART = "com.android.cts.norestart";
+ static final String APK_NO_RESTART_BASE = "CtsNoRestartBase.apk";
+ static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
+
static final String PKG = "com.android.cts.splitapp";
static final String CLASS = ".SplitAppTest";
@@ -110,6 +114,7 @@
super.tearDown();
getDevice().uninstallPackage(PKG);
+ getDevice().uninstallPackage(PKG_NO_RESTART);
}
public void testSingleBase() throws Exception {
@@ -279,6 +284,18 @@
// TODO: flesh out this test
}
+ public void testFeatureWithoutRestart() throws Exception {
+ new InstallMultiple().addApk(APK).run();
+ new InstallMultiple().addApk(APK_NO_RESTART_BASE).run();
+ runDeviceTests(PKG, CLASS, "testBaseInstalled");
+ new InstallMultiple()
+ .addArg("--dont-kill")
+ .inheritFrom(PKG_NO_RESTART)
+ .addApk(APK_NO_RESTART_FEATURE)
+ .run();
+ runDeviceTests(PKG, CLASS, "testFeatureInstalled");
+ }
+
/**
* Verify that installing a new version of app wipes code cache.
*/
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.mk b/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.mk
new file mode 100644
index 0000000..69d3b4a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.mk
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := CtsNoRestartBase
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+
+################################################
+# Build the feature split
+
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(LOCAL_PATH)/feature/Android.mk
+endif
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
new file mode 100644
index 0000000..7140333
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.cts.norestart"
+ tools:ignore="MissingVersion" >
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="23" />
+
+ <application
+ tools:ignore="AllowBackup,MissingApplicationIcon" >
+ <activity
+ android:name=".NoRestartActivity"
+ android:launchMode="singleTop" >
+ <intent-filter>
+ <action android:name="com.android.cts.norestart.START" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.mk b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.mk
new file mode 100644
index 0000000..204275b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.mk
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := CtsNoRestartFeature
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+localRStamp := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)/src/R.stamp
+featureOf := CtsNoRestartBase
+featureOfApk := $(call intermediates-dir-for,APPS,$(featureOf))/package.apk
+$(localRStamp): $(featureOfApk)
+LOCAL_APK_LIBRARIES := $(featureOf)
+LOCAL_AAPT_FLAGS += --feature-of $(featureOfApk)
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml
new file mode 100644
index 0000000..b2fa3e8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.cts.norestart"
+ split="feature"
+ tools:ignore="MissingVersion" >
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="23" />
+
+ <application
+ android:allowBackup="false"
+ tools:ignore="MissingApplicationIcon" >
+ <activity
+ android:name=".feature.NoRestartFeatureActivity" >
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/res/layout/no_restart_feature_activity.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/res/layout/no_restart_feature_activity.xml
new file mode 100644
index 0000000..a5f8812
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/res/layout/no_restart_feature_activity.xml
@@ -0,0 +1,32 @@
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="16dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="16dp"
+ android:orientation="vertical"
+ tools:context="com.android.cts.norestart.NoRestartFeatureActivity" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/no_restart_feature_text" />
+
+</LinearLayout>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/res/values/strings.xml
new file mode 100644
index 0000000..1fb1db3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <string name="no_restart_feature_text">Hello feature!</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/src/com/android/cts/norestart/feature/NoRestartFeatureActivity.java b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/src/com/android/cts/norestart/feature/NoRestartFeatureActivity.java
new file mode 100644
index 0000000..40f8259
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/src/com/android/cts/norestart/feature/NoRestartFeatureActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.cts.norestart.feature;
+
+import com.android.cts.norestart.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class NoRestartFeatureActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.no_restart_feature_activity);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/res/layout/no_restart_activity.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/res/layout/no_restart_activity.xml
new file mode 100644
index 0000000..60ab817
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/res/layout/no_restart_activity.xml
@@ -0,0 +1,32 @@
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="16dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="16dp"
+ android:orientation="vertical"
+ tools:context="com.android.cts.norestart.NoRestartActivity" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/no_restart_text" />
+
+</LinearLayout>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/res/values/strings.xml
new file mode 100644
index 0000000..5240150
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <string name="no_restart_text">Hello, world!</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java b/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java
new file mode 100644
index 0000000..26d5712
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.android.cts.norestart;
+
+import com.android.cts.norestart.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class NoRestartActivity extends Activity {
+ private int mCreateCount;
+ private int mNewIntentCount;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.no_restart_activity);
+ mCreateCount++;
+ sendBroadcast();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ mNewIntentCount++;
+ sendBroadcast();
+ }
+
+ private void sendBroadcast() {
+ final Intent intent = new Intent("com.android.cts.norestart.BROADCAST");
+ intent.putExtra("CREATE_COUNT", mCreateCount);
+ intent.putExtra("NEW_INTENT_COUNT", mNewIntentCount);
+ sendBroadcast(intent);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/values.xml
index 3118fde..ecb7eae 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/values.xml
@@ -19,6 +19,7 @@
<string name="my_string1">blue</string>
<string name="my_string2">purple</string>
+ <string name="no_restart_text">Hello world!</string>
<string-array name="my_string_array">
<item>@string/my_string1</item>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index c5f0fd0..7a693e8 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -19,8 +19,10 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.os.ConditionVariable;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.StatFs;
@@ -305,6 +308,50 @@
}
}
+ public void testBaseInstalled() throws Exception {
+ final ConditionVariable cv = new ConditionVariable();
+ final BroadcastReceiver r = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
+ assertEquals(0, intent.getIntExtra("NEW_INTENT_COUNT", -1));
+ cv.open();
+ }
+ };
+ final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
+ getContext().registerReceiver(r, filter);
+ final Intent i = new Intent("com.android.cts.norestart.START");
+ i.addCategory(Intent.CATEGORY_DEFAULT);
+ getContext().startActivity(i);
+ assertTrue(cv.block(2000L));
+ getContext().unregisterReceiver(r);
+ }
+
+ /**
+ * Tests a running activity remains active while a new feature split is installed.
+ * <p>
+ * Prior to running this test, the activity must be started. That is currently
+ * done in {@link #testBaseInstalled()}.
+ */
+ public void testFeatureInstalled() throws Exception {
+ final ConditionVariable cv = new ConditionVariable();
+ final BroadcastReceiver r = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
+ assertEquals(1, intent.getIntExtra("NEW_INTENT_COUNT", -1));
+ cv.open();
+ }
+ };
+ final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
+ getContext().registerReceiver(r, filter);
+ final Intent i = new Intent("com.android.cts.norestart.START");
+ i.addCategory(Intent.CATEGORY_DEFAULT);
+ getContext().startActivity(i);
+ assertTrue(cv.block(2000L));
+ getContext().unregisterReceiver(r);
+ }
+
public void testFeatureApi() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 1944444..e1c42b1 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -47,7 +47,8 @@
</receiver>
<activity
- android:name="com.android.cts.deviceowner.KeyManagementActivity" />
+ android:name="com.android.cts.deviceowner.KeyManagementActivity"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name="com.android.cts.deviceowner.LockTaskUtilityActivity" />
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/assets/ca.conf b/hostsidetests/devicepolicy/app/DeviceOwner/assets/ca.conf
new file mode 100644
index 0000000..c27a473
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/assets/ca.conf
@@ -0,0 +1,156 @@
+# OpenSSL root CA configuration file.
+# Copy to `/root/ca/openssl.cnf`.
+
+[ ca ]
+# `man ca`
+
+[ RootCA ]
+# Directory and file locations.
+dir = ./rootca
+certs = $dir/certs
+crl_dir = $dir/crl
+new_certs_dir = $dir/newcerts
+database = $dir/index.txt
+serial = $dir/serial
+RANDFILE = $dir/private/.rand
+
+# The root key and root certificate.
+private_key = $dir/private/ca.key.pem
+certificate = $dir/certs/ca.cert.pem
+
+# For certificate revocation lists.
+crlnumber = $dir/crlnumber
+crl = $dir/crl/ca.crl.pem
+crl_extensions = crl_ext
+default_crl_days = 30
+
+# SHA-1 is deprecated, so use SHA-2 instead.
+default_md = sha256
+
+name_opt = ca_default
+cert_opt = ca_default
+default_days = 375
+preserve = no
+policy = policy_strict
+
+[ IntermediateCA ]
+# Directory and file locations.
+dir = ./intermediate
+certs = $dir/certs
+crl_dir = $dir/crl
+new_certs_dir = $dir/newcerts
+database = $dir/index.txt
+serial = $dir/serial
+RANDFILE = $dir/private/.rand
+
+# The root key and root certificate.
+private_key = $dir/private/intermediate.key.pem
+certificate = $dir/certs/intermediate.cert.pem
+
+# For certificate revocation lists.
+crlnumber = $dir/crlnumber
+crl = $dir/crl/ca.crl.pem
+crl_extensions = crl_ext
+default_crl_days = 30
+
+# SHA-1 is deprecated, so use SHA-2 instead.
+default_md = sha256
+
+name_opt = ca_default
+cert_opt = ca_default
+default_days = 375
+preserve = no
+policy = policy_strict
+
+[ policy_strict ]
+# The root CA should only sign intermediate certificates that match.
+# See the POLICY FORMAT section of `man ca`.
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[ policy_loose ]
+# Allow the intermediate CA to sign a more diverse range of certificates.
+# See the POLICY FORMAT section of the `ca` man page.
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[ req ]
+# Options for the `req` tool (`man req`).
+default_bits = 4096
+distinguished_name = req_distinguished_name
+string_mask = utf8only
+
+# SHA-1 is deprecated, so use SHA-2 instead.
+default_md = sha256
+
+# Extension to add when the -x509 option is used.
+x509_extensions = v3_ca
+
+[ req_distinguished_name ]
+# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
+countryName = Country Name (2 letter code)
+stateOrProvinceName = State or Province Name
+0.organizationName = Organization Name
+organizationalUnitName = Organizational Unit Name
+commonName = Common Name
+
+# Optionally, specify some defaults.
+countryName_default = GB
+stateOrProvinceName_default = England
+0.organizationName_default = Google UK
+organizationalUnitName_default = AfW
+
+[ v3_ca ]
+# Extensions for a typical CA (`man x509v3_config`).
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+[ v3_intermediate_ca ]
+# Extensions for a typical intermediate CA (`man x509v3_config`).
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true, pathlen:0
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+[ usr_cert ]
+# Extensions for client certificates (`man x509v3_config`).
+basicConstraints = CA:FALSE
+nsCertType = client, email
+nsComment = "OpenSSL Generated Client Certificate"
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer
+keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = clientAuth, emailProtection
+
+[ server_cert ]
+# Extensions for server certificates (`man x509v3_config`).
+basicConstraints = CA:FALSE
+nsCertType = server
+nsComment = "OpenSSL Generated Server Certificate"
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer:always
+keyUsage = critical, digitalSignature, keyEncipherment
+extendedKeyUsage = serverAuth
+
+[ crl_ext ]
+# Extension for CRLs (`man x509v3_config`).
+authorityKeyIdentifier=keyid:always
+
+[ ocsp ]
+# Extension for OCSP signing certificates (`man ocsp`).
+basicConstraints = CA:FALSE
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer
+keyUsage = critical, digitalSignature
+extendedKeyUsage = critical, OCSPSigning
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/assets/generate-client-cert-chain.sh b/hostsidetests/devicepolicy/app/DeviceOwner/assets/generate-client-cert-chain.sh
new file mode 100755
index 0000000..8b0639f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/assets/generate-client-cert-chain.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+#
+# Generates:
+# - user-cert-chain.crt
+# - user-cert-chain.key
+#
+
+set -e
+
+WORKDIR='temp'
+
+mkdir "$WORKDIR"
+cp ca.conf "$WORKDIR/"
+pushd "$WORKDIR"
+
+## Generate root CA
+mkdir -p rootca/{certs,crl,newcerts,private}
+pushd rootca
+touch index.txt
+echo '1000' > serial
+openssl req \
+ -config ../ca.conf \
+ -new \
+ -x509 \
+ -days 7300 \
+ -sha256 \
+ -extensions v3_ca \
+ -keyout private/ca.key.pem \
+ -out certs/ca.cert.pem
+popd
+
+## Generate Intermediate CA
+mkdir intermediate intermediate/{certs,crl,csr,newcerts,private}
+touch intermediate/index.txt
+
+echo '1000' > intermediate/serial
+echo '1000' > intermediate/crlnumber
+
+openssl req \
+ -config ca.conf \
+ -new \
+ -sha256 \
+ -keyout intermediate/private/intermediate.key.pem \
+ -out intermediate/csr/intermediate.csr.pem
+
+openssl ca \
+ -config ca.conf \
+ -name RootCA \
+ -extensions v3_intermediate_ca \
+ -days 3650 \
+ -notext \
+ -md sha256 \
+ -in intermediate/csr/intermediate.csr.pem \
+ -out intermediate/certs/intermediate.cert.pem
+
+## Generate client cert
+openssl req \
+ -config ca.conf \
+ -newkey rsa:1024 \
+ -keyout user.key.pem \
+ -nodes \
+ -days 3650 \
+ -out user.csr.pem
+
+openssl ca \
+ -config ca.conf \
+ -name IntermediateCA \
+ -extensions usr_cert \
+ -days 365 \
+ -notext \
+ -md sha256 \
+ -in user.csr.pem \
+ -out user.cert.pem
+
+popd # WORKDIR
+
+## Convert client cert to acceptable form
+cat \
+ "$WORKDIR"/user.cert.pem \
+ "$WORKDIR"/intermediate/certs/intermediate.cert.pem \
+ "$WORKDIR"/rootca/certs/ca.cert.pem \
+ > user-cert-chain.crt
+
+openssl pkcs8 \
+ -topk8 \
+ -nocrypt \
+ -inform PEM \
+ -outform DER \
+ -in "$WORKDIR"/user.key.pem \
+ -out user-cert-chain.key
+
+rm -r "$WORKDIR"
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/assets/user-cert-chain.crt b/hostsidetests/devicepolicy/app/DeviceOwner/assets/user-cert-chain.crt
new file mode 100644
index 0000000..72a86e3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/assets/user-cert-chain.crt
@@ -0,0 +1,96 @@
+-----BEGIN CERTIFICATE-----
+MIIFLzCCAxegAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwZDELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUdvb2dsZSBVSzEMMAoGA1UECwwD
+QWZ3MSEwHwYDVQQDDBhBZlcgVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTYwMzE4
+MTcxMzA4WhcNMTcwMzI4MTcxMzA4WjCBiDELMAkGA1UEBhMCR0IxEDAOBgNVBAgM
+B0VuZ2xhbmQxEjAQBgNVBAoMCUdvb2dsZSBVSzEMMAoGA1UECwwDQWZ3MSUwIwYD
+VQQDDBxVc2VyMDAgdW5kZXIgaW50ZXJtZWRpYXRlIENBMR4wHAYJKoZIhvcNAQkB
+Fg90ZXN0QGdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQC8W+PUeNVDIy6GSeTVjN9JSkYxcsupFq9AOUma0R+7z9EGuZBURZprgbrN7c2q
+RQnlSBZTC9fRMkXZ6LImWoY5GqS3NcbkJbUlA+UeK2uJXQQfjTO7bYDslvudX+8y
+WfYrR71DLpIFgDkxQAWGywMzNTR6TEmPy1qBGIFYohGqZkQoTS//s/iEEKDSsbPr
+mkTrf4lDAc8cgnmUPFPkN1Lr4ITkvhmEHQjJTcS+Qjeotlt+ss5vrmlqopFkCbI9
+7uC6RQDI0PvP9achzBsTUi0vNsGg45luCJhNrDu6s4NpnusKIVAoJPSJdion2yoD
+3Dp8LX/ueGNbP64LY6qmDWDlAgMBAAGjgcUwgcIwCQYDVR0TBAIwADARBglghkgB
+hvhCAQEEBAMCBaAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIENs
+aWVudCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUSp7kS1On3b7MdMstDVPCNkHm/EUw
+HwYDVR0jBBgwFoAUdejD6Fb3X8ZHOCKMWe5XwukxBDswDgYDVR0PAQH/BAQDAgXg
+MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDANBgkqhkiG9w0BAQsFAAOC
+AgEAXIOVhMjxbpO1uxe1MnyIsTrl0ajPlkn+4qWLwjXzUQ6TcE2Ow91AMcYs5siq
+UBplZyNYNBOhX8TZLNy7jJ/REwj65Qa/y0TcDucpGhtT9l1JIJCdEpPoymyiM18C
+NktXDyaw+DFkWC0a5oUhjk4UuzTfHkSVMKjZUnRPPiwL2gl9zEgS8qVI3ew4JjdP
+KCYGy/1B+61EE5vCP8GAByeKgtgnh4sVZnsKYQZzjwwUGL1uXQtazPs04qTUw3IK
+YvoOyNsXB4gcp2u4DXv2roVI36DQM5ZGenS9MViTeblg5vkZgy8xsktHyDGDlNe6
+cPw5OgyxDo4nr6TY4SX9eankantPMx7498n390B4lYAgBj4Cz4QaXM1IGN3JVF5J
+EEKqGkLpOYMRNZ4qPFhMknDZgHljjgFlcXGwtXtugCzQ5ldwkFb9qZeB5lQn1Aw0
+PthcDdGp/KCtHC5jF+BjlQITt0tVqJ4+SAdHyF53H+ScoINFul89m32pgvJjI/0k
+c0tidvXNPNodbJCqHmc917DryVJGXbxp+BqxTQ0a7e9K/WA4MnRKPfBTTeDq/j+w
+6B/rLd0bhMrPDi6a/1w97PqfAbS9TlkpnK8Gj4pN+ZOEEF0j0DtDRbb+CfJX14fR
+2R96mEfCeSbCuxLcbwdG1OUQM8GKlIcYfWIp0KjICxRKaDU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFojCCA4qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwXDELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUdvb2dsZSBVSzEMMAoGA1UECwwD
+QWZXMRkwFwYDVQQDDBBBZncgVGVzdCBSb290IENBMB4XDTE2MDMxODE3MDQ1N1oX
+DTI2MDMxNjE3MDQ1N1owZDELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQx
+EjAQBgNVBAoMCUdvb2dsZSBVSzEMMAoGA1UECwwDQWZ3MSEwHwYDVQQDDBhBZlcg
+VGVzdCBJbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCZbYchy2Fry9nJuu2YEp2P13lIbyWu0FXF8qTus8Vp/25LyXDjBiDXZUlY
+PL32PY0PRbBQjm7tm/WNqXw8S7gw+5XXpY+XNCd/ygyIZhMdxPm7nqYsEtZDFViL
+ct/QJNAKILFejZQOfRSeyxeINprL+EjFHecA6KtruZULzJE0u0UGTgs5h9HbqhH7
+LbZ8iiE/TfG6kflUI2kAPxGiRpIyerYoVjp3Ta5026T+aoc6VyNnSYiZULgYLoL8
+P8x19G3Pplqf4U5bUyKtRtnPWOvM9iYphxsVuTc8rRpZGcMKhdL4gGLQpdruIZ43
+gvGMq4Kt2xVJExBOKMg3j3x52j1XtOcad/nz7ncak/6ElTd0gfhFgt9PwAfQZ32b
+BL3Zlcb+7Pvtv14xAWNHy5cMyn7UDzIsy/yqWLvJSfkZViU0vPuokXMKZIyzv73V
+4N9qXQAWXNz4HwgWy35rB1sirgMxLdWCpHrVeh/DzSrWZ/MtJIC9Ac1jTAuI6F1u
+b7dRRujWpcr57ReKDXXJzM83JQnENJQ3gAHrY8qTkGz7NLa7DsyzPdKOC7vZ0+Ed
+VMvn+c2AMWrwkRpn9JlU5bd2BN7D6UWGLTdzSN9QH7n7sXmQNAo/M7Lr9baxKZNY
+aU5DORVjnGvITZDHYiw9OuakWZUZATF+TTInKEasF131r9q9ZwIDAQABo2YwZDAd
+BgNVHQ4EFgQUdejD6Fb3X8ZHOCKMWe5XwukxBDswHwYDVR0jBBgwFoAUV4EHHOi0
+AqQIj4IMjPEFW3fVS8QwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC
+AYYwDQYJKoZIhvcNAQELBQADggIBACs0qS3EXymo1VgBInR9GrzvUq1gC/d3ohSG
+iB3x2zfqfQ4D0eiYADcCdXTPIwPtm8P8woK5QsRV/MCklqyvgVpRHFQe1uOAZJ7X
+Ud6hx9CCye0QkEoy+JDeVdPeFFf1b8S/daxLXUVbCKSTA+z8YLPRSEFi2d3cOwpx
+WPlkfLSwP6DfODicmPNd1V/qB/fevlmfRB6UKquT+v9xWyQqu4aa6F6xGWYWmc+1
+E/MB/oEOizJVv8VVETqMk8/xFPrMk28foI8ohrLkstSx8gH+oII1Ud1k1XoMMqqU
+Ge656Tr85Di5WfacMdKUommOEKQYRiic6ikcNEAVVNOHlOtw08ua7g1k1G/dwcj0
+DCF2WmWzdAMwST0AH/RPa+i9cX8f/yS15OUP7ncSaI7/ChGT3EBzP+bqxeXFOCNH
+0yNLk4tNLIzNwnKXGTfSbKMTYOZ3ngAiR4w3ro/LJhe2z03MOawxoiIosTc9UwKA
+YJ3nYHYw8/EJCKPth6yrUU3gU1V0vyaBy34y4xuha3oWnbc53vm1cv4BINwmuAms
+ASQpqCiGp2ZaalNu87xCnWE3HA4S3+0U3dsFJXdPdQt/cDzX+kDzojWeHmECp6mn
+GodmmPbEBqzDckMaM9CvSAp8NyZuO8hrOSoGTdxQtP1w3waOeM4zLYd7aBYUfefL
+36OoziEN
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFnjCCA4agAwIBAgIJANLdX1zcxUSUMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
+BAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQKDAlHb29nbGUgVUsxDDAK
+BgNVBAsMA0FmVzEZMBcGA1UEAwwQQWZ3IFRlc3QgUm9vdCBDQTAeFw0xNjAzMTgx
+NzAxMDBaFw0zNjAzMTMxNzAxMDBaMFwxCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdF
+bmdsYW5kMRIwEAYDVQQKDAlHb29nbGUgVUsxDDAKBgNVBAsMA0FmVzEZMBcGA1UE
+AwwQQWZ3IFRlc3QgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBANFyOD/BIGV4iHSGDrp1ajvp+00098dn+p1cqlMHMrWUjnqzcMdOVmeqSaQ/
+EkOlAIsdcl1yb+oo3DhomIzX/B2lTQSOSLDmthIgmu0hfk/gAiqLdA8/L2F9m64N
+9x4+72xscN3MxzvjKGUBgPDmRfR9Tp347j42HUCjmF5sTa7DzGMrU7I3gCmi7B3D
+zbkgdTwpucH2JDqHQPv+7PLaNyuZNEmiXM76DPyMypxMrtGrq/FDVJ7JwF+cSwbY
+WVfzbmOfHG7g6hRw7Bap/NNjcdtP09hRPG/g2WDy4z0Ay8MTZVe95EHTsyeR+kpv
+0f60eUI0cV7EovbLmp10I3RdsxbWTjbeFmNjM7WmmmsFRzA1jMlFGil/po4mJvMF
+Bcqbi4kUhQ49F4tRUlHRG1b/up71tDuzToF0YmN9GHkf/kt7/noVTYdEsm4RwaeF
+mhoaTMFaNaHGTHSyqroqbBCqlkfTqB1Cqw1weGqV6bGfaYpCJGx5vXmr06mh5dwo
+zvpyHQKCQu96a0G81T526RtVeA4QR89ELa0JSBpWR9MqVZKBte9AgS5vlF0386uM
+vcKC3zJ4srv1YrTOmMkLktNJHsyfLQgb70RdHR38hDEwKaq6VDWiewKDhsWAI5SJ
+wRgjAYspsNUVahDWvpXq/bRGM3JTW+QxiR22vgEitvKeIysLAgMBAAGjYzBhMB0G
+A1UdDgQWBBRXgQcc6LQCpAiPggyM8QVbd9VLxDAfBgNVHSMEGDAWgBRXgQcc6LQC
+pAiPggyM8QVbd9VLxDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAN
+BgkqhkiG9w0BAQsFAAOCAgEAjo/Fj7iTOr1/nTvZpeaon0/xY4D+Hf83FW/4yASZ
+Et0ITa510zIi8rIVvlaR1xdbXLYxHgdtm4vQtKZStwOBdwj+4VZrb9WgwQyBCYU5
+RqK387oMUeZdfsh9m2nqM8buYls3mldv34XUg8y1oytx6GDdC7NKz6PLNpIVkj5F
+aBnyfh43FsXHkzAy0nfkdE2mqfhQ4CD9Zkm9fJcX0inEmcspM5G8ba16uESZDqUS
+oJc1bgNtW64fL7pOtVfHDIJqKf/G/iIq1lk33gv5/4z6Z8e7fYVm1JabUUd9rZ6t
+cjXXFqkA7SkcXTs829/gaXQQv2FARt7g70UxJmNN0MCKfYnKM4dKddi934mTWrOI
+eLe0u3OAa1wZaHggJJXgRxMx/acWnGfersTpsAB1XG74XTSXHV7zHHnNWXjQ+gu0
+N4RAkQFMYWqp6KoHgQrdQfLPcaw0wc+ZMJj35z50b4ab+Bygthx3W+v/MiMFK9Wv
+/AsQCGslDcGWbFCYP7IvHDfownIFGefMnOm41NKWus9z6HoEUmfJiiSSVxECDT/2
+fE7M+sQovdrlHx7ru/fO6PP+6ocUE1afY6cHUzE0Dhv6xMcdvwL7COGd5ZU1bqAQ
+TqbePM5Kpk1ytkigdixzMDz/HFum0fdGfc/59Ll+f6+uHAX5NpOJZkBHBCWAoCeX
+bsg=
+-----END CERTIFICATE-----
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/assets/user-cert-chain.key b/hostsidetests/devicepolicy/app/DeviceOwner/assets/user-cert-chain.key
new file mode 100644
index 0000000..8bb399e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/assets/user-cert-chain.key
Binary files differ
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementActivity.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementActivity.java
index c108d24..fda124f 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementActivity.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementActivity.java
@@ -17,14 +17,6 @@
package com.android.cts.deviceowner;
import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
public class KeyManagementActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- finish();
- }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
index 5d6d7fb..219dfc2 100755
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
@@ -27,21 +27,29 @@
import android.test.ActivityInstrumentationTestCase2;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.security.cert.Certificate;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import android.content.ComponentName;
import android.content.Context;
+import android.content.res.AssetManager;
public class KeyManagementTest extends ActivityInstrumentationTestCase2<KeyManagementActivity> {
@@ -126,6 +134,38 @@
assertGranted(withhold, false);
}
+ public void testCanInstallCertChain() throws Exception {
+ // Use assets/generate-client-cert-chain.sh to regenerate the client cert chain.
+ final PrivateKey privKey = loadPrivateKeyFromAsset("user-cert-chain.key");
+ final Collection<Certificate> certs = loadCertificatesFromAsset("user-cert-chain.crt");
+ final Certificate[] certChain = certs.toArray(new Certificate[certs.size()]);
+ final String alias = "com.android.test.clientkeychain";
+ // Some sanity check on the cert chain
+ assertTrue(certs.size() > 1);
+ for (int i = 1; i < certs.size(); i++) {
+ certChain[i - 1].verify(certChain[i].getPublicKey());
+ }
+
+ // Install keypairs.
+ assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true));
+ try {
+ // Verify only the requested key was actually granted.
+ assertGranted(alias, true);
+
+ // Verify the granted key is actually obtainable in PrivateKey form.
+ assertEquals(KeyChain.getPrivateKey(getActivity(), alias).getAlgorithm(), "RSA");
+
+ // Verify the certificate chain is correct
+ X509Certificate[] returnedCerts = KeyChain.getCertificateChain(getActivity(), alias);
+ assertTrue(Arrays.equals(certChain, returnedCerts));
+ } finally {
+ // Delete both keypairs.
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ // Verify they're actually gone.
+ assertGranted(alias, false);
+ }
+
public void testGrantsDoNotPersistBetweenInstallations() throws Exception {
final String alias = "com.android.test.persistent-key-1";
final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
@@ -200,6 +240,35 @@
new ByteArrayInputStream(cert));
}
+ private Collection<Certificate> loadCertificatesFromAsset(String assetName) {
+ try {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ AssetManager am = getActivity().getAssets();
+ InputStream is = am.open(assetName);
+ return (Collection<Certificate>) certFactory.generateCertificates(is);
+ } catch (IOException | CertificateException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private PrivateKey loadPrivateKeyFromAsset(String assetName) {
+ try {
+ AssetManager am = getActivity().getAssets();
+ InputStream is = am.open(assetName);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int length;
+ byte[] buffer = new byte[4096];
+ while ((length = is.read(buffer, 0, buffer.length)) != -1) {
+ output.write(buffer, 0, length);
+ }
+ return getPrivateKey(output.toByteArray(), "RSA");
+ } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
private class KeyChainAliasFuture implements KeyChainAliasCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private String mChosenAlias = null;
@@ -224,5 +293,5 @@
assertTrue("Chooser timeout", mLatch.await(KEYCHAIN_TIMEOUT_MINS, TimeUnit.MINUTES));
return mChosenAlias;
}
- };
+ }
}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java
index f2445c0..54892de 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java
@@ -31,6 +31,7 @@
import java.util.Objects;
import static android.server.cts.ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID;
+import static android.server.cts.ActivityManagerTestBase.PINNED_STACK_ID;
import static android.server.cts.StateLogger.log;
/** Combined state of the activity manager and window manager. */
@@ -349,7 +350,8 @@
}
if (aStackBounds.getWidth() >= aTaskMinWidth
- && aStackBounds.getHeight() >= aTaskMinHeight) {
+ && aStackBounds.getHeight() >= aTaskMinHeight
+ || stackId == PINNED_STACK_ID) {
// Bounds are not smaller then minimal possible, so stack and task
// bounds must be equal.
assertEquals("Task bounds must be equal to stack bounds taskId="
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
index 0a01d6d..24b9fa9 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
@@ -117,11 +117,11 @@
}
public void testTranslucentActivityOverDockedStack() throws Exception {
- mDevice.executeShellCommand(getAmStartCmd(TEST_ACTIVITY_NAME));
launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
- launchActivityInDockStack(TRANSLUCENT_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME},
- false /* compareTaskAndStackBounds */);
+ launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+ launchActivityInStack(TRANSLUCENT_ACTIVITY_NAME, DOCKED_STACK_ID);
+ mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME,
+ TRANSLUCENT_ACTIVITY_NAME}, false /* compareTaskAndStackBounds */);
mAmWmState.assertContainsStack("Must contain docked stack", DOCKED_STACK_ID);
mAmWmState.assertContainsStack("Must contain fullscreen stack",
FULLSCREEN_WORKSPACE_STACK_ID);
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index 9e1e2cc..35ea407 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -194,7 +194,7 @@
public void testResizeDockedStack() throws Exception {
launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
- launchActivityToSide(TEST_ACTIVITY_NAME);
+ launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME},
false /* compareTaskAndStackBounds */);
@@ -215,7 +215,7 @@
mAmWmState.getWmState().getStack(FULLSCREEN_WORKSPACE_STACK_ID).getBounds();
moveActivityToDockStack(TEST_ACTIVITY_NAME);
- launchActivityToSide(NO_RELAUNCH_ACTIVITY_NAME);
+ launchActivityInStack(NO_RELAUNCH_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
mAmWmState.computeState(mDevice,
new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java
index 8eb0361..e56c32f 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java
@@ -33,6 +33,7 @@
import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
import static android.server.cts.StateLogger.log;
+import static android.server.cts.StateLogger.logE;
class ActivityManagerState {
private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity activities";
@@ -96,16 +97,16 @@
}
if (mStacks.isEmpty()) {
- log("No stacks found...");
+ logE("No stacks found...");
}
if (mFocusedStackId == -1) {
- log("No focused stack found...");
+ logE("No focused stack found...");
}
if (mFocusedActivityRecord == null) {
- log("No focused activity found...");
+ logE("No focused activity found...");
}
if (mResumedActivities.isEmpty()) {
- log("No resumed activities found...");
+ logE("No resumed activities found...");
}
}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index e330a9d..4c4748a 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -98,6 +98,7 @@
protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
private int mInitialAccelerometerRotation;
+ private int mUserRotation;
@Override
protected void setUp() throws Exception {
@@ -106,6 +107,7 @@
// Get the device, this gives a handle to run commands and install APKs.
mDevice = getDevice();
mInitialAccelerometerRotation = getAccelerometerRotation();
+ mUserRotation = getUserRotation();
}
@Override
@@ -114,6 +116,7 @@
try {
mDevice.executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
setAccelerometerRotation(mInitialAccelerometerRotation);
+ setUserRotation(mUserRotation);
// Remove special stacks.
mDevice.executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
mDevice.executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
@@ -167,8 +170,10 @@
CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
mDevice.executeShellCommand(AM_STACK_LIST, outputReceiver);
final String output = outputReceiver.getOutput();
+ final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
for (String line : output.split("\\n")) {
- if (line.contains(name)) {
+ Matcher matcher = activityPattern.matcher(line);
+ if (matcher.matches()) {
for (String word : line.split("\\s+")) {
if (word.startsWith(TASK_ID_PREFIX)) {
final String withColon = word.split("=")[1];
@@ -224,13 +229,13 @@
protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException {
setAccelerometerRotation(0);
- runCommandAndPrintOutput("settings put system user_rotation " + rotation);
+ setUserRotation(rotation);
}
private int getAccelerometerRotation() throws DeviceNotAvailableException {
final String rotation =
runCommandAndPrintOutput("settings get system accelerometer_rotation");
- return Integer.valueOf(rotation.trim());
+ return Integer.parseInt(rotation.trim());
}
private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
@@ -238,6 +243,25 @@
"settings put system accelerometer_rotation " + rotation);
}
+ private int getUserRotation() throws DeviceNotAvailableException {
+ final String rotation =
+ runCommandAndPrintOutput("settings get system user_rotation").trim();
+ if ("null".equals(rotation)) {
+ return -1;
+ }
+ return Integer.parseInt(rotation);
+ }
+
+ private void setUserRotation(int rotation) throws DeviceNotAvailableException {
+ if (rotation == -1) {
+ runCommandAndPrintOutput(
+ "settings delete system user_rotation");
+ } else {
+ runCommandAndPrintOutput(
+ "settings put system user_rotation " + rotation);
+ }
+ }
+
protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
final String output = mDevice.executeShellCommand(command);
CLog.logAndDisplay(LogLevel.INFO, command);
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/StateLogger.java b/hostsidetests/services/activitymanager/src/android/server/cts/StateLogger.java
index 1266102..335f26c 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/StateLogger.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/StateLogger.java
@@ -19,6 +19,7 @@
import com.android.tradefed.log.LogUtil.CLog;
import static com.android.ddmlib.Log.LogLevel.INFO;
+import static com.android.ddmlib.Log.LogLevel.ERROR;
/**
* Util class to perform simple state logging.
@@ -32,4 +33,8 @@
CLog.logAndDisplay(INFO, logText);
}
}
+
+ public static void logE(String logText) {
+ CLog.logAndDisplay(ERROR, logText);
+ }
}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
index ffa3b83..da346ac 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
@@ -32,6 +32,7 @@
import java.util.regex.Matcher;
import static android.server.cts.StateLogger.log;
+import static android.server.cts.StateLogger.logE;
class WindowManagerState {
private static final String DUMPSYS_WINDOWS_APPS = "dumpsys window apps";
@@ -105,13 +106,13 @@
}
if (mWindows.isEmpty()) {
- log("No Windows found...");
+ logE("No Windows found...");
}
if (mFocusedWindow == null) {
- log("No Focused Window...");
+ logE("No Focused Window...");
}
if (mFocusedApp == null) {
- log("No Focused App...");
+ logE("No Focused App...");
}
}
diff --git a/hostsidetests/systemui/src/android/host/systemui/ActiveTileServiceTest.java b/hostsidetests/systemui/src/android/host/systemui/ActiveTileServiceTest.java
index 5325b29..3f91c20 100644
--- a/hostsidetests/systemui/src/android/host/systemui/ActiveTileServiceTest.java
+++ b/hostsidetests/systemui/src/android/host/systemui/ActiveTileServiceTest.java
@@ -32,6 +32,7 @@
}
public void testNotListening() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
@@ -42,6 +43,7 @@
}
public void testRequestListening() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
@@ -52,6 +54,7 @@
}
public void testClick() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
diff --git a/hostsidetests/systemui/src/android/host/systemui/BaseTileServiceTest.java b/hostsidetests/systemui/src/android/host/systemui/BaseTileServiceTest.java
index 50d25e8..0ae861b 100644
--- a/hostsidetests/systemui/src/android/host/systemui/BaseTileServiceTest.java
+++ b/hostsidetests/systemui/src/android/host/systemui/BaseTileServiceTest.java
@@ -63,6 +63,7 @@
protected void tearDown() throws Exception {
super.tearDown();
+ if (!supportedHardware()) return;
collapse();
remTile();
// Try to wait for a onTileRemoved.
@@ -122,4 +123,10 @@
private void clearLogcat() throws DeviceNotAvailableException {
getDevice().executeAdbCommand("logcat", "-c");
}
+
+ protected boolean supportedHardware() throws DeviceNotAvailableException {
+ String features = getDevice().executeShellCommand("pm list features");
+ return !features.contains("android.hardware.type.television") &&
+ !features.contains("android.hardware.type.watch");
+ }
}
diff --git a/hostsidetests/systemui/src/android/host/systemui/TileServiceTest.java b/hostsidetests/systemui/src/android/host/systemui/TileServiceTest.java
index 3c92918..0c06935 100644
--- a/hostsidetests/systemui/src/android/host/systemui/TileServiceTest.java
+++ b/hostsidetests/systemui/src/android/host/systemui/TileServiceTest.java
@@ -33,6 +33,7 @@
}
public void testAddTile() throws Exception {
+ if (!supportedHardware()) return;
addTile();
// Verify that the service starts up and gets a onTileAdded callback.
assertTrue(waitFor("onCreate"));
@@ -41,6 +42,7 @@
}
public void testRemoveTile() throws Exception {
+ if (!supportedHardware()) return;
addTile();
// Verify that the service starts up and gets a onTileAdded callback.
assertTrue(waitFor("onCreate"));
@@ -52,6 +54,7 @@
}
public void testListeningNotifications() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
@@ -64,6 +67,7 @@
}
public void testListeningSettings() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
@@ -76,6 +80,7 @@
}
public void testCantAddDialog() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
@@ -93,6 +98,7 @@
}
public void testClick() throws Exception {
+ if (!supportedHardware()) return;
addTile();
// Wait for the tile to be added.
assertTrue(waitFor("onTileAdded"));
@@ -114,6 +120,7 @@
}
public void testClickAndShowDialog() throws Exception {
+ if (!supportedHardware()) return;
addTile();
assertTrue(waitFor("onDestroy"));
@@ -132,6 +139,7 @@
}
public void testStartActivity() throws Exception {
+ if (!supportedHardware()) return;
addTile();
// Wait for the tile to be added.
assertTrue(waitFor("onTileAdded"));
diff --git a/libs/deviceutil/src/android/cts/util/KeyEventUtil.java b/libs/deviceutil/src/android/cts/util/KeyEventUtil.java
new file mode 100644
index 0000000..4031adf
--- /dev/null
+++ b/libs/deviceutil/src/android/cts/util/KeyEventUtil.java
@@ -0,0 +1,202 @@
+/*
+ * 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.cts.util;
+
+import android.app.Instrumentation;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import java.lang.reflect.Field;
+
+/**
+ * Utility class to send KeyEvents to TextView bypassing the IME. The code is similar to functions
+ * in {@link Instrumentation} and {@link android.test.InstrumentationTestCase} classes. It uses
+ * {@link InputMethodManager#dispatchKeyEventFromInputMethod(View, KeyEvent)} to send the events.
+ * After sending the events waits for idle.
+ */
+public class KeyEventUtil {
+ private final Instrumentation mInstrumentation;
+
+ public KeyEventUtil(Instrumentation instrumentation) {
+ this.mInstrumentation = instrumentation;
+ }
+
+ /**
+ * Sends the key events corresponding to the text to the app being instrumented.
+ *
+ * @param targetView View to find the ViewRootImpl and dispatch.
+ * @param text The text to be sent. Null value returns immediately.
+ */
+ public final void sendString(final View targetView, final String text) {
+ if (text == null) {
+ return;
+ }
+
+ KeyEvent[] events = getKeyEvents(text);
+
+ if (events != null) {
+ for (int i = 0; i < events.length; i++) {
+ // We have to change the time of an event before injecting it because
+ // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
+ // time stamp and the system rejects too old events. Hence, it is
+ // possible for an event to become stale before it is injected if it
+ // takes too long to inject the preceding ones.
+ sendKey(targetView, KeyEvent.changeTimeRepeat(events[i], SystemClock.uptimeMillis(),
+ 0));
+ }
+ }
+ }
+
+ /**
+ * Sends a series of key events through instrumentation. For instance:
+ * sendKeys(view, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER).
+ *
+ * @param targetView View to find the ViewRootImpl and dispatch.
+ * @param keys The series of key codes.
+ */
+ public final void sendKeys(final View targetView, final int...keys) {
+ final int count = keys.length;
+
+ for (int i = 0; i < count; i++) {
+ try {
+ sendKeyDownUp(targetView, keys[i]);
+ } catch (SecurityException e) {
+ // Ignore security exceptions that are now thrown
+ // when trying to send to another app, to retain
+ // compatibility with existing tests.
+ }
+ }
+ }
+
+ /**
+ * Sends a series of key events through instrumentation. The sequence of keys is a string
+ * containing the key names as specified in KeyEvent, without the KEYCODE_ prefix. For
+ * instance: sendKeys(view, "DPAD_LEFT A B C DPAD_CENTER"). Each key can be repeated by using
+ * the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use the following:
+ * sendKeys(view, "2*DPAD_LEFT").
+ *
+ * @param targetView View to find the ViewRootImpl and dispatch.
+ * @param keysSequence The sequence of keys.
+ */
+ public final void sendKeys(final View targetView, final String keysSequence) {
+ final String[] keys = keysSequence.split(" ");
+ final int count = keys.length;
+
+ for (int i = 0; i < count; i++) {
+ String key = keys[i];
+ int repeater = key.indexOf('*');
+
+ int keyCount;
+ try {
+ keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater));
+ } catch (NumberFormatException e) {
+ Log.w("ActivityTestCase", "Invalid repeat count: " + key);
+ continue;
+ }
+
+ if (repeater != -1) {
+ key = key.substring(repeater + 1);
+ }
+
+ for (int j = 0; j < keyCount; j++) {
+ try {
+ final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key);
+ final int keyCode = keyCodeField.getInt(null);
+ try {
+ sendKeyDownUp(targetView, keyCode);
+ } catch (SecurityException e) {
+ // Ignore security exceptions that are now thrown
+ // when trying to send to another app, to retain
+ // compatibility with existing tests.
+ }
+ } catch (NoSuchFieldException e) {
+ Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
+ break;
+ } catch (IllegalAccessException e) {
+ Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sends an up and down key events.
+ *
+ * @param targetView View to find the ViewRootImpl and dispatch.
+ * @param key The integer keycode for the event to be send.
+ */
+ public final void sendKeyDownUp(final View targetView, final int key) {
+ sendKey(targetView, new KeyEvent(KeyEvent.ACTION_DOWN, key));
+ sendKey(targetView, new KeyEvent(KeyEvent.ACTION_UP, key));
+ }
+
+ /**
+ * Sends a key event.
+ *
+ * @param targetView View to find the ViewRootImpl and dispatch.
+ * @param event KeyEvent to be send.
+ */
+ public final void sendKey(final View targetView, final KeyEvent event) {
+ validateNotAppThread();
+
+ long downTime = event.getDownTime();
+ long eventTime = event.getEventTime();
+ int action = event.getAction();
+ int code = event.getKeyCode();
+ int repeatCount = event.getRepeatCount();
+ int metaState = event.getMetaState();
+ int deviceId = event.getDeviceId();
+ int scancode = event.getScanCode();
+ int source = event.getSource();
+ int flags = event.getFlags();
+ if (source == InputDevice.SOURCE_UNKNOWN) {
+ source = InputDevice.SOURCE_KEYBOARD;
+ }
+ if (eventTime == 0) {
+ eventTime = SystemClock.uptimeMillis();
+ }
+ if (downTime == 0) {
+ downTime = eventTime;
+ }
+
+ final KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount,
+ metaState, deviceId, scancode, flags, source);
+
+ InputMethodManager imm = targetView.getContext().getSystemService(InputMethodManager.class);
+ imm.dispatchKeyEventFromInputMethod(targetView, newEvent);
+ mInstrumentation.waitForIdleSync();
+ }
+
+ private KeyEvent[] getKeyEvents(final String text) {
+ KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ return keyCharacterMap.getEvents(text.toCharArray());
+ }
+
+ private void validateNotAppThread() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new RuntimeException(
+ "This method can not be called from the main application thread");
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index 7da7d37..df48a61 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -84,12 +84,11 @@
GestureDescription click = createClick(clickX, clickY);
mService.runOnServiceSync(() -> mService.doDispatchGesture(click, mCallback, null));
mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
- waitForMotionEvents(3);
+ waitForMotionEvents(2);
- assertEquals(3, mMotionEvents.size());
+ assertEquals(2, mMotionEvents.size());
MotionEvent clickDown = mMotionEvents.get(0);
- MotionEvent clickMove = mMotionEvents.get(1);
- MotionEvent clickUp = mMotionEvents.get(2);
+ MotionEvent clickUp = mMotionEvents.get(1);
assertEquals(MotionEvent.ACTION_DOWN, clickDown.getActionMasked());
assertEquals(0, clickDown.getActionIndex());
@@ -103,23 +102,13 @@
assertEquals((float) clickYInsideView, clickDown.getY());
assertEquals(clickDown.getDownTime(), clickDown.getEventTime());
- assertEquals(MotionEvent.ACTION_MOVE, clickMove.getActionMasked());
- assertEquals(clickDown.getDownTime(), clickMove.getDownTime());
- assertEquals(ViewConfiguration.getTapTimeout(),
- clickMove.getEventTime() - clickMove.getDownTime());
- assertEquals(0, clickMove.getActionIndex());
- assertEquals(1, clickMove.getPointerCount());
- assertEquals((float) clickXInsideView + 1, clickMove.getX());
- assertEquals((float) clickYInsideView, clickMove.getY());
- assertEquals(clickDown.getPointerId(0),
- clickMove.getPointerId(0));
-
assertEquals(MotionEvent.ACTION_UP, clickUp.getActionMasked());
assertEquals(clickDown.getDownTime(), clickUp.getDownTime());
- assertEquals(clickMove.getEventTime(), clickUp.getEventTime());
+ assertEquals(ViewConfiguration.getTapTimeout(),
+ clickUp.getEventTime() - clickUp.getDownTime());
assertTrue(clickDown.getEventTime() + ViewConfiguration.getLongPressTimeout()
> clickUp.getEventTime());
- assertEquals((float) clickXInsideView + 1, clickUp.getX());
+ assertEquals((float) clickXInsideView, clickUp.getX());
assertEquals((float) clickYInsideView, clickUp.getY());
}
@@ -133,27 +122,20 @@
mCallback.assertGestureCompletes(
ViewConfiguration.getLongPressTimeout() + GESTURE_COMPLETION_TIMEOUT);
- waitForMotionEvents(3);
+ waitForMotionEvents(2);
MotionEvent clickDown = mMotionEvents.get(0);
- MotionEvent clickMove = mMotionEvents.get(1);
- MotionEvent clickUp = mMotionEvents.get(2);
+ MotionEvent clickUp = mMotionEvents.get(1);
assertEquals(MotionEvent.ACTION_DOWN, clickDown.getActionMasked());
assertEquals((float) clickXInsideView, clickDown.getX());
assertEquals((float) clickYInsideView, clickDown.getY());
- assertEquals(MotionEvent.ACTION_MOVE, clickMove.getActionMasked());
- assertEquals(clickDown.getDownTime(), clickMove.getDownTime());
- assertEquals((float) clickXInsideView + 1, clickMove.getX());
- assertEquals((float) clickYInsideView, clickMove.getY());
- assertEquals(clickDown.getPointerId(0), clickMove.getPointerId(0));
-
assertEquals(MotionEvent.ACTION_UP, clickUp.getActionMasked());
assertTrue(clickDown.getEventTime() + ViewConfiguration.getLongPressTimeout()
<= clickUp.getEventTime());
assertEquals(clickDown.getDownTime(), clickUp.getDownTime());
- assertEquals((float) clickXInsideView + 1, clickUp.getX());
+ assertEquals((float) clickXInsideView, clickUp.getX());
assertEquals((float) clickYInsideView, clickUp.getY());
}
@@ -349,22 +331,17 @@
assertTrue("Failed to reset", result.get());
}
- assertEquals(3, mMotionEvents.size());
+ assertEquals(2, mMotionEvents.size());
MotionEvent clickDown = mMotionEvents.get(0);
- MotionEvent clickMove = mMotionEvents.get(1);
- MotionEvent clickUp = mMotionEvents.get(2);
+ MotionEvent clickUp = mMotionEvents.get(1);
assertEquals(MotionEvent.ACTION_DOWN, clickDown.getActionMasked());
assertEquals((float) clickXInsideView, clickDown.getX(), TOUCH_TOLERANCE);
assertEquals((float) clickYInsideView, clickDown.getY(), TOUCH_TOLERANCE);
assertEquals(clickDown.getDownTime(), clickDown.getEventTime());
- assertEquals(MotionEvent.ACTION_MOVE, clickMove.getActionMasked());
- assertEquals((float) clickXInsideView + 1, clickMove.getX(), TOUCH_TOLERANCE);
- assertEquals((float) clickYInsideView, clickMove.getY(), TOUCH_TOLERANCE);
-
assertEquals(MotionEvent.ACTION_UP, clickUp.getActionMasked());
- assertEquals((float) clickXInsideView + 1, clickUp.getX(), TOUCH_TOLERANCE);
+ assertEquals((float) clickXInsideView, clickUp.getX(), TOUCH_TOLERANCE);
assertEquals((float) clickYInsideView, clickUp.getY(), TOUCH_TOLERANCE);
}
@@ -450,7 +427,6 @@
private GestureDescription createClick(int x, int y) {
Path clickPath = new Path();
clickPath.moveTo(x, y);
- clickPath.lineTo(x + 1, y);
GestureDescription.StrokeDescription clickStroke =
new GestureDescription.StrokeDescription(clickPath, 0, ViewConfiguration.getTapTimeout());
GestureDescription.Builder clickBuilder = new GestureDescription.Builder();
@@ -461,7 +437,6 @@
private GestureDescription createLongClick(int x, int y) {
Path clickPath = new Path();
clickPath.moveTo(x, y);
- clickPath.lineTo(x + 1, y);
int longPressTime = ViewConfiguration.getLongPressTimeout();
GestureDescription.StrokeDescription longClickStroke =
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
index b3fa9d2..234f66a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
@@ -93,13 +93,11 @@
}
}
- public void testCreateStroke_pathWithZeroLength_shouldThrow() {
- Path zeroLengthPath = new Path();
- zeroLengthPath.moveTo(0, 0);
- zeroLengthPath.lineTo(0, 0);
+ public void testCreateStroke_withEmptyPath_shouldThrow() {
+ Path emptyPath = new Path();
try {
- new GestureDescription.StrokeDescription(zeroLengthPath, 0, NOMINAL_PATH_DURATION);
- fail("Missing exception for stroke with path of zero length.");
+ new GestureDescription.StrokeDescription(emptyPath, 0, NOMINAL_PATH_DURATION);
+ fail("Missing exception for empty path.");
} catch (IllegalArgumentException e) {
}
}
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index ea094ab..cc06ead 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -124,6 +124,9 @@
<meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
</service>
+ <service android:name="android.app.stubs.LocalForegroundService">
+ </service>
+
<service android:name="android.app.stubs.LocalGrantedService"
android:permission="android.app.stubs.permission.TEST_GRANTED">
<intent-filter>
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
new file mode 100644
index 0000000..119b9f8
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -0,0 +1,90 @@
+/*
+ * 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.app.stubs;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.cts.util.IBinderParcelable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.app.stubs.R;
+
+public class LocalForegroundService extends LocalService {
+
+ private static final String TAG = "LocalForegroundService";
+ private static final String EXTRA_COMMAND = "LocalForegroundService.command";
+
+ public static final int COMMAND_START_FOREGROUND = 1;
+ public static final int COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION = 2;
+ public static final int COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION = 3;
+ public static final int COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION = 4;
+ public static final int COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION_USING_FLAGS = 5;
+
+ private int mNotificationId = 0;
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+
+ Context context = getApplicationContext();
+ int command = intent.getIntExtra(EXTRA_COMMAND, -1);
+
+ switch (command) {
+ case COMMAND_START_FOREGROUND:
+ mNotificationId ++;
+ Log.d(TAG, "Starting foreground using notification " + mNotificationId);
+ Notification notification = new Notification.Builder(context)
+ .setContentTitle(getNotificationTitle(mNotificationId))
+ .setSmallIcon(R.drawable.black)
+ .build();
+ startForeground(mNotificationId, notification);
+ break;
+ case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION:
+ Log.d(TAG, "Stopping foreground removing notification");
+ stopForeground(true);
+ break;
+ case COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION:
+ Log.d(TAG, "Stopping foreground without removing notification");
+ stopForeground(false);
+ break;
+ case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION_USING_FLAGS:
+ Log.d(TAG, "Stopping foreground removing notification using flags");
+ stopForeground(Service.STOP_FOREGROUND_REMOVE | Service.STOP_FOREGROUND_DETACH);
+ break;
+ case COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION:
+ Log.d(TAG, "Detaching foreground service notification");
+ stopForeground(Service.STOP_FOREGROUND_DETACH);
+ break;
+ default:
+ Log.e(TAG, "Unknown command: " + command);
+ }
+ }
+
+ public static Bundle newCommand(IBinder stateReceiver, int command) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(LocalService.REPORT_OBJ_NAME, new IBinderParcelable(stateReceiver));
+ bundle.putInt(EXTRA_COMMAND, command);
+ return bundle;
+ }
+
+ public static String getNotificationTitle(int id) {
+ return "I AM FOREGROOT #" + id;
+ }
+}
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 4842bb1..1d6d23d 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,8 +16,11 @@
package android.app.cts;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.stubs.ActivityTestsBase;
import android.app.stubs.LocalDeniedService;
+import android.app.stubs.LocalForegroundService;
import android.app.stubs.LocalGrantedService;
import android.app.stubs.LocalService;
import android.content.ComponentName;
@@ -30,15 +33,20 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.app.stubs.R;
public class ServiceTest extends ActivityTestsBase {
+ private static final String TAG = "ServiceTest";
private static final int STATE_START_1 = 0;
private static final int STATE_START_2 = 1;
- private static final int STATE_UNBIND = 2;
- private static final int STATE_DESTROY = 3;
- private static final int STATE_REBIND = 4;
- private static final int STATE_UNBIND_ONLY = 5;
+ private static final int STATE_START_3 = 2;
+ private static final int STATE_UNBIND = 3;
+ private static final int STATE_DESTROY = 4;
+ private static final int STATE_REBIND = 5;
+ private static final int STATE_UNBIND_ONLY = 6;
private static final int DELAY = 5000;
private static final
String EXIST_CONN_TO_RECEIVE_SERVICE = "existing connection to receive service";
@@ -47,6 +55,7 @@
private Context mContext;
private Intent mLocalService;
private Intent mLocalDeniedService;
+ private Intent mLocalForegroundService;
private Intent mLocalGrantedService;
private Intent mLocalService_ApplicationHasPermission;
private Intent mLocalService_ApplicationDoesNotHavePermission;
@@ -157,6 +166,82 @@
waitForResultOrThrow(DELAY, "service to be destroyed");
}
+ private NotificationManager getNotificationManager() {
+ NotificationManager notificationManager =
+ (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ return notificationManager;
+ }
+
+ private void sendNotififcation(int id, String title) {
+ Notification notification = new Notification.Builder(getContext())
+ .setContentTitle(title)
+ .setSmallIcon(R.drawable.black)
+ .build();
+ getNotificationManager().notify(id, notification);
+ }
+
+ private void cancelNotification(int id) {
+ getNotificationManager().cancel(id);
+ }
+
+ private void assertNotification(int id, String expectedTitle) {
+ String packageName = getContext().getPackageName();
+ String errorMessage = null;
+ for (int i = 1; i<=2; i++) {
+ errorMessage = null;
+ StatusBarNotification[] sbns = getNotificationManager().getActiveNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getId() == id && sbn.getPackageName().equals(packageName)) {
+ String actualTitle =
+ sbn.getNotification().extras.getString(Notification.EXTRA_TITLE);
+ if (expectedTitle.equals(actualTitle)) {
+ return;
+ }
+ // It's possible the notification hasn't been updated yet, so save the error
+ // message to only fail after retrying.
+ errorMessage = String.format("Wrong title for notification #%d: "
+ + "expected '%s', actual '%s'", id, expectedTitle, actualTitle);
+ Log.w(TAG, errorMessage);
+ }
+ }
+ // Notification might not be rendered yet, wait and try again...
+ try {
+ Thread.sleep(DELAY);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (errorMessage != null) {
+ fail(errorMessage);
+ }
+ fail("No notification with id " + id + " for package " + packageName);
+ }
+
+ private void assertNoNotification(int id) {
+ String packageName = getContext().getPackageName();
+ StatusBarNotification found = null;
+ for (int i = 1; i<=2; i++) {
+ found = null;
+ StatusBarNotification[] sbns = getNotificationManager().getActiveNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getId() == id && sbn.getPackageName().equals(packageName)) {
+ found = sbn;
+ break;
+ }
+ }
+ if (found != null) {
+ // Notification might not be canceled yet, wait and try again...
+ try {
+ Thread.sleep(DELAY);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ assertNull("Found notification with id " + id + " for package " + packageName + ": "
+ + found, found);
+ }
+
/**
* test the service lifecycle, a service can be used in two ways:
* 1 It can be started and allowed to run until someone stops it or it stops itself.
@@ -297,6 +382,7 @@
super.setUp();
mContext = getContext();
mLocalService = new Intent(mContext, LocalService.class);
+ mLocalForegroundService = new Intent(mContext, LocalForegroundService.class);
mLocalDeniedService = new Intent(mContext, LocalDeniedService.class);
mLocalGrantedService = new Intent(mContext, LocalGrantedService.class);
mLocalService_ApplicationHasPermission = new Intent(
@@ -327,6 +413,13 @@
finishBad("onStart() the first time on an object when it "
+ "should have been the second time");
}
+ } else if (mExpectedServiceState == STATE_START_3) {
+ if (count == 3) {
+ finishGood();
+ } else {
+ finishBad("onStart() the first time on an object when it "
+ + "should have been the third time");
+ }
} else {
finishBad("onStart() was called when not expected (state="
+ mExpectedServiceState + ")");
@@ -371,6 +464,7 @@
protected void tearDown() throws Exception {
super.tearDown();
mContext.stopService(mLocalService);
+ mContext.stopService(mLocalForegroundService);
mContext.stopService(mLocalGrantedService);
mContext.stopService(mLocalService_ApplicationHasPermission);
}
@@ -388,6 +482,155 @@
bindExpectResult(mLocalService);
}
+ private void startForegroundService(int command) {
+ mContext.startService(new Intent(mLocalForegroundService).putExtras(LocalForegroundService
+ .newCommand(mStateReceiver, command)));
+ }
+
+ @MediumTest
+ public void testForegroundService_dontRemoveNotificationOnStop() throws Exception {
+ boolean success = false;
+ try {
+ // Start service as foreground - it should show notification #1
+ mExpectedServiceState = STATE_START_1;
+ startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start first time");
+ assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+ // Stop foreground without removing notification - it should still show notification #1
+ mExpectedServiceState = STATE_START_2;
+ startForegroundService(
+ LocalForegroundService.COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION);
+ waitForResultOrThrow(DELAY, "service to stop foreground");
+ assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+ // Sends another notification reusing the same notification id.
+ String newTitle = "YODA I AM";
+ sendNotififcation(1, newTitle);
+ assertNotification(1, newTitle);
+
+ // Start service as foreground again - it should kill notification #1 and show #2
+ mExpectedServiceState = STATE_START_3;
+ startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start foreground 2nd time");
+ assertNoNotification(1);
+ assertNotification(2, LocalForegroundService.getNotificationTitle(2));
+
+ success = true;
+ } finally {
+ if (!success) {
+ mContext.stopService(mLocalForegroundService);
+ }
+ }
+ mExpectedServiceState = STATE_DESTROY;
+ mContext.stopService(mLocalForegroundService);
+ waitForResultOrThrow(DELAY, "service to be destroyed");
+ assertNoNotification(1);
+ assertNoNotification(2);
+ }
+
+ @MediumTest
+ public void testForegroundService_removeNotificationOnStop() throws Exception {
+ testForegroundServiceRemoveNotificationOnStop(false);
+ }
+
+ @MediumTest
+ public void testForegroundService_removeNotificationOnStopUsingFlags() throws Exception {
+ testForegroundServiceRemoveNotificationOnStop(true);
+ }
+
+ private void testForegroundServiceRemoveNotificationOnStop(boolean usingFlags)
+ throws Exception {
+ boolean success = false;
+ try {
+ // Start service as foreground - it should show notification #1
+ mExpectedServiceState = STATE_START_1;
+ startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start first time");
+ assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+ // Stop foreground removing notification
+ mExpectedServiceState = STATE_START_2;
+ if (usingFlags) {
+ startForegroundService(LocalForegroundService
+ .COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION_USING_FLAGS);
+ } else {
+ startForegroundService(LocalForegroundService
+ .COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION);
+ }
+ waitForResultOrThrow(DELAY, "service to stop foreground");
+ assertNoNotification(1);
+
+ // Start service as foreground again - it should show notification #2
+ mExpectedServiceState = STATE_START_3;
+ startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start as foreground 2nd time");
+ assertNotification(2, LocalForegroundService.getNotificationTitle(2));
+
+ success = true;
+ } finally {
+ if (!success) {
+ mContext.stopService(mLocalForegroundService);
+ }
+ }
+ mExpectedServiceState = STATE_DESTROY;
+ mContext.stopService(mLocalForegroundService);
+ waitForResultOrThrow(DELAY, "service to be destroyed");
+ assertNoNotification(1);
+ assertNoNotification(2);
+ }
+
+ @MediumTest
+ public void testForegroundService_detachNotificationOnStop() throws Exception {
+ String newTitle = null;
+ boolean success = false;
+ try {
+
+ // Start service as foreground - it should show notification #1
+ mExpectedServiceState = STATE_START_1;
+ startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start first time");
+ assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+ // Detaching notification
+ mExpectedServiceState = STATE_START_2;
+ startForegroundService(
+ LocalForegroundService.COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION);
+ waitForResultOrThrow(DELAY, "service to stop foreground");
+ assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+ // Sends another notification reusing the same notification id.
+ newTitle = "YODA I AM";
+ sendNotififcation(1, newTitle);
+ assertNotification(1, newTitle);
+
+ // Start service as foreground again - it should show notification #2..
+ mExpectedServiceState = STATE_START_3;
+ startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start as foreground 2nd time");
+ assertNotification(2, LocalForegroundService.getNotificationTitle(2));
+ //...but keeping notification #1
+ assertNotification(1, newTitle);
+
+ success = true;
+ } finally {
+ if (!success) {
+ mContext.stopService(mLocalForegroundService);
+ }
+ }
+ mExpectedServiceState = STATE_DESTROY;
+ mContext.stopService(mLocalForegroundService);
+ waitForResultOrThrow(DELAY, "service to be destroyed");
+ if (newTitle == null) {
+ assertNoNotification(1);
+ } else {
+ assertNotification(1, newTitle);
+ cancelNotification(1);
+ assertNoNotification(1);
+ }
+ assertNoNotification(2);
+ }
+
@MediumTest
public void testLocalBindAction() throws Exception {
bindExpectResult(new Intent(
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index a5aa2ac..8d0b084 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -779,6 +779,7 @@
"NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG is supported",
!supportZslNoiseReductionMode);
}
+ counter++;
}
}
@@ -930,6 +931,7 @@
"All necessary depth fields defined, but DEPTH_OUTPUT capability is not listed",
!hasFields);
}
+ counter++;
}
}
@@ -1137,9 +1139,8 @@
invalidSize.toString()),
config.isOutputSupportedFor(surf));
- counter++;
}
-
+ counter++;
} // mCharacteristics
}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index d7ec5f1..c32be5c 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -35,6 +35,7 @@
import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.media.AudioManager;
@@ -52,6 +53,7 @@
private final static long TIME_TO_PLAY = 2000;
private final static String APPOPS_OP_STR = "android:write_settings";
private AudioManager mAudioManager;
+ private NotificationManager mNm;
private boolean mHasVibrator;
private boolean mUseFixedVolume;
private boolean mIsTelevision;
@@ -64,6 +66,7 @@
Utils.enableAppOps(mContext.getPackageName(), APPOPS_OP_STR, getInstrumentation());
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mHasVibrator = (vibrator != null) && vibrator.hasVibrator();
mUseFixedVolume = mContext.getResources().getBoolean(
Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
@@ -389,26 +392,28 @@
}
assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
- // Apps without policy access cannot change vibrate -> silent.
- Utils.toggleNotificationPolicyAccess(
- mContext.getPackageName(), getInstrumentation(), true);
- mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
- assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
- Utils.toggleNotificationPolicyAccess(
- mContext.getPackageName(), getInstrumentation(), false);
+ if (mHasVibrator) {
+ // Apps without policy access cannot change vibrate -> silent.
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+ assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
- try {
- mAudioManager.setRingerMode(RINGER_MODE_SILENT);
- fail("Apps without notification policy access cannot change ringer mode");
- } catch (SecurityException e) {
+ try {
+ mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+ fail("Apps without notification policy access cannot change ringer mode");
+ } catch (SecurityException e) {
+ }
+
+ // Apps without policy access can change vibrate -> normal and vice versa.
+ assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
+ mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+ assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+ mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+ assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
}
-
- // Apps without policy access can change vibrate -> normal and vice versa.
- assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
- mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
- assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
- mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
- assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
} finally {
Utils.toggleNotificationPolicyAccess(
mContext.getPackageName(), getInstrumentation(), false);
@@ -618,18 +623,94 @@
}
}
- public void testMute() throws Exception {
+ public void testMuteDndAffectedStreams() throws Exception {
if (mUseFixedVolume) {
return;
}
+ int[] streams = { AudioManager.STREAM_RING };
+ try {
+ // Mute streams
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ // Verify streams cannot be unmuted without policy access.
+ for (int i = 0; i < streams.length; i++) {
+ try {
+ mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_UNMUTE, 0);
+ assertEquals("Apps without Notification policy access can't change ringer mode",
+ RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+ } catch (SecurityException e) {
+ }
+ try {
+ mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_TOGGLE_MUTE,
+ 0);
+ assertEquals("Apps without Notification policy access can't change ringer mode",
+ RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+ } catch (SecurityException e) {
+ }
+
+ try {
+ mAudioManager.setStreamMute(streams[i], false);
+ assertEquals("Apps without Notification policy access can't change ringer mode",
+ RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+ } catch (SecurityException e) {
+ }
+ }
+
+ // This ensures we're out of vibrate or silent modes.
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+ for (int i = 0; i < streams.length; i++) {
+ // ensure each stream is on and turned up.
+ mAudioManager.setStreamVolume(streams[i],
+ mAudioManager.getStreamMaxVolume(streams[i]),
+ 0);
+
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ try {
+ mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_MUTE, 0);
+ assertEquals("Apps without Notification policy access can't change ringer mode",
+ RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+ } catch (SecurityException e) {
+ }
+ try {
+ mAudioManager.adjustStreamVolume(
+ streams[i], AudioManager.ADJUST_TOGGLE_MUTE, 0);
+ assertEquals("Apps without Notification policy access can't change ringer mode",
+ RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+ } catch (SecurityException e) {
+ }
+
+ try {
+ mAudioManager.setStreamMute(streams[i], true);
+ assertEquals("Apps without Notification policy access can't change ringer mode",
+ RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+ } catch (SecurityException e) {
+ }
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ testStreamMuting(streams[i]);
+ }
+ } finally {
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ }
+ }
+
+ public void testMuteDndUnaffectedStreams() throws Exception {
+ if (mUseFixedVolume) {
+ return;
+ }
int[] streams = {
AudioManager.STREAM_VOICE_CALL,
AudioManager.STREAM_MUSIC,
- AudioManager.STREAM_RING,
- AudioManager.STREAM_ALARM,
- AudioManager.STREAM_NOTIFICATION,
- AudioManager.STREAM_SYSTEM };
+ AudioManager.STREAM_ALARM
+ };
try {
int muteAffectedStreams = System.getInt(mContext.getContentResolver(),
@@ -664,34 +745,7 @@
mAudioManager.isStreamMute(streams[i]));
continue;
}
- mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_MUTE, 0);
- assertTrue("Muting stream " + streams[i] + " failed.",
- mAudioManager.isStreamMute(streams[i]));
-
- mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_UNMUTE, 0);
- assertFalse("Unmuting stream " + streams[i] + " failed.",
- mAudioManager.isStreamMute(streams[i]));
-
- mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_TOGGLE_MUTE, 0);
- assertTrue("Toggling mute on stream " + streams[i] + " failed.",
- mAudioManager.isStreamMute(streams[i]));
-
- mAudioManager.adjustStreamVolume(streams[i], AudioManager.ADJUST_TOGGLE_MUTE, 0);
- assertFalse("Toggling mute on stream " + streams[i] + " failed.",
- mAudioManager.isStreamMute(streams[i]));
-
- mAudioManager.setStreamMute(streams[i], true);
- assertTrue("Muting stream " + streams[i] + " using setStreamMute failed",
- mAudioManager.isStreamMute(streams[i]));
-
- // mute it three more times to verify the ref counting is gone.
- mAudioManager.setStreamMute(streams[i], true);
- mAudioManager.setStreamMute(streams[i], true);
- mAudioManager.setStreamMute(streams[i], true);
-
- mAudioManager.setStreamMute(streams[i], false);
- assertFalse("Unmuting stream " + streams[i] + " using setStreamMute failed.",
- mAudioManager.isStreamMute(streams[i]));
+ testStreamMuting(streams[i]);
}
} finally {
Utils.toggleNotificationPolicyAccess(
@@ -699,6 +753,37 @@
}
}
+ private void testStreamMuting(int stream) {
+ mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+ assertTrue("Muting stream " + stream + " failed.",
+ mAudioManager.isStreamMute(stream));
+
+ mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+ assertFalse("Unmuting stream " + stream + " failed.",
+ mAudioManager.isStreamMute(stream));
+
+ mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+ assertTrue("Toggling mute on stream " + stream + " failed.",
+ mAudioManager.isStreamMute(stream));
+
+ mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+ assertFalse("Toggling mute on stream " + stream + " failed.",
+ mAudioManager.isStreamMute(stream));
+
+ mAudioManager.setStreamMute(stream, true);
+ assertTrue("Muting stream " + stream + " using setStreamMute failed",
+ mAudioManager.isStreamMute(stream));
+
+ // mute it three more times to verify the ref counting is gone.
+ mAudioManager.setStreamMute(stream, true);
+ mAudioManager.setStreamMute(stream, true);
+ mAudioManager.setStreamMute(stream, true);
+
+ mAudioManager.setStreamMute(stream, false);
+ assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
+ mAudioManager.isStreamMute(stream));
+ }
+
public void testSetInvalidRingerMode() {
int ringerMode = mAudioManager.getRingerMode();
mAudioManager.setRingerMode(-1337);
@@ -708,6 +793,110 @@
assertEquals(ringerMode, mAudioManager.getRingerMode());
}
+ public void testAdjustVolumeInTotalSilenceMode() throws Exception {
+ if (mUseFixedVolume || mIsTelevision) {
+ return;
+ }
+ try {
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+
+ int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.adjustStreamVolume(
+ AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
+ assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+
+ } finally {
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ }
+ }
+
+ public void testAdjustVolumeInAlarmsOnlyMode() throws Exception {
+ if (mUseFixedVolume || mIsTelevision) {
+ return;
+ }
+ try {
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
+
+ int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.adjustStreamVolume(
+ AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
+ int volumeDelta =
+ getVolumeDelta(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+ assertEquals(musicVolume + volumeDelta,
+ mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+
+ } finally {
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ }
+ }
+
+ public void testSetStreamVolumeInTotalSilenceMode() throws Exception {
+ if (mUseFixedVolume || mIsTelevision) {
+ return;
+ }
+ try {
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+
+ int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 7, 0);
+ assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
+ assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+ } finally {
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ }
+ }
+
+ public void testSetStreamVolumeInAlarmsOnlyMode() throws Exception {
+ if (mUseFixedVolume || mIsTelevision) {
+ return;
+ }
+ try {
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), true);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
+
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);
+ assertEquals(3, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+
+ mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
+ assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+ } finally {
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ Utils.toggleNotificationPolicyAccess(
+ mContext.getPackageName(), getInstrumentation(), false);
+ }
+ }
+
+ private void setInterruptionFilter(int filter) throws Exception {
+ mNm.setInterruptionFilter(filter);
+ for (int i = 0; i < 5; i++) {
+ if (mNm.getCurrentInterruptionFilter() == filter) {
+ break;
+ }
+ Thread.sleep(1000);
+ }
+ }
+
private int getVolumeDelta(int volume) {
return 1;
}
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index 4112466..231db97 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -38,9 +38,16 @@
import android.test.AndroidTestCase;
import android.util.Log;
import android.os.SystemProperties;
+import android.system.Os;
+import android.system.OsConstants;
import com.android.internal.telephony.PhoneConstants;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -57,7 +64,15 @@
public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
+
private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
+ private static final String TEST_HOST = "connectivitycheck.gstatic.com";
+ private static final int SOCKET_TIMEOUT_MS = 2000;
+ private static final int HTTP_PORT = 80;
+ private static final String HTTP_REQUEST =
+ "GET /generate_204 HTTP/1.0\r\n" +
+ "Host: " + TEST_HOST + "\r\n" +
+ "Connection: keep-alive\r\n\r\n";
// Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
private static final String NETWORK_CALLBACK_ACTION =
@@ -249,6 +264,12 @@
mCm.getBackgroundDataSetting();
}
+ private NetworkRequest makeWifiNetworkRequest() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ }
+
/**
* Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
* see if we get a callback for the TRANSPORT_WIFI transport type being available.
@@ -265,16 +286,14 @@
}
// We will register for a WIFI network being available or lost.
- final NetworkRequest request = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .build();
final TestNetworkCallback callback = new TestNetworkCallback();
- mCm.registerNetworkCallback(request, callback);
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultTrackingCallback);
final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+ Network wifiNetwork = null;
try {
// Make sure WiFi is connected to an access point to start with.
@@ -285,10 +304,11 @@
// Now we should expect to get a network callback about availability of the wifi
// network even if it was already connected as a state-based action when the callback
// is registered.
- assertTrue("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
- callback.waitForAvailable());
+ wifiNetwork = callback.waitForAvailable();
+ assertNotNull("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
+ wifiNetwork);
- assertTrue("Did not receive NetworkCallback.onAvailable for any default network",
+ assertNotNull("Did not receive NetworkCallback.onAvailable for any default network",
defaultTrackingCallback.waitForAvailable());
} catch (InterruptedException e) {
fail("Broadcast receiver or NetworkCallback wait was interrupted.");
@@ -298,7 +318,7 @@
// Return WiFi to its original enabled/disabled state.
if (!previousWifiEnabledState) {
- disconnectFromWifi();
+ disconnectFromWifi(wifiNetwork);
}
}
}
@@ -329,10 +349,7 @@
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
// We will register for a WIFI network being available or lost.
- NetworkRequest request = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .build();
- mCm.registerNetworkCallback(request, pendingIntent);
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
@@ -356,7 +373,7 @@
// Return WiFi to its original enabled/disabled state.
if (!previousWifiEnabledState) {
- disconnectFromWifi();
+ disconnectFromWifi(null);
}
}
}
@@ -370,8 +387,10 @@
// We will toggle the state of wifi to generate a connectivity change.
final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+
if (previousWifiEnabledState) {
- disconnectFromWifi();
+ Network wifiNetwork = getWifiNetwork();
+ disconnectFromWifi(wifiNetwork);
} else {
connectToWifi();
}
@@ -387,12 +406,16 @@
if (previousWifiEnabledState) {
connectToWifi();
} else {
- disconnectFromWifi();
+ disconnectFromWifi(null);
}
}
/** Enable WiFi and wait for it to become connected to a network. */
- private void connectToWifi() {
+ private Network connectToWifi() {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ Network wifiNetwork = null;
+
ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
IntentFilter filter = new IntentFilter();
@@ -402,36 +425,94 @@
boolean connected = false;
try {
assertTrue(mWifiManager.setWifiEnabled(true));
+ // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
+ wifiNetwork = callback.waitForAvailable();
+ assertNotNull(wifiNetwork);
connected = receiver.waitForState();
} catch (InterruptedException ex) {
fail("connectToWifi was interrupted");
} finally {
+ mCm.unregisterNetworkCallback(callback);
mContext.unregisterReceiver(receiver);
}
assertTrue("Wifi must be configured to connect to an access point for this test.",
connected);
+ return wifiNetwork;
+ }
+
+ private Socket getBoundSocket(Network network, String host, int port) throws IOException {
+ InetSocketAddress addr = new InetSocketAddress(host, port);
+ Socket s = network.getSocketFactory().createSocket();
+ try {
+ s.setSoTimeout(SOCKET_TIMEOUT_MS);
+ s.connect(addr, SOCKET_TIMEOUT_MS);
+ } catch (IOException e) {
+ s.close();
+ throw e;
+ }
+ return s;
+ }
+
+ private void testHttpRequest(Socket s) throws IOException {
+ OutputStream out = s.getOutputStream();
+ InputStream in = s.getInputStream();
+
+ final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+ byte[] responseBytes = new byte[4096];
+ out.write(requestBytes);
+ in.read(responseBytes);
+ assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
}
/** Disable WiFi and wait for it to become disconnected from the network. */
- private void disconnectFromWifi() {
+ private void disconnectFromWifi(Network wifiNetworkToCheck) {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ Network lostWifiNetwork = null;
+
ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(receiver, filter);
+ // Assert that we can establish a TCP connection on wifi.
+ Socket wifiBoundSocket = null;
+ if (wifiNetworkToCheck != null) {
+ try {
+ wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
+ testHttpRequest(wifiBoundSocket);
+ } catch (IOException e) {
+ fail("HTTP request before wifi disconnected failed with: " + e);
+ }
+ }
+
boolean disconnected = false;
try {
assertTrue(mWifiManager.setWifiEnabled(false));
+ // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
+ lostWifiNetwork = callback.waitForLost();
+ assertNotNull(lostWifiNetwork);
disconnected = receiver.waitForState();
} catch (InterruptedException ex) {
fail("disconnectFromWifi was interrupted");
} finally {
+ mCm.unregisterNetworkCallback(callback);
mContext.unregisterReceiver(receiver);
}
assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
+
+ // Check that the socket is closed when wifi disconnects.
+ if (wifiBoundSocket != null) {
+ try {
+ testHttpRequest(wifiBoundSocket);
+ fail("HTTP request should not succeed after wifi disconnects");
+ } catch (IOException expected) {
+ assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
+ }
+ }
}
/**
@@ -498,15 +579,48 @@
*/
private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
+ private final CountDownLatch mLostLatch = new CountDownLatch(1);
- public boolean waitForAvailable() throws InterruptedException {
- return mAvailableLatch.await(30, TimeUnit.SECONDS);
+ public Network currentNetwork;
+ public Network lastLostNetwork;
+
+ public Network waitForAvailable() throws InterruptedException {
+ return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
+ }
+
+ public Network waitForLost() throws InterruptedException {
+ return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
}
@Override
public void onAvailable(Network network) {
+ currentNetwork = network;
mAvailableLatch.countDown();
}
+
+ @Override
+ public void onLost(Network network) {
+ lastLostNetwork = network;
+ if (network.equals(currentNetwork)) {
+ currentNetwork = null;
+ }
+ mLostLatch.countDown();
+ }
+ }
+
+ private Network getWifiNetwork() {
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ Network network = null;
+ try {
+ network = callback.waitForAvailable();
+ } catch (InterruptedException e) {
+ fail("NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
+ return network;
}
/** Verify restricted networks cannot be requested. */
@@ -523,8 +637,6 @@
try {
mCm.requestNetwork(request, callback);
fail("No exception thrown when restricted network requested.");
- } catch (SecurityException e) {
- // Expected.
- }
+ } catch (SecurityException expected) {}
}
}
diff --git a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
index dbc8ede..be380c7 100644
--- a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
+++ b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
@@ -259,13 +259,14 @@
#define FIXED_ADDR 0x45678000
#define TIMEOUT 60 /* seconds */
-struct iovec *iovs = NULL;
-int fd[2];
+static struct iovec *iovs = NULL;
+static int fd[2];
+static void *overflow_addr;
void* func_map(void*)
{
- munmap((void*)(FIXED_ADDR), PAGE_SIZE);
- mmap((void*)(FIXED_ADDR), PAGE_SIZE, PROT_READ | PROT_WRITE,
+ munmap(overflow_addr, PAGE_SIZE);
+ overflow_addr = mmap(overflow_addr, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return NULL;
}
@@ -306,8 +307,10 @@
iovs[OVERFLOW_BUF].iov_base = bufs[OVERFLOW_BUF];
iovs[OVERFLOW_BUF].iov_len = IOV_LEN;
- bufs[OVERFLOW_BUF] = mmap((void*)(FIXED_ADDR), PAGE_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+ overflow_addr = mmap((void *) FIXED_ADDR, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ bufs[OVERFLOW_BUF] = overflow_addr;
if (bufs[OVERFLOW_BUF] == MAP_FAILED) {
ALOGE("mmap fixed addr failed:%s", strerror(errno));
goto __close_pipe;
@@ -338,6 +341,12 @@
pthread_join(thr_map, NULL);
pthread_join(thr_readv, NULL);
+ bufs[OVERFLOW_BUF] = overflow_addr;
+ if (bufs[OVERFLOW_BUF] == MAP_FAILED) {
+ ALOGE("mmap fixed addr failed:%s", strerror(errno));
+ goto __free_bufs;
+ }
+
clock_gettime(CLOCK_MONOTONIC, &ts);
if ((ts.tv_sec - time) > TIMEOUT) {
ret = true;
diff --git a/tests/tests/view/src/android/view/cts/GLSurfaceViewCtsActivity.java b/tests/tests/view/src/android/view/cts/GLSurfaceViewCtsActivity.java
index bc916a7..bff6511 100644
--- a/tests/tests/view/src/android/view/cts/GLSurfaceViewCtsActivity.java
+++ b/tests/tests/view/src/android/view/cts/GLSurfaceViewCtsActivity.java
@@ -83,6 +83,17 @@
mGlVersion = 0;
}
+ private static boolean mFixedSizeSet = false;
+ private static int mFixedWidth, mFixedHeight;
+ public static void setFixedSize(int width, int height) {
+ mFixedSizeSet = true;
+ mFixedWidth = width;
+ mFixedHeight = height;
+ }
+ public static void resetFixedSize() {
+ mFixedSizeSet = false;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -101,6 +112,9 @@
if (mRenderModeSet) {
mView.setRenderMode(mRenderMode);
}
+ if (mFixedSizeSet) {
+ mView.getHolder().setFixedSize(mFixedWidth, mFixedHeight);
+ }
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(mView);
}
diff --git a/tests/tests/view/src/android/view/cts/MockView.java b/tests/tests/view/src/android/view/cts/MockView.java
index 3e140c2..f07e991 100644
--- a/tests/tests/view/src/android/view/cts/MockView.java
+++ b/tests/tests/view/src/android/view/cts/MockView.java
@@ -71,6 +71,10 @@
private boolean mCalledOnKeyPreIme = false;
private boolean mCalledGetPointerIcon = false;
private boolean mCalledOnVisibilityAggregated = false;
+ private boolean mCalledDispatchStartTemporaryDetach = false;
+ private boolean mCalledDispatchFinishTemporaryDetach = false;
+ private boolean mCalledOnStartTemporaryDetach = false;
+ private boolean mCalledOnFinishTemporaryDetach = false;
private int mOldWidth = -1;
private int mOldHeight = -1;
@@ -634,6 +638,46 @@
return mLastAggregatedVisibility;
}
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ mCalledDispatchStartTemporaryDetach = true;
+ }
+
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ super.dispatchFinishTemporaryDetach();
+ mCalledDispatchFinishTemporaryDetach = true;
+ }
+
+ @Override
+ public void onStartTemporaryDetach() {
+ super.onStartTemporaryDetach();
+ mCalledOnStartTemporaryDetach = true;
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ super.onFinishTemporaryDetach();
+ mCalledOnFinishTemporaryDetach = true;
+ }
+
+ public boolean hasCalledDispatchStartTemporaryDetach() {
+ return mCalledDispatchStartTemporaryDetach;
+ }
+
+ public boolean hasCalledDispatchFinishTemporaryDetach() {
+ return mCalledDispatchFinishTemporaryDetach;
+ }
+
+ public boolean hasCalledOnStartTemporaryDetach() {
+ return mCalledOnStartTemporaryDetach;
+ }
+
+ public boolean hasCalledOnFinishTemporaryDetach() {
+ return mCalledOnFinishTemporaryDetach;
+ }
+
public void reset() {
mCalledOnCreateContextMenu = false;
@@ -674,6 +718,11 @@
mCalledOnKeyPreIme = false;
mCalledGetPointerIcon = false;
mCalledOnVisibilityAggregated = false;
+ mCalledOnVisibilityAggregated = false;
+ mCalledDispatchStartTemporaryDetach = false;
+ mCalledDispatchFinishTemporaryDetach = false;
+ mCalledOnStartTemporaryDetach = false;
+ mCalledOnFinishTemporaryDetach = false;
mOldWidth = -1;
mOldHeight = -1;
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTests.java b/tests/tests/view/src/android/view/cts/PixelCopyTests.java
new file mode 100644
index 0000000..b54ecb9
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTests.java
@@ -0,0 +1,281 @@
+/*
+ * 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.view.cts;
+
+import android.app.Instrumentation;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.view.PixelCopy;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.PixelCopy.OnPixelCopyFinishedListener;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
+import static android.opengl.GLES20.GL_SCISSOR_TEST;
+import static android.opengl.GLES20.glClear;
+import static android.opengl.GLES20.glClearColor;
+import static android.opengl.GLES20.glEnable;
+import static android.opengl.GLES20.glScissor;
+
+import static org.junit.Assert.*;
+
+@MediumTest
+public class PixelCopyTests {
+ @Rule
+ public ActivityTestRule<GLSurfaceViewCtsActivity> mGLSurfaceViewActivityRule =
+ new ActivityTestRule<>(GLSurfaceViewCtsActivity.class, false, false);
+
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ assertNotNull(mInstrumentation);
+ }
+
+ @Test
+ public void testErrors() {
+ Bitmap dest = null;
+ SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
+ SurfaceTexture surfaceTexture = null;
+ Surface surface = null;
+
+ try {
+ surfaceTexture = new SurfaceTexture(false);
+ surface = new Surface(surfaceTexture);
+ try {
+ copyHelper.request(surface, dest);
+ fail("Should have generated an IllegalArgumentException, null dest!");
+ } catch (IllegalArgumentException iae) {
+ // success!
+ } catch (Throwable t) {
+ throw new AssertionError("Should have generated an IllegalArgumentException!", t);
+ }
+
+ dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
+ int result = copyHelper.request(surface, dest);
+ assertEquals(PixelCopy.ERROR_SOURCE_NO_DATA, result);
+
+ dest.recycle();
+ try {
+ copyHelper.request(surface, dest);
+ fail("Should have generated an IllegalArgumentException!");
+ } catch (IllegalArgumentException iae) {
+ // success!
+ } catch (Throwable t) {
+ throw new AssertionError(
+ "Should have generated an IllegalArgumentException, recycled bitmap!", t);
+ }
+ } finally {
+ try {
+ if (surface != null) surface.release();
+ } catch (Throwable t) {}
+ try {
+ if (surfaceTexture != null) surfaceTexture.release();
+ } catch (Throwable t) {}
+ surface = null;
+ surfaceTexture = null;
+ }
+ }
+
+ @Test
+ public void testGlProducer() {
+ try {
+ CountDownLatch swapFence = new CountDownLatch(2);
+ GLSurfaceViewCtsActivity.setGlVersion(2);
+ GLSurfaceViewCtsActivity.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ GLSurfaceViewCtsActivity.setFixedSize(100, 100);
+ GLSurfaceViewCtsActivity.setRenderer(new QuadColorGLRenderer(
+ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, swapFence));
+
+ GLSurfaceViewCtsActivity activity =
+ mGLSurfaceViewActivityRule.launchActivity(null);
+
+ while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
+ activity.getView().requestRender();
+ }
+
+ // Test a fullsize copy
+ Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
+ int result = copyHelper.request(activity.getView(), bitmap);
+ assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
+ // Make sure nothing messed with the bitmap
+ assertEquals(100, bitmap.getWidth());
+ assertEquals(100, bitmap.getHeight());
+ assertEquals(Config.ARGB_8888, bitmap.getConfig());
+ assertBitmapQuadColor(bitmap,
+ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+
+ // Test that scaling works
+ // Since we only sample mid-pixel of each qudrant, filtering
+ // quality isn't tested
+ bitmap.reconfigure(20, 20, Config.ARGB_8888);
+ result = copyHelper.request(activity.getView(), bitmap);
+ assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
+ // Make sure nothing messed with the bitmap
+ assertEquals(20, bitmap.getWidth());
+ assertEquals(20, bitmap.getHeight());
+ assertEquals(Config.ARGB_8888, bitmap.getConfig());
+ assertBitmapQuadColor(bitmap,
+ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+
+ } catch (InterruptedException e) {
+ fail("Interrupted, error=" + e.getMessage());
+ } finally {
+ GLSurfaceViewCtsActivity.resetFixedSize();
+ GLSurfaceViewCtsActivity.resetGlVersion();
+ GLSurfaceViewCtsActivity.resetRenderer();
+ GLSurfaceViewCtsActivity.resetRenderMode();
+ }
+ }
+
+ private static int getPixelFloatPos(Bitmap bitmap, float xpos, float ypos) {
+ return bitmap.getPixel((int) (bitmap.getWidth() * xpos), (int) (bitmap.getHeight() * ypos));
+ }
+
+ private void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
+ int bottomLeft, int bottomRight) {
+ // Just quickly sample 4 pixels in the various regions.
+ assertEquals("Top left", topLeft, getPixelFloatPos(bitmap, .25f, .25f));
+ assertEquals("Top right", topRight, getPixelFloatPos(bitmap, .75f, .25f));
+ assertEquals("Bottom left", bottomLeft, getPixelFloatPos(bitmap, .25f, .75f));
+ assertEquals("Bottom right", bottomRight, getPixelFloatPos(bitmap, .75f, .75f));
+ }
+
+ private static class QuadColorGLRenderer implements Renderer {
+
+ private final int mTopLeftColor;
+ private final int mTopRightColor;
+ private final int mBottomLeftColor;
+ private final int mBottomRightColor;
+
+ private final CountDownLatch mFence;
+
+ private int mWidth, mHeight;
+
+ public QuadColorGLRenderer(int topLeft, int topRight,
+ int bottomLeft, int bottomRight, CountDownLatch fence) {
+ mTopLeftColor = topLeft;
+ mTopRightColor = topRight;
+ mBottomLeftColor = bottomLeft;
+ mBottomRightColor = bottomRight;
+ mFence = fence;
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ int cx = mWidth / 2;
+ int cy = mHeight / 2;
+
+ glEnable(GL_SCISSOR_TEST);
+
+ glScissor(0, cy, cx, mHeight - cy);
+ clearColor(mTopLeftColor);
+
+ glScissor(cx, cy, mWidth - cx, mHeight - cy);
+ clearColor(mTopRightColor);
+
+ glScissor(0, 0, cx, cy);
+ clearColor(mBottomLeftColor);
+
+ glScissor(cx, 0, mWidth - cx, cy);
+ clearColor(mBottomRightColor);
+
+ mFence.countDown();
+ }
+
+ private void clearColor(int color) {
+ glClearColor(Color.red(color) / 255.0f,
+ Color.green(color) / 255.0f,
+ Color.blue(color) / 255.0f,
+ Color.alpha(color) / 255.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
+ }
+
+ private static class SynchronousPixelCopy implements OnPixelCopyFinishedListener {
+ private static Handler sHandler;
+ static {
+ HandlerThread thread = new HandlerThread("PixelCopyHelper");
+ thread.start();
+ sHandler = new Handler(thread.getLooper());
+ }
+
+ private int mStatus = -1;
+
+ public int request(Surface source, Bitmap dest) {
+ synchronized (this) {
+ PixelCopy.request(source, dest, this, sHandler);
+ return getResultLocked();
+ }
+ }
+
+ public int request(SurfaceView source, Bitmap dest) {
+ synchronized (this) {
+ PixelCopy.request(source, dest, this, sHandler);
+ return getResultLocked();
+ }
+ }
+
+ private int getResultLocked() {
+ try {
+ this.wait(1000);
+ } catch (InterruptedException e) {
+ fail("PixelCopy request didn't complete within 1s");
+ }
+ return mStatus;
+ }
+
+ @Override
+ public void onPixelCopyFinished(int copyResult) {
+ synchronized (this) {
+ mStatus = copyResult;
+ this.notify();
+ }
+ }
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewGroupTest.java b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
index 49979ca..dba02ee 100644
--- a/tests/tests/view/src/android/view/cts/ViewGroupTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
@@ -1769,6 +1769,164 @@
assertTrue(vgParent.isStartActionModeForChildTypedCalled);
}
+ public void testTemporaryDetach() {
+ // [vgParent]
+ // - [viewParent1]
+ // - [viewParent1]
+ // - [vg]
+ // - [view1]
+ // - [view2]
+ MockViewGroupSubclass vgParent = new MockViewGroupSubclass(mContext);
+ TemporaryDetachingMockView viewParent1 = new TemporaryDetachingMockView(mContext);
+ TemporaryDetachingMockView viewParent2 = new TemporaryDetachingMockView(mContext);
+ vgParent.addView(viewParent1);
+ vgParent.addView(viewParent2);
+ MockViewGroupSubclass vg = new MockViewGroupSubclass(mContext);
+ vgParent.addView(vg);
+ TemporaryDetachingMockView view1 = new TemporaryDetachingMockView(mContext);
+ TemporaryDetachingMockView view2 = new TemporaryDetachingMockView(mContext);
+ vg.addView(view1);
+ vg.addView(view2);
+
+ // Make sure that no View is temporarity detached in the initial state.
+ assertFalse(viewParent1.isTemporarilyDetached());
+ assertEquals(0, viewParent1.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnFinishTemporaryDetachCount());
+ assertFalse(viewParent2.isTemporarilyDetached());
+ assertEquals(0, viewParent2.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnFinishTemporaryDetachCount());
+ assertFalse(view1.isTemporarilyDetached());
+ assertEquals(0, view1.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, view1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, view1.getOnStartTemporaryDetachCount());
+ assertEquals(0, view1.getOnFinishTemporaryDetachCount());
+ assertFalse(view2.isTemporarilyDetached());
+ assertEquals(0, view2.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, view2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, view2.getOnStartTemporaryDetachCount());
+ assertEquals(0, view2.getOnFinishTemporaryDetachCount());
+
+ // [vgParent]
+ // - [viewParent1]
+ // - [viewParent1]
+ // - [vg] <- dispatchStartTemporaryDetach()
+ // - [view1]
+ // - [view2]
+ vg.dispatchStartTemporaryDetach();
+
+ assertFalse(viewParent1.isTemporarilyDetached());
+ assertEquals(0, viewParent1.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnFinishTemporaryDetachCount());
+ assertFalse(viewParent2.isTemporarilyDetached());
+ assertEquals(0, viewParent2.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnFinishTemporaryDetachCount());
+ assertTrue(view1.isTemporarilyDetached());
+ assertEquals(1, view1.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, view1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, view1.getOnStartTemporaryDetachCount());
+ assertEquals(0, view1.getOnFinishTemporaryDetachCount());
+ assertTrue(view2.isTemporarilyDetached());
+ assertEquals(1, view2.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, view2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, view2.getOnStartTemporaryDetachCount());
+ assertEquals(0, view2.getOnFinishTemporaryDetachCount());
+
+ // [vgParent]
+ // - [viewParent1]
+ // - [viewParent1]
+ // - [vg] <- dispatchFinishTemporaryDetach()
+ // - [view1]
+ // - [view2]
+ vg.dispatchFinishTemporaryDetach();
+
+ assertFalse(viewParent1.isTemporarilyDetached());
+ assertEquals(0, viewParent1.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnFinishTemporaryDetachCount());
+ assertFalse(viewParent2.isTemporarilyDetached());
+ assertEquals(0, viewParent2.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnFinishTemporaryDetachCount());
+ assertFalse(view1.isTemporarilyDetached());
+ assertEquals(1, view1.getDispatchStartTemporaryDetachCount());
+ assertEquals(1, view1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, view1.getOnStartTemporaryDetachCount());
+ assertEquals(1, view1.getOnFinishTemporaryDetachCount());
+ assertFalse(view2.isTemporarilyDetached());
+ assertEquals(1, view2.getDispatchStartTemporaryDetachCount());
+ assertEquals(1, view2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, view2.getOnStartTemporaryDetachCount());
+ assertEquals(1, view2.getOnFinishTemporaryDetachCount());
+
+ // [vgParent] <- dispatchStartTemporaryDetach()
+ // - [viewParent1]
+ // - [viewParent1]
+ // - [vg]
+ // - [view1]
+ // - [view2]
+ vgParent.dispatchStartTemporaryDetach();
+
+ assertTrue(viewParent1.isTemporarilyDetached());
+ assertEquals(1, viewParent1.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, viewParent1.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent1.getOnFinishTemporaryDetachCount());
+ assertTrue(viewParent2.isTemporarilyDetached());
+ assertEquals(1, viewParent2.getDispatchStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, viewParent2.getOnStartTemporaryDetachCount());
+ assertEquals(0, viewParent2.getOnFinishTemporaryDetachCount());
+ assertTrue(view1.isTemporarilyDetached());
+ assertEquals(2, view1.getDispatchStartTemporaryDetachCount());
+ assertEquals(1, view1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(2, view1.getOnStartTemporaryDetachCount());
+ assertEquals(1, view1.getOnFinishTemporaryDetachCount());
+ assertTrue(view2.isTemporarilyDetached());
+ assertEquals(2, view2.getDispatchStartTemporaryDetachCount());
+ assertEquals(1, view2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(2, view2.getOnStartTemporaryDetachCount());
+ assertEquals(1, view2.getOnFinishTemporaryDetachCount());
+
+ // [vgParent] <- dispatchFinishTemporaryDetach()
+ // - [viewParent1]
+ // - [viewParent1]
+ // - [vg]
+ // - [view1]
+ // - [view2]
+ vgParent.dispatchFinishTemporaryDetach();
+
+ assertFalse(viewParent1.isTemporarilyDetached());
+ assertEquals(1, viewParent1.getDispatchStartTemporaryDetachCount());
+ assertEquals(1, viewParent1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, viewParent1.getOnStartTemporaryDetachCount());
+ assertEquals(1, viewParent1.getOnFinishTemporaryDetachCount());
+ assertFalse(viewParent2.isTemporarilyDetached());
+ assertEquals(1, viewParent2.getDispatchStartTemporaryDetachCount());
+ assertEquals(1, viewParent2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(1, viewParent2.getOnStartTemporaryDetachCount());
+ assertEquals(1, viewParent2.getOnFinishTemporaryDetachCount());
+ assertFalse(view1.isTemporarilyDetached());
+ assertEquals(2, view1.getDispatchStartTemporaryDetachCount());
+ assertEquals(2, view1.getDispatchFinishTemporaryDetachCount());
+ assertEquals(2, view1.getOnStartTemporaryDetachCount());
+ assertEquals(2, view1.getOnFinishTemporaryDetachCount());
+ assertFalse(view2.isTemporarilyDetached());
+ assertEquals(2, view2.getDispatchStartTemporaryDetachCount());
+ assertEquals(2, view2.getDispatchFinishTemporaryDetachCount());
+ assertEquals(2, view2.getOnStartTemporaryDetachCount());
+ assertEquals(2, view2.getOnFinishTemporaryDetachCount());
+ }
+
private static final ActionMode.Callback NO_OP_ACTION_MODE_CALLBACK =
new ActionMode.Callback() {
@Override
@@ -2422,6 +2580,57 @@
}
}
+ static final class TemporaryDetachingMockView extends View {
+ private int mDispatchStartTemporaryDetachCount = 0;
+ private int mDispatchFinishTemporaryDetachCount = 0;
+ private int mOnStartTemporaryDetachCount = 0;
+ private int mOnFinishTemporaryDetachCount = 0;
+
+ public TemporaryDetachingMockView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ mDispatchStartTemporaryDetachCount += 1;
+ }
+
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ super.dispatchFinishTemporaryDetach();
+ mDispatchFinishTemporaryDetachCount += 1;
+ }
+
+ @Override
+ public void onStartTemporaryDetach() {
+ super.onStartTemporaryDetach();
+ mOnStartTemporaryDetachCount += 1;
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ super.onFinishTemporaryDetach();
+ mOnFinishTemporaryDetachCount += 1;
+ }
+
+ public int getDispatchStartTemporaryDetachCount() {
+ return mDispatchStartTemporaryDetachCount;
+ }
+
+ public int getDispatchFinishTemporaryDetachCount() {
+ return mDispatchFinishTemporaryDetachCount;
+ }
+
+ public int getOnStartTemporaryDetachCount() {
+ return mOnStartTemporaryDetachCount;
+ }
+
+ public int getOnFinishTemporaryDetachCount() {
+ return mOnFinishTemporaryDetachCount;
+ }
+ }
+
@Override
public void setResult(int resultCode) {
synchronized (mSync) {
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 426a20f..6a431a2 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -83,7 +83,6 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -3561,107 +3560,41 @@
}
public void testOnStartAndFinishTemporaryDetach() throws Throwable {
- final MockListView listView = new MockListView(mActivity);
- List<String> items = new ArrayList<>();
- items.add("1");
- items.add("2");
- items.add("3");
- final Adapter<String> adapter = new Adapter<>(mActivity, 0, items);
+ final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
+
+ assertFalse(view.isTemporarilyDetached());
+ assertFalse(view.hasCalledDispatchStartTemporaryDetach());
+ assertFalse(view.hasCalledDispatchFinishTemporaryDetach());
+ assertFalse(view.hasCalledOnStartTemporaryDetach());
+ assertFalse(view.hasCalledOnFinishTemporaryDetach());
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- mActivity.setContentView(listView);
- listView.setAdapter(adapter);
+ view.dispatchStartTemporaryDetach();
}
});
getInstrumentation().waitForIdleSync();
- final MockView focusChild = (MockView) listView.getChildAt(0);
+
+ assertTrue(view.isTemporarilyDetached());
+ assertTrue(view.hasCalledDispatchStartTemporaryDetach());
+ assertFalse(view.hasCalledDispatchFinishTemporaryDetach());
+ assertTrue(view.hasCalledOnStartTemporaryDetach());
+ assertFalse(view.hasCalledOnFinishTemporaryDetach());
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- focusChild.requestFocus();
+ view.dispatchFinishTemporaryDetach();
}
});
getInstrumentation().waitForIdleSync();
- assertTrue(listView.getChildAt(0).isFocused());
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- listView.detachViewFromParent(focusChild);
- }
- });
- getInstrumentation().waitForIdleSync();
- assertFalse(listView.hasCalledOnStartTemporaryDetach());
- assertFalse(listView.hasCalledOnFinishTemporaryDetach());
- }
-
- private static class MockListView extends ListView {
- private boolean mCalledOnStartTemporaryDetach = false;
- private boolean mCalledOnFinishTemporaryDetach = false;
-
- public MockListView(Context context) {
- super(context);
- }
-
- @Override
- protected void detachViewFromParent(View child) {
- super.detachViewFromParent(child);
- }
-
- @Override
- public void onFinishTemporaryDetach() {
- super.onFinishTemporaryDetach();
- mCalledOnFinishTemporaryDetach = true;
- }
-
- public boolean hasCalledOnFinishTemporaryDetach() {
- return mCalledOnFinishTemporaryDetach;
- }
-
- @Override
- public void onStartTemporaryDetach() {
- super.onStartTemporaryDetach();
- mCalledOnStartTemporaryDetach = true;
- }
-
- public boolean hasCalledOnStartTemporaryDetach() {
- return mCalledOnStartTemporaryDetach;
- }
-
- public void reset() {
- mCalledOnStartTemporaryDetach = false;
- mCalledOnFinishTemporaryDetach = false;
- }
- }
-
- private static class Adapter<T> extends ArrayAdapter<T> {
- ArrayList<MockView> views = new ArrayList<>();
-
- public Adapter(Context context, int textViewResourceId, List<T> objects) {
- super(context, textViewResourceId, objects);
- for (int i = 0; i < objects.size(); i++) {
- views.add(new MockView(context));
- views.get(i).setFocusable(true);
- }
- }
-
- @Override
- public int getCount() {
- return views.size();
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return views.get(position);
- }
+ assertFalse(view.isTemporarilyDetached());
+ assertTrue(view.hasCalledDispatchStartTemporaryDetach());
+ assertTrue(view.hasCalledDispatchFinishTemporaryDetach());
+ assertTrue(view.hasCalledOnStartTemporaryDetach());
+ assertTrue(view.hasCalledOnFinishTemporaryDetach());
}
public void testKeyPreIme() throws Throwable {
diff --git a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
index 1394ccd..b6dc991 100644
--- a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
@@ -64,6 +64,7 @@
// url, and different domains.
private static final String URL_1 = "https://www.example.com";
private static final String URL_2 = "https://www.example.org";
+ private static final String URL_INSECURE = "http://www.example.org";
private static final String JS_INTERFACE_NAME = "Android";
private static final int POLLING_TIMEOUT = 60 * 1000;
@@ -536,7 +537,7 @@
originCheck.run();
}
- // Test loading pages and checks rejecting once and recjecting the domain forever
+ // Test loading pages and checks rejecting once and rejecting the domain forever
public void testSimpleGeolocationRequestReject() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -591,6 +592,26 @@
falseCheck.run();
}
+ // Test deny geolocation on insecure origins
+ public void testGeolocationRequestDeniedOnInsecureOrigin() throws Exception {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+ final TestSimpleGeolocationRequestWebChromeClient chromeClientAcceptAlways =
+ new TestSimpleGeolocationRequestWebChromeClient(mOnUiThread, true, true);
+ mOnUiThread.setWebChromeClient(chromeClientAcceptAlways);
+ loadUrlAndUpdateLocation(URL_INSECURE);
+ Callable<Boolean> locationDenied = new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return mJavascriptStatusReceiver.mDenied;
+ }
+ };
+ PollingCheck.check("JS got position", POLLING_TIMEOUT, locationDenied);
+ // The geolocation permission prompt should not be called
+ assertFalse(chromeClientAcceptAlways.mReceivedRequest);
+ }
+
// Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
// notify back to Java when a location or error is received.
public final static class JavascriptStatusReceiver {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 3f4fd92..6d67547 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -203,6 +203,46 @@
}
@UiThreadTest
+ public void testCreatingWebViewWithDeviceEncrpytionFails() {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
+ try {
+ new WebView(deviceEncryptedContext);
+ } catch (IllegalArgumentException e) {
+ return;
+ }
+
+ Assert.fail("WebView should have thrown exception when creating with a device " +
+ "protected storage context");
+ }
+
+ @UiThreadTest
+ public void testCreatingWebViewWithMultipleEncryptionContext() {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ // Credential encrpytion is the default. Create one here for the sake of clarity.
+ Context credentialEncryptedContext = getActivity().createCredentialProtectedStorageContext();
+ Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
+
+ // No exception should be thrown with credential encryption context.
+ new WebView(credentialEncryptedContext);
+
+ try {
+ new WebView(deviceEncryptedContext);
+ } catch (IllegalArgumentException e) {
+ return;
+ }
+
+ Assert.fail("WebView should have thrown exception when creating with a device " +
+ "protected storage context");
+ }
+
+ @UiThreadTest
public void testCreatingWebViewCreatesCookieSyncManager() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index 48f2f06..224966d 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -254,7 +254,9 @@
</activity>
<activity android:name="android.widget.cts.TextViewCtsActivity"
- android:label="TextViewCtsActivity">
+ android:label="TextViewCtsActivity"
+ android:screenOrientation="nosensor"
+ android:windowSoftInputMode="stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
@@ -286,7 +288,7 @@
</activity>
<activity android:name="android.widget.cts.VideoViewCtsActivity"
- android:configChanges="keyboardHidden|orientation|screenSize">
+ android:configChanges="keyboardHidden|orientation|screenSize"
android:label="VideoViewCtsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -295,7 +297,9 @@
</activity>
<activity android:name="android.widget.cts.AutoCompleteCtsActivity"
- android:label="AutoCompleteCtsActivity">
+ android:label="AutoCompleteCtsActivity"
+ android:screenOrientation="nosensor"
+ android:windowSoftInputMode="stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/tests/tests/widget/res/layout/autocompletetextview_layout.xml b/tests/tests/widget/res/layout/autocompletetextview_layout.xml
index 25a8541..27eccab 100644
--- a/tests/tests/widget/res/layout/autocompletetextview_layout.xml
+++ b/tests/tests/widget/res/layout/autocompletetextview_layout.xml
@@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:text="@string/notify" />
- <AutoCompleteTextView android:id="@+id/autocompletetv_edit"
+ <android.widget.cts.AutoCompleteTextViewNoIme android:id="@+id/autocompletetv_edit"
android:completionThreshold="1"
android:completionHint="@string/tabs_1"
android:layout_width="match_parent"
diff --git a/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewNoIme.java b/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewNoIme.java
new file mode 100644
index 0000000..c4ada69
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewNoIme.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 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.widget.cts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.AutoCompleteTextView;
+
+/**
+ * An AutoCompleteTextView that returns null for onCreateInputConnection.
+ */
+public class AutoCompleteTextViewNoIme extends AutoCompleteTextView {
+
+ public AutoCompleteTextViewNoIme(Context context) {
+ super(context);
+ }
+
+ public AutoCompleteTextViewNoIme(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoCompleteTextViewNoIme(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AutoCompleteTextViewNoIme(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public AutoCompleteTextViewNoIme(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes, Resources.Theme popupTheme) {
+ super(context, attrs, defStyleAttr, defStyleRes, popupTheme);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return null;
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
index ea0861f..073a90b 100644
--- a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
@@ -475,6 +475,8 @@
promptView.getLocationOnScreen(promptViewOnScreenXY);
final ListView listView = mPopupWindow.getListView();
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, listView, null);
+
final View firstListChild = listView.getChildAt(0);
final int[] firstChildOnScreenXY = new int[2];
firstListChild.getLocationOnScreen(firstChildOnScreenXY);
@@ -498,6 +500,8 @@
promptView.getLocationOnScreen(promptViewOnScreenXY);
final ListView listView = mPopupWindow.getListView();
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, listView, null);
+
final View lastListChild = listView.getChildAt(listView.getChildCount() - 1);
final int[] lastChildOnScreenXY = new int[2];
lastListChild.getLocationOnScreen(lastChildOnScreenXY);
@@ -595,6 +599,8 @@
.withDismissListener().withItemSelectedListener();
mPopupWindowBuilder.show();
+ final View root = mPopupWindow.getListView().getRootView();
+
// "Point" our custom extension of EditText to our ListPopupWindow
final MockViewForListPopupWindow anchor =
(MockViewForListPopupWindow) mPopupWindow.getAnchorView();
@@ -617,6 +623,8 @@
mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
mInstrumentation.waitForIdleSync();
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, root, null);
+
// At this point we expect that item #2 was selected
verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
any(AdapterView.class), any(View.class), eq(2), eq(2L));
@@ -627,6 +635,8 @@
mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
mInstrumentation.waitForIdleSync();
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, root, null);
+
// At this point we expect that item #1 was selected
verify(mPopupWindowBuilder.mOnItemSelectedListener, times(2)).onItemSelected(
any(AdapterView.class), any(View.class), eq(1), eq(1L));
@@ -637,6 +647,8 @@
mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
mInstrumentation.waitForIdleSync();
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, root, null);
+
// At this point we expect that item #0 was selected
verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
any(AdapterView.class), any(View.class), eq(0), eq(0L));
diff --git a/tests/tests/widget/src/android/widget/cts/ListViewTest.java b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
index 3ee9ead..b5a92e9 100644
--- a/tests/tests/widget/src/android/widget/cts/ListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
@@ -37,7 +37,6 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnDrawListener;
import android.view.animation.LayoutAnimationController;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
@@ -569,7 +568,7 @@
* The following functions are merged from frameworktest.
*/
@MediumTest
- public void testRequestLayout() throws Exception {
+ public void testRequestLayoutCallsMeasure() throws Exception {
ListView listView = new ListView(mActivity);
List<String> items = new ArrayList<>();
items.add("hello");
@@ -643,6 +642,114 @@
}
}
+ @MediumTest
+ public void testRequestLayoutWithTemporaryDetach() throws Exception {
+ ListView listView = new ListView(mActivity);
+ List<String> items = new ArrayList<>();
+ items.add("0");
+ items.add("1");
+ items.add("2");
+ final TemporarilyDetachableMockViewAdapter<String> adapter =
+ new TemporarilyDetachableMockViewAdapter<>(
+ mActivity, android.R.layout.simple_list_item_1, items);
+ mInstrumentation.runOnMainSync(() -> {
+ listView.setAdapter(adapter);
+ mActivity.setContentView(listView);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ assertEquals(items.size(), listView.getCount());
+ final TemporarilyDetachableMockView childView0 =
+ (TemporarilyDetachableMockView) listView.getChildAt(0);
+ final TemporarilyDetachableMockView childView1 =
+ (TemporarilyDetachableMockView) listView.getChildAt(1);
+ final TemporarilyDetachableMockView childView2 =
+ (TemporarilyDetachableMockView) listView.getChildAt(2);
+ assertNotNull(childView0);
+ assertNotNull(childView1);
+ assertNotNull(childView2);
+
+ // Make sure that the childView1 has focus.
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, childView1, childView1::requestFocus);
+ assertTrue(childView1.isFocused());
+
+ // Make sure that ListView#requestLayout() is optimized when nothing is changed.
+ ViewTestUtils.runOnMainAndDrawSync(mInstrumentation, listView, listView::requestLayout);
+ assertEquals(childView0, listView.getChildAt(0));
+ assertEquals(childView1, listView.getChildAt(1));
+ assertEquals(childView2, listView.getChildAt(2));
+ }
+
+ private class TemporarilyDetachableMockView extends View {
+
+ private boolean mIsDispatchingStartTemporaryDetach = false;
+ private boolean mIsDispatchingFinishTemporaryDetach = false;
+
+ public TemporarilyDetachableMockView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ mIsDispatchingStartTemporaryDetach = true;
+ super.dispatchStartTemporaryDetach();
+ mIsDispatchingStartTemporaryDetach = false;
+ }
+
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ mIsDispatchingFinishTemporaryDetach = true;
+ super.dispatchFinishTemporaryDetach();
+ mIsDispatchingFinishTemporaryDetach = false;
+ }
+
+ @Override
+ public void onStartTemporaryDetach() {
+ super.onStartTemporaryDetach();
+ if (!mIsDispatchingStartTemporaryDetach) {
+ throw new IllegalStateException("#onStartTemporaryDetach() must be indirectly"
+ + " called via #dispatchStartTemporaryDetach()");
+ }
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ super.onFinishTemporaryDetach();
+ if (!mIsDispatchingFinishTemporaryDetach) {
+ throw new IllegalStateException("#onStartTemporaryDetach() must be indirectly"
+ + " called via #dispatchFinishTemporaryDetach()");
+ }
+ }
+ }
+
+ private class TemporarilyDetachableMockViewAdapter<T> extends ArrayAdapter<T> {
+ ArrayList<TemporarilyDetachableMockView> views = new ArrayList<>();
+
+ public TemporarilyDetachableMockViewAdapter(Context context, int textViewResourceId,
+ List<T> objects) {
+ super(context, textViewResourceId, objects);
+ for (int i = 0; i < objects.size(); i++) {
+ views.add(new TemporarilyDetachableMockView(context));
+ views.get(i).setFocusable(true);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return views.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return views.get(position);
+ }
+ }
+
public void testTransientStateUnstableIds() throws Exception {
final ListView listView = mListView;
final ArrayList<String> items = new ArrayList<String>(Arrays.asList(mCountryList));
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 40d42a2..effc685 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -23,6 +23,7 @@
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources.NotFoundException;
+import android.cts.util.KeyEventUtil;
import android.cts.util.PollingCheck;
import android.cts.util.WidgetTestUtils;
import android.graphics.Bitmap;
@@ -123,6 +124,7 @@
+ "this text, I would love to see the kind of devices you guys now use!";
private static final long TIMEOUT = 5000;
private CharSequence mTransformedText;
+ private KeyEventUtil mKeyEventUtil;
public TextViewTest() {
super("android.widget.cts", TextViewCtsActivity.class);
@@ -139,6 +141,7 @@
}
}.run();
mInstrumentation = getInstrumentation();
+ mKeyEventUtil = new KeyEventUtil(mInstrumentation);
}
/**
@@ -1400,7 +1403,7 @@
initTextViewForTyping();
// Type some text.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Precondition: The cursor is at the end of the text.
@@ -1432,8 +1435,9 @@
initTextViewForTyping();
// Simulate deleting text and undoing it.
- mInstrumentation.sendStringSync("xyz");
- sendKeys(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL);
+ mKeyEventUtil.sendString(mTextView, "xyz");
+ mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL,
+ KeyEvent.KEYCODE_DEL);
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Precondition: The text was actually deleted.
@@ -1563,8 +1567,9 @@
initTextViewForTyping();
// Create two undo operations, an insert and a delete.
- mInstrumentation.sendStringSync("xyz");
- sendKeys(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL);
+ mKeyEventUtil.sendString(mTextView, "xyz");
+ mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL,
+ KeyEvent.KEYCODE_DEL);
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Calling setText() clears both undo operations, so undo doesn't happen.
@@ -1585,7 +1590,7 @@
initTextViewForTyping();
// Type some text. This creates an undo entry.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Undo the typing to create a redo entry.
@@ -1604,7 +1609,7 @@
initTextViewForTyping();
// Type some text.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Programmatically append some text.
@@ -1627,7 +1632,7 @@
initTextViewForTyping();
// Type some text.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Directly modify the underlying Editable to insert some text.
@@ -1676,7 +1681,7 @@
mTextView.addTextChangedListener(new ConvertToSpacesTextWatcher());
// Type some text.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// TextWatcher altered the text.
@@ -1714,7 +1719,7 @@
initTextViewForTyping();
// Type some text.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Pressing Control-Z triggers undo.
@@ -1737,7 +1742,7 @@
initTextViewForTyping();
// Type some text to create an undo operation.
- mInstrumentation.sendStringSync("abc");
+ mKeyEventUtil.sendString(mTextView, "abc");
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Parcel and unparcel the TextView.
@@ -1748,7 +1753,7 @@
mInstrumentation.waitForIdleSync();
// Delete a character to create a new undo operation.
- sendKeys(KeyEvent.KEYCODE_DEL);
+ mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL);
mActivity.runOnUiThread(new Runnable() {
public void run() {
assertEquals("ab", mTextView.getText().toString());
@@ -1775,8 +1780,8 @@
initTextViewForTyping();
// Type and delete to create two new undo operations.
- mInstrumentation.sendStringSync("a");
- sendKeys(KeyEvent.KEYCODE_DEL);
+ mKeyEventUtil.sendString(mTextView, "a");
+ mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL);
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Empty the undo stack then parcel and unparcel the TextView. While the undo
@@ -1790,8 +1795,8 @@
mInstrumentation.waitForIdleSync();
// Create two more undo operations.
- mInstrumentation.sendStringSync("b");
- sendKeys(KeyEvent.KEYCODE_DEL);
+ mKeyEventUtil.sendString(mTextView, "b");
+ mKeyEventUtil.sendKeys(mTextView, KeyEvent.KEYCODE_DEL);
mActivity.runOnUiThread(new Runnable() {
public void run() {
// Verify undo still works.
diff --git a/tests/tests/widget/src/android/widget/cts/util/ViewTestUtils.java b/tests/tests/widget/src/android/widget/cts/util/ViewTestUtils.java
index 02ac28c..e9ef867 100644
--- a/tests/tests/widget/src/android/widget/cts/util/ViewTestUtils.java
+++ b/tests/tests/widget/src/android/widget/cts/util/ViewTestUtils.java
@@ -19,6 +19,8 @@
import junit.framework.Assert;
import android.app.Instrumentation;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnDrawListener;
@@ -37,10 +39,11 @@
*
* @param instrumentation the instrumentation used to run the test
* @param view the view whose tree should be drawn before returning
- * @param runner the runnable to run on the main thread
+ * @param runner the runnable to run on the main thread, or {@code null} to
+ * simply force invalidation and a draw pass
*/
- public static void runOnMainAndDrawSync(Instrumentation instrumentation,
- final View view, final Runnable runner) {
+ public static void runOnMainAndDrawSync(@NonNull Instrumentation instrumentation,
+ @NonNull final View view, @Nullable final Runnable runner) {
final CountDownLatch latch = new CountDownLatch(1);
instrumentation.runOnMainSync(() -> {
@@ -54,7 +57,12 @@
};
observer.addOnDrawListener(listener);
- runner.run();
+
+ if (runner != null) {
+ runner.run();
+ } else {
+ view.invalidate();
+ }
});
try {