DO NOT MERGE make system health check failures more readable am: eedaf3b7f5
am: e549673f77 -s ours
Change-Id: I309eae789adae81cfd93bb697b73cdd9f3cf2118
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..94deaa7
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,11 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+ -fw libraries/annotations
+ libraries/aupt-lib
+ libraries/base-app-helpers
+ libraries/launcher-helper
+ libraries/metrics-helper
+ libraries/power-helper
+ libraries/system-helpers
+ libraries/timeresult-helpers
+
diff --git a/build/tasks/continuous_instrumentation_metric_tests.mk b/build/tasks/continuous_instrumentation_metric_tests.mk
new file mode 100644
index 0000000..90cf9f8
--- /dev/null
+++ b/build/tasks/continuous_instrumentation_metric_tests.mk
@@ -0,0 +1,36 @@
+# 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.
+
+# Rules to generate a tests zip file that included test modules
+# based on the configuration.
+
+LOCAL_PATH := $(call my-dir)
+include $(LOCAL_PATH)/tests/instrumentation_metric_test_list.mk
+-include $(wildcard vendor/*/build/tasks/tests/instrumentation_metric_test_list.mk)
+
+my_modules := \
+ $(instrumentation_metric_tests)
+
+my_package_name := continuous_instrumentation_metric_tests
+
+include $(BUILD_SYSTEM)/tasks/tools/package-modules.mk
+
+.PHONY: continuous_instrumentation_metric_tests
+continuous_instrumentation_metric_tests : $(my_package_zip)
+
+name := $(TARGET_PRODUCT)-continuous_instrumentation_metric_tests-$(FILE_NAME_TAG)
+$(call dist-for-goals, continuous_instrumentation_metric_tests, $(my_package_zip):$(name).zip)
+
+# Also build this when you run "make tests".
+tests: continuous_instrumentation_metric_tests
diff --git a/build/tasks/continuous_instrumentation_tests.mk b/build/tasks/continuous_instrumentation_tests.mk
new file mode 100644
index 0000000..5d964e6
--- /dev/null
+++ b/build/tasks/continuous_instrumentation_tests.mk
@@ -0,0 +1,96 @@
+# 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.
+
+# Rules to generate a tests zip file that included test modules
+# based on the configuration.
+
+LOCAL_PATH := $(call my-dir)
+include $(LOCAL_PATH)/tests/instrumentation_test_list.mk
+-include $(wildcard vendor/*/build/tasks/tests/instrumentation_test_list.mk)
+
+my_modules := \
+ $(instrumentation_tests)
+
+my_package_name := continuous_instrumentation_tests
+
+include $(BUILD_SYSTEM)/tasks/tools/package-modules.mk
+
+.PHONY: continuous_instrumentation_tests
+continuous_instrumentation_tests : $(my_package_zip)
+
+name := $(TARGET_PRODUCT)-continuous_instrumentation_tests-$(FILE_NAME_TAG)
+$(call dist-for-goals, continuous_instrumentation_tests, $(my_package_zip):$(name).zip)
+
+# Also build this when you run "make tests".
+tests: continuous_instrumentation_tests
+
+# Include test em files in emma metadata
+ifeq ($(EMMA_INSTRUMENT_STATIC),true)
+ $(EMMA_META_ZIP) : continuous_instrumentation_tests
+endif
+
+# Rules to generate an API-coverage report based on the above tests
+
+# Coverage report output location
+coverage_out := $(call intermediates-dir-for,PACKAGING,continuous_instrumentation_tests_coverage)
+coverage_report := $(coverage_out)/api_coverage.html
+
+# Framework API descriptions
+api_text := frameworks/base/api/system-current.txt
+api_xml := $(coverage_out)/api.xml
+$(api_xml) : $(api_text) $(APICHECK)
+ $(hide) echo "Converting API file to XML: $@"
+ $(hide) mkdir -p $(dir $@)
+ $(hide) $(APICHECK_COMMAND) -convert2xml $< $@
+
+# CTS API coverage tool
+api_coverage_exe := $(HOST_OUT_EXECUTABLES)/cts-api-coverage
+dexdeps_exe := $(HOST_OUT_EXECUTABLES)/dexdeps
+
+# APKs to measure for coverage
+test_apks := $(call intermediates-dir-for,PACKAGING,continuous_instrumentation_tests)/DATA/app/*
+
+# Rule to generate the coverage report
+api_coverage_dep := continuous_instrumentation_tests $(api_coverage_exe) $(dexdeps_exe) $(api_xml)
+$(coverage_report): PRIVATE_API_COVERAGE_EXE := $(api_coverage_exe)
+$(coverage_report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(coverage_report): PRIVATE_API_XML := $(api_xml)
+$(coverage_report): PRIVATE_REPORT_TITLE := "APCT API Coverage Report"
+$(coverage_report): PRIVATE_TEST_APKS := $(test_apks)
+$(coverage_report): $(api_coverage_dep) | $(ACP)
+ $(hide) mkdir -p $(dir $@)
+ $(hide) $(PRIVATE_API_COVERAGE_EXE) -d $(PRIVATE_DEXDEPS_EXE) \
+ -a $(PRIVATE_API_XML) -t $(PRIVATE_REPORT_TITLE) -f html -o $@ $(PRIVATE_TEST_APKS)
+ @ echo $(PRIVATE_REPORT_TITLE): file://$(ANDROID_BUILD_TOP)/$@
+
+.PHONY: continuous_instrumentation_tests_api_coverage
+continuous_instrumentation_tests_api_coverage : $(coverage_report)
+
+# Include the coverage report in the dist folder
+$(call dist-for-goals, continuous_instrumentation_tests_api_coverage, \
+ $(coverage_report):$(name)-api_coverage.html)
+
+# Also build this when you run "make tests".
+# This allow us to not change the build server config.
+tests : continuous_instrumentation_tests_api_coverage
+
+# Reset temp vars
+coverage_out :=
+coverage_report :=
+api_text :=
+api_xml :=
+api_coverage_exe :=
+dexdeps_exe :=
+test_apks :=
+api_coverage_dep :=
diff --git a/build/tasks/continuous_native_metric_tests.mk b/build/tasks/continuous_native_metric_tests.mk
new file mode 100644
index 0000000..aff19b4
--- /dev/null
+++ b/build/tasks/continuous_native_metric_tests.mk
@@ -0,0 +1,36 @@
+# 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.
+
+# Rules to generate a tests zip file that included test modules
+# based on the configuration for continuous metric testing.
+
+LOCAL_PATH := $(call my-dir)
+include $(LOCAL_PATH)/tests/native_metric_test_list.mk
+-include $(wildcard vendor/*/build/tasks/tests/native_metric_test_list.mk)
+
+my_modules := \
+ $(native_metric_tests)
+
+my_package_name := continuous_native_metric_tests
+
+include $(BUILD_SYSTEM)/tasks/tools/package-modules.mk
+
+.PHONY: continuous_native_metric_tests
+continuous_native_metric_tests : $(my_package_zip)
+
+name := $(TARGET_PRODUCT)-continuous_native_metric_tests-$(FILE_NAME_TAG)
+$(call dist-for-goals, continuous_native_metric_tests, $(my_package_zip):$(name).zip)
+
+# Also build this when you run "make tests".
+tests: continuous_native_metric_tests
diff --git a/build/tasks/continuous_native_tests.mk b/build/tasks/continuous_native_tests.mk
new file mode 100644
index 0000000..b8db813
--- /dev/null
+++ b/build/tasks/continuous_native_tests.mk
@@ -0,0 +1,36 @@
+# 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.
+
+# Rules to generate a tests zip file that included test modules
+# based on the configuration for continuous testing.
+
+LOCAL_PATH := $(call my-dir)
+include $(LOCAL_PATH)/tests/native_test_list.mk
+-include $(wildcard vendor/*/build/tasks/tests/native_test_list.mk)
+
+my_modules := \
+ $(native_tests)
+
+my_package_name := continuous_native_tests
+
+include $(BUILD_SYSTEM)/tasks/tools/package-modules.mk
+
+.PHONY: continuous_native_tests
+continuous_native_tests : $(my_package_zip)
+
+name := $(TARGET_PRODUCT)-continuous_native_tests-$(FILE_NAME_TAG)
+$(call dist-for-goals, continuous_native_tests, $(my_package_zip):$(name).zip)
+
+# Also build this when you run "make tests".
+tests: continuous_native_tests
diff --git a/libraries/base-app-helpers/Android.mk b/build/tasks/tests/instrumentation_metric_test_list.mk
similarity index 61%
copy from libraries/base-app-helpers/Android.mk
copy to build/tasks/tests/instrumentation_metric_test_list.mk
index 9e797fb..3387458 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/build/tasks/tests/instrumentation_metric_test_list.mk
@@ -1,4 +1,3 @@
-#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,17 +11,15 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
+instrumentation_metric_tests := \
+ crashcollector \
+ CorePerfTests \
+ DocumentsUIPerfTests \
+ DocumentsUIAppPerfTests \
+ MtpDocumentsProviderPerfTests \
+ perf-setup.sh \
+ SurfaceComposition \
+ RsBlasBenchmark \
+ ImageProcessingJB \
+ MultiUserPerfTests
diff --git a/build/tasks/tests/instrumentation_test_list.mk b/build/tasks/tests/instrumentation_test_list.mk
new file mode 100644
index 0000000..9018382
--- /dev/null
+++ b/build/tasks/tests/instrumentation_test_list.mk
@@ -0,0 +1,54 @@
+# 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.
+
+instrumentation_tests := \
+ HelloWorldTests \
+ crashcollector \
+ ManagedProvisioningTests \
+ FrameworksCoreTests \
+ FrameworksNetTests \
+ FrameworksNotificationTests \
+ FrameworksServicesTests \
+ FrameworksUtilTests \
+ MtpDocumentsProviderTests \
+ DocumentsUITests \
+ ShellTests \
+ SystemUITests \
+ RecyclerViewTests \
+ FrameworksWifiApiTests \
+ FrameworksWifiTests \
+ FrameworksTelephonyTests \
+ ContactsProviderTests \
+ ContactsProviderTests2 \
+ SettingsUnitTests \
+ TelecomUnitTests \
+ AndroidVCardTests \
+ PermissionFunctionalTests \
+ BlockedNumberProviderTest \
+ SettingsFunctionalTests \
+ LauncherFunctionalTests \
+ DownloadAppFunctionalTests \
+ NotificationFunctionalTests \
+ DownloadProviderTests \
+ EmergencyInfoTests \
+ CalendarProviderTests \
+ SettingsLibTests \
+ RetailDemoTests \
+ RSTest \
+ PrintSpoolerOutOfProcessTests \
+ CellBroadcastReceiverUnitTests \
+ TelephonyProviderTests \
+ CarrierConfigTests \
+ TeleServiceTests \
+ SettingsProviderTest
diff --git a/libraries/base-app-helpers/Android.mk b/build/tasks/tests/native_metric_test_list.mk
similarity index 61%
copy from libraries/base-app-helpers/Android.mk
copy to build/tasks/tests/native_metric_test_list.mk
index 9e797fb..3ec3c79 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/build/tasks/tests/native_metric_test_list.mk
@@ -1,4 +1,3 @@
-#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,17 +11,15 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
+native_metric_tests := \
+ binderAddInts \
+ bionic-benchmarks \
+ crashcollector \
+ hwuimacro \
+ hwuimicro \
+ libjavacore-benchmarks \
+ minikin_perftests \
+ mmapPerf \
+ netd_benchmark \
+ perf-setup.sh
diff --git a/build/tasks/tests/native_test_list.mk b/build/tasks/tests/native_test_list.mk
new file mode 100644
index 0000000..4f2c36a
--- /dev/null
+++ b/build/tasks/tests/native_test_list.mk
@@ -0,0 +1,76 @@
+# 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.
+
+native_tests := \
+ adbd_test \
+ async_io_test \
+ bionic-unit-tests \
+ bionic-unit-tests-static \
+ bluetoothtbd_test \
+ bootstat_tests \
+ boringssl_crypto_test \
+ boringssl_ssl_test \
+ bugreportz_test \
+ bsdiff_unittest \
+ camera2_test \
+ camera_client_test \
+ crashcollector \
+ debuggerd_test \
+ dumpstate_test \
+ dumpstate_test_fixture \
+ dumpsys_test \
+ hello_world_test \
+ hwui_unit_tests \
+ init_tests \
+ JniInvocation_test \
+ libappfuse_test \
+ libbase_test \
+ libcutils_test \
+ libcutils_test_static \
+ libgui_test \
+ libjavacore-unit-tests \
+ liblog-unit-tests \
+ libminijail_unittest_gtest \
+ libtextclassifier_tests \
+ libwifi-system_tests \
+ linker-unit-tests \
+ logcat-unit-tests \
+ logd-unit-tests \
+ kernel-config-unit-tests \
+ malloc_debug_unit_tests \
+ memory_replay_tests \
+ minadbd_test \
+ minikin_tests \
+ mtp_ffs_handle_test \
+ net_test_bluetooth \
+ net_test_btcore \
+ net_test_device \
+ net_test_hci \
+ net_test_osi \
+ netd_integration_test \
+ netd_unit_test \
+ pagemap_test \
+ perfprofd_test \
+ recovery_component_test \
+ recovery_unit_test \
+ scrape_mmap_addr \
+ simpleperf_cpu_hotplug_test \
+ simpleperf_unit_test \
+ syscall_filter_unittest_gtest \
+ time-unit-tests \
+ update_engine_unittests \
+ wificond_unit_test \
+ wifilogd_unit_test \
+ ziparchive-tests \
+ SurfaceFlinger_test
diff --git a/libraries/annotations/Android.mk b/libraries/annotations/Android.mk
index 9c4c2e1..391a17e 100644
--- a/libraries/annotations/Android.mk
+++ b/libraries/annotations/Android.mk
@@ -19,6 +19,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := platform-test-annotations
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SDK_VERSION := current # To allow use from CTS
include $(BUILD_STATIC_JAVA_LIBRARY)
# Build for host side tests
diff --git a/libraries/annotations/src/android/platform/test/annotations/GlobalPresubmit.java b/libraries/annotations/src/android/platform/test/annotations/GlobalPresubmit.java
new file mode 100644
index 0000000..046afd5
--- /dev/null
+++ b/libraries/annotations/src/android/platform/test/annotations/GlobalPresubmit.java
@@ -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.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the global presubmit suite for platform development.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface GlobalPresubmit {
+
+}
diff --git a/libraries/annotations/src/android/platform/test/annotations/Presubmit.java b/libraries/annotations/src/android/platform/test/annotations/Presubmit.java
index 5f08acc..484847c 100644
--- a/libraries/annotations/src/android/platform/test/annotations/Presubmit.java
+++ b/libraries/annotations/src/android/platform/test/annotations/Presubmit.java
@@ -22,7 +22,7 @@
import java.lang.annotation.Target;
/**
- * Marks a test that should run as part of the presubmit suite for platform development.
+ * Marks a test or test class that should run as part of a project scoped presubmit suite
*
*/
@Retention(RetentionPolicy.RUNTIME)
diff --git a/libraries/annotations/src/android/platform/test/annotations/RequiresDevice.java b/libraries/annotations/src/android/platform/test/annotations/RequiresDevice.java
new file mode 100644
index 0000000..852582c
--- /dev/null
+++ b/libraries/annotations/src/android/platform/test/annotations/RequiresDevice.java
@@ -0,0 +1,33 @@
+/*
+ * 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.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a specific host side test should not be run against an emulator.
+ * <p/>
+ * It will be executed only if the test is running against a physical android device. <br>
+ * For device side tests, annotate with android.support.test.filters.RequiresDevice
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface RequiresDevice {
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/app-helpers/auto/Android.mk
similarity index 71%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/app-helpers/auto/Android.mk
index 9e797fb..c6519fd 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/app-helpers/auto/Android.mk
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,13 +16,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := auto-app-helper-base
+LOCAL_STATIC_JAVA_LIBRARIES := app-helpers-common
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoDialHelper.java b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoDialHelper.java
new file mode 100644
index 0000000..b0ceb28
--- /dev/null
+++ b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoDialHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.platform.test.helpers;
+
+import android.app.Instrumentation;
+
+public abstract class AbstractAutoDialHelper extends AbstractStandardAppHelper{
+
+ public AbstractAutoDialHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * Setup expectations: The app is open.
+ *
+ * This method is used to enter a dial a number and make calls.
+ * @param phoneNumber - phone number to dial.
+ */
+ public abstract void dialNumber(String phoneNumber);
+
+ /**
+ * Setup expectations: The app is open.
+ *
+ * This method is used to end call.
+ */
+ public abstract void endCall();
+
+ /**
+ * Setup expectations: The app is open.
+ *
+ * This method is used to open call history details.
+ */
+ public abstract void openCallHistory();
+
+ /**
+ * Setup expectations: The app is open.
+ *
+ * This method is used to open missed call details.
+ */
+ public abstract void openMissedCall();
+
+}
diff --git a/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoMediaHelper.java b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoMediaHelper.java
new file mode 100644
index 0000000..40c4fef
--- /dev/null
+++ b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoMediaHelper.java
@@ -0,0 +1,95 @@
+/*
+ * 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.platform.test.helpers;
+
+import android.app.Instrumentation;
+
+public abstract class AbstractAutoMediaHelper extends AbstractStandardAppHelper{
+
+ public AbstractAutoMediaHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * Setup expectations: media app is open
+ *
+ * This method is used to play media.
+ *
+ */
+ public abstract void playMedia();
+
+ /**
+ * Setup expectations: media app is open.
+ *
+ * This method is used to pause media.
+ *
+ */
+ public abstract void pauseMedia();
+
+ /**
+ * Setup expectations: media app is open.
+ *
+ * This method is used to select next track.
+ *
+ */
+ public abstract void clickNextTrack();
+
+ /**
+ * Setup expectations: media app is open.
+ *
+ * This method is used to select previous track.
+ *
+ */
+ public abstract void clickPreviousTrack();
+
+ /**
+ * Setup expectations: media app is open.
+ *
+ * This method is used to shuffle tracks.
+ *
+ */
+ public abstract void clickShuffleAll();
+
+ /**
+ * Setup expectations: media app is open.
+ *
+ * This method is used to open Folder Menu with menuOptions.
+ * Example - openMenu->Folder->Mediafilename->trackName
+ * openMenuWith(Folder,mediafilename,trackName);
+ *
+ * @param - menuOptions used to pass multiple level of menu options in one go.
+ *
+ */
+ public abstract void openMenuWith(String... menuOptions);
+
+ /**
+ * Setup expectations: media app is open.
+ *
+ * This method is used to used to open mediafilename from now playing list.
+ *
+ * @param - trackName - media to be played.
+ */
+ public abstract void openNowPlayingWith(String trackName);
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * @return to get current playing track name.
+ */
+ public abstract String getMediaTrackName();
+
+}
diff --git a/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoOverviewHelper.java b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoOverviewHelper.java
new file mode 100644
index 0000000..e0cb1a5
--- /dev/null
+++ b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoOverviewHelper.java
@@ -0,0 +1,148 @@
+/*
+ * 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.platform.test.helpers;
+
+import android.app.Instrumentation;
+
+public abstract class AbstractAutoOverviewHelper extends AbstractStandardAppHelper{
+
+ public AbstractAutoOverviewHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * Setup expectations: in home (overview) screen.
+ *
+ * This method is used to open settings.
+ *
+ */
+ public abstract void openSettings();
+
+ /**
+ * Setup expectations: in home (overview) screen.
+ *
+ * This method is used to start voice assistant.
+ *
+ */
+ public abstract void startVoiceAssistant();
+
+ /**
+ * Setup expectations:
+ * 1.Play media from Media player/ Radio.
+ * 2.Select Home button.
+ * 3.Media/Radio card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to click next track/next station on Media/Radio card.
+ *
+ */
+ public abstract void clickNextTrack();
+
+ /**
+ * Setup expectations:
+ * 1.Play media from Media player/ Radio.
+ * 2.Select Home button.
+ * 3.Media/Radio card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to click previous track/previous station on Media/Radio card.
+ *
+ */
+ public abstract void clickPreviousTrack();
+
+ /**
+ * Setup expectations:
+ * 1.Play media from Media player/ Radio.
+ * 2.Select Home button.
+ * 3.Media/Radio card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to play media on Media/Radio card.
+ *
+ */
+ public abstract void playMedia();
+
+ /**
+ * Setup expectations:
+ * 1.Play media from Media player/ Radio.
+ * 2.Select Home button.
+ * 3.Media/Radio card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to pause media on Media/Radio card.
+ *
+ */
+ public abstract void pauseMedia();
+
+ /**
+ * Setup expectations:
+ * 1.Play media from Media player/ Radio.
+ * 2.Select Home button.
+ * 3.Media/Radio card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to open active media card app.
+ * ( Radio,Bluetooth Media, Local Media player).
+ *
+ */
+ public abstract void openMediaApp();
+
+ /**
+ * Setup expectations:
+ * 1.Dial call from Dial app .
+ * 2.Select Home button.
+ * 3.Dial card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to end call on Dial card.
+ *
+ */
+ public abstract void endCall();
+
+ /**
+ * Setup expectations:
+ * 1.Dial call from Dial app .
+ * 2.Select Home button.
+ * 3.Dial card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to mute on going call.
+ *
+ */
+ public abstract void muteCall();
+
+ /**
+ * Setup expectations:
+ * 1.Dial call from Dial app and end call.
+ * 2.Select Home button.
+ * 3.Recent Dial card shown in home (overview) screen.
+ *
+ * else throws UnknownUiException if element not found.
+ *
+ * This method is used to dial recent call activity.
+ *
+ */
+ public abstract void dialRecentCall();
+
+}
diff --git a/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoRadioHelper.java b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoRadioHelper.java
new file mode 100644
index 0000000..e782a17
--- /dev/null
+++ b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoRadioHelper.java
@@ -0,0 +1,110 @@
+
+/*
+ * 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.platform.test.helpers;
+
+import android.app.Instrumentation;
+
+public abstract class AbstractAutoRadioHelper extends AbstractStandardAppHelper{
+
+ public AbstractAutoRadioHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * Setup expectations: Radio app is open
+ *
+ * This method is used to play Radio.
+ *
+ */
+ public abstract void playRadio();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to pause radio.
+ *
+ */
+ public abstract void pauseRadio();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to select next station.
+ *
+ */
+ public abstract void clickNextStation();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to select previous station.
+ *
+ */
+ public abstract void clickPreviousStation();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to save current station.
+ *
+ */
+ public abstract void saveCurrentStation();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to open saved station list.
+ *
+ */
+ public abstract void openSavedStationList();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to select AM from menu.
+ *
+ */
+ public abstract void clickAmFromMenu();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to select FM from menu.
+ *
+ */
+ public abstract void clickFmFromMenu();
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * This method is used to tune station manually.
+ *
+ * @param stationType - to select AM or FM.
+ * @param band - band to tune in.
+ *
+ */
+ public abstract void setStation(String stationType,double band);
+
+ /**
+ * Setup expectations: Radio app is open.
+ *
+ * @return to get current playing station band with Am or Fm.
+ */
+ public abstract String getStationBand();
+
+}
diff --git a/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoUiProviderHelper.java b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoUiProviderHelper.java
new file mode 100644
index 0000000..40b14e4
--- /dev/null
+++ b/libraries/app-helpers/auto/src/android/platform/test/helpers/auto/AbstractAutoUiProviderHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.platform.test.helpers;
+
+
+import android.app.Instrumentation;
+
+/**
+ * AbstractAutoUiProviderHelper used to open menu in different applications like Dial,
+ * Bluetooth Media and Local Media player.
+ */
+
+public abstract class AbstractAutoUiProviderHelper extends AbstractStandardAppHelper {
+
+ public AbstractAutoUiProviderHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * Setup expectations: The applications like dial,Media should be open.
+ *
+ * This method is used to open menu in different applications like Dial and Media.
+ */
+ public abstract void openMenu();
+
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/app-helpers/base/Android.mk
similarity index 86%
rename from libraries/base-app-helpers/Android.mk
rename to libraries/app-helpers/base/Android.mk
index 9e797fb..0e4e7c0 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/app-helpers/base/Android.mk
@@ -16,9 +16,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := app-helpers-base
+LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractStandardAppHelper.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/AbstractStandardAppHelper.java
similarity index 78%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractStandardAppHelper.java
rename to libraries/app-helpers/base/src/android/platform/test/helpers/base/AbstractStandardAppHelper.java
index ea8e46a..6d0301c 100644
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractStandardAppHelper.java
+++ b/libraries/app-helpers/base/src/android/platform/test/helpers/base/AbstractStandardAppHelper.java
@@ -23,13 +23,22 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
import android.platform.test.helpers.exceptions.AccountException;
import android.support.test.launcherhelper.ILauncherStrategy;
import android.support.test.launcherhelper.LauncherStrategyFactory;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
public abstract class AbstractStandardAppHelper implements IStandardAppHelper {
+ private static final String SCREENSHOT_DIR = "apphelper-screenshots";
+
+ private static File sScreenshotDirectory;
+
public UiDevice mDevice;
public Instrumentation mInstrumentation;
public ILauncherStrategy mLauncherStrategy;
@@ -123,4 +132,26 @@
}
return false;
}
+
+ @Override
+ public boolean captureScreenshot(String name) throws IOException {
+ File scrOut = File.createTempFile(name, ".png", getScreenshotDirectory());
+ File uixOut = File.createTempFile(name, ".uix", getScreenshotDirectory());
+ mDevice.dumpWindowHierarchy(uixOut);
+ return mDevice.takeScreenshot(scrOut);
+ }
+
+ private static File getScreenshotDirectory() {
+ if (sScreenshotDirectory == null) {
+ File storage = Environment.getExternalStorageDirectory();
+ sScreenshotDirectory = new File(storage, SCREENSHOT_DIR);
+ if (!sScreenshotDirectory.exists()) {
+ if (!sScreenshotDirectory.mkdirs()) {
+ throw new RuntimeException(
+ "Failed to create a screenshot directory.");
+ }
+ }
+ }
+ return sScreenshotDirectory;
+ }
}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/HelperManager.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/HelperManager.java
similarity index 86%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/HelperManager.java
rename to libraries/app-helpers/base/src/android/platform/test/helpers/base/HelperManager.java
index 533b0b2..11b6178 100644
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/HelperManager.java
+++ b/libraries/app-helpers/base/src/android/platform/test/helpers/base/HelperManager.java
@@ -16,6 +16,8 @@
package android.platform.test.helpers;
+import static java.lang.reflect.Modifier.isAbstract;
+
import android.app.Instrumentation;
import android.content.Context;
import android.util.Log;
@@ -135,7 +137,26 @@
* @return a concrete implementation of base
*/
public <T extends AbstractStandardAppHelper> T get(Class<T> base) {
+ List<T> implementations = getAll(base);
+
+ if (implementations.size() > 0) {
+ return implementations.get(0);
+ } else {
+ throw new RuntimeException(
+ String.format("Failed to find an implementation for %s", base.toString()));
+ }
+ }
+
+ /**
+ * Returns all concrete implementations of the helper interface supplied.
+ *
+ * @param base the interface base class to find an implementation for
+ * @return a list of all concrete implementations we could find
+ */
+ public <T extends AbstractStandardAppHelper> List<T> getAll(Class<T> base) {
ClassLoader loader = HelperManager.class.getClassLoader();
+ List<T> implementations = new ArrayList<>();
+
// Iterate and search for the implementation
for (String className : mClasses) {
Class<?> clazz = null;
@@ -143,12 +164,16 @@
clazz = loader.loadClass(className);
} catch (ClassNotFoundException e) {
Log.w(LOG_TAG, String.format("Class not found: %s", className));
+ continue;
}
- if (base.isAssignableFrom(clazz) && !clazz.equals(base)) {
+ if (base.isAssignableFrom(clazz) &&
+ !clazz.equals(base) &&
+ !isAbstract(clazz.getModifiers())) {
+
// Instantiate the implementation class and return
try {
Constructor<?> constructor = clazz.getConstructor(Instrumentation.class);
- return (T)constructor.newInstance(mInstrumentation);
+ implementations.add((T)constructor.newInstance(mInstrumentation));
} catch (NoSuchMethodException e) {
Log.w(LOG_TAG, String.format("Failed to find a matching constructor for %s",
className), e);
@@ -164,7 +189,7 @@
}
}
}
- throw new RuntimeException(
- String.format("Failed to find an implementation for %s", base.toString()));
+
+ return implementations;
}
}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/IStandardAppHelper.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/IStandardAppHelper.java
similarity index 70%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/IStandardAppHelper.java
rename to libraries/app-helpers/base/src/android/platform/test/helpers/base/IStandardAppHelper.java
index 9658429..fce833e 100644
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/IStandardAppHelper.java
+++ b/libraries/app-helpers/base/src/android/platform/test/helpers/base/IStandardAppHelper.java
@@ -18,6 +18,8 @@
import android.content.pm.PackageManager.NameNotFoundException;
+import java.io.IOException;
+
public interface IStandardAppHelper {
/**
@@ -38,14 +40,14 @@
/**
* Setup expectations: This application is on the initial launch screen.
* <p>
- * This method will dismiss all visible relevant dialogs and block until this process is
- * complete.
+ * Dismiss all visible relevant dialogs and block until this process is complete.
*/
abstract void dismissInitialDialogs();
/**
* Setup expectations: None
* <p>
+ * Get the target application's component package.
* @return the package name for this helper's application.
*/
abstract String getPackage();
@@ -53,24 +55,33 @@
/**
* Setup expectations: None.
* <p>
- * @return the name for this application in the launcher.
+ * Get the target application's launcher name.
+ * @return the name of this application's launcher.
*/
abstract String getLauncherName();
/**
* Setup expectations: None
* <p>
- * This method will return the version String from PackageManager.
- *
- * @return the version as a String
- * @throws NameNotFoundException if the package is not found in PM
+ * Get the target application's version String.
+ * @return the version code
+ * @throws NameNotFoundException if {@code getPackage} is not found
*/
abstract String getVersion() throws NameNotFoundException;
/**
* Setup expectations: None
- * <p>
* @return true, if this app's package is the root (depth 0), and false otherwise
*/
abstract boolean isAppInForeground();
+
+ /**
+ * Setup expectations: None
+ * <p>
+ * Captures a screenshot and UI XML with the supplied name.
+ * @param name the screenshot prefix
+ * @throws IOException if there is a capture failure
+ * @throws RuntimeException if creating the screenshot directory fails.
+ */
+ abstract boolean captureScreenshot(String name) throws IOException;
}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/exceptions/AccountException.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/exceptions/AccountException.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/exceptions/AccountException.java
rename to libraries/app-helpers/base/src/android/platform/test/helpers/base/exceptions/AccountException.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/exceptions/UiTimeoutException.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/exceptions/UiTimeoutException.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/exceptions/UiTimeoutException.java
rename to libraries/app-helpers/base/src/android/platform/test/helpers/base/exceptions/UiTimeoutException.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/exceptions/UnknownUiException.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/exceptions/UnknownUiException.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/exceptions/UnknownUiException.java
rename to libraries/app-helpers/base/src/android/platform/test/helpers/base/exceptions/UnknownUiException.java
diff --git a/libraries/app-helpers/base/src/android/platform/test/helpers/base/listeners/FailureTestWatcher.java b/libraries/app-helpers/base/src/android/platform/test/helpers/base/listeners/FailureTestWatcher.java
new file mode 100644
index 0000000..dde0812
--- /dev/null
+++ b/libraries/app-helpers/base/src/android/platform/test/helpers/base/listeners/FailureTestWatcher.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.helpers.listeners;
+
+import android.platform.test.helpers.IStandardAppHelper;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.IOException;
+
+/**
+ * A JUnit {@code TestWatcher} for collecting screenshots and UI XML files on test failure.
+ */
+public class FailureTestWatcher extends TestWatcher {
+ private static final String LOG_TAG = FailureTestWatcher.class.getSimpleName();
+ private static final String SCREENSHOT_NAME_FORMAT = "%s_%s";
+
+ @Nullable
+ private IStandardAppHelper mHelper;
+
+ public void setHelper (IStandardAppHelper helper) {
+ mHelper = helper;
+ }
+
+ @Override
+ protected void failed(Throwable e, Description description) {
+ try {
+ if (!mHelper.captureScreenshot(String.format(SCREENSHOT_NAME_FORMAT,
+ description.getClassName(), description.getMethodName()))) {
+ Log.e(LOG_TAG, "Failed to capture a screenshot for unknown reasons.");
+ }
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Failed to capture a screenshot.", ioe);
+ }
+ }
+}
diff --git a/libraries/app-helpers/clockwork/Android.mk b/libraries/app-helpers/clockwork/Android.mk
index 879280e..abb2d44 100644
--- a/libraries/app-helpers/clockwork/Android.mk
+++ b/libraries/app-helpers/clockwork/Android.mk
@@ -17,8 +17,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := clockwork-app-helper-base
-LOCAL_STATIC_JAVA_LIBRARIES := base-app-helpers
-LOCAL_JAVA_LIBRARIES := launcher-helper-lib
+LOCAL_STATIC_JAVA_LIBRARIES := app-helpers-common
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/app-helpers/common/Android.mk
similarity index 86%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/app-helpers/common/Android.mk
index 9e797fb..8762439 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/app-helpers/common/Android.mk
@@ -16,9 +16,9 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := app-helpers-common
+LOCAL_STATIC_JAVA_LIBRARIES := app-helpers-base
+LOCAL_JAVA_LIBRARIES := ub-uiautomator
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleFitHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractGoogleFitHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleFitHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractGoogleFitHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleKeyboardHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractGoogleKeyboardHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleKeyboardHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractGoogleKeyboardHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayMoviesHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayMoviesHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayMoviesHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayMoviesHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayMusicHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayMusicHelper.java
similarity index 96%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayMusicHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayMusicHelper.java
index e2e8299..7002053 100644
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayMusicHelper.java
+++ b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayMusicHelper.java
@@ -35,10 +35,10 @@
/**
* Setup expectations: PlayMusic is open and the navigation bar is visible.
*
- * This method will open the navigation bar, press "Listen Now".
+ * This method will open the navigation bar, press "Home".
* This method blocks until the process is complete.
*/
- public abstract void goToListenNow();
+ public abstract void goToHome();
/**
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayStoreHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayStoreHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayStoreHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractPlayStoreHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractRecentsHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractRecentsHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractRecentsHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractRecentsHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractYouTubeHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractYouTubeHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractYouTubeHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/AbstractYouTubeHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/IRecentsHelper.java b/libraries/app-helpers/common/src/android/platform/test/helpers/common/IRecentsHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/IRecentsHelper.java
rename to libraries/app-helpers/common/src/android/platform/test/helpers/common/IRecentsHelper.java
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/app-helpers/handheld/Android.mk
similarity index 71%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/app-helpers/handheld/Android.mk
index 9e797fb..85f324d 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/app-helpers/handheld/Android.mk
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,13 +16,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := handheld-app-helper-base
+LOCAL_STATIC_JAVA_LIBRARIES := app-helpers-common ub-uiautomator
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractChromeHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractChromeHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractChromeHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractChromeHelper.java
diff --git a/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractDownloadsHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractDownloadsHelper.java
new file mode 100644
index 0000000..a0ae37c
--- /dev/null
+++ b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractDownloadsHelper.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.platform.test.helpers;
+
+import android.app.Instrumentation;
+
+public abstract class AbstractDownloadsHelper extends AbstractStandardAppHelper {
+
+ public static enum Category {
+ AUDIO,
+ IMAGES,
+ RECENT,
+ VIDEOS
+ }
+
+ public AbstractDownloadsHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * Setup expectation: Downloads app's Navigation Drawer is open
+ * <p>
+ * This method will select an item from the navigation drawer's list
+ *
+ * @param category - menu item to select (click)
+ */
+ public abstract void selectMenuCategory(Category category);
+
+ /**
+ * Setup expectation: Item has been selected from Navigation Drawer's list
+ * <p>
+ * This method opens a directory from the directories list
+ *
+ * @param directoryName - name of directory to open
+ */
+ public abstract void selectDirectory(String directoryName);
+
+ /**
+ * Setup expectation: Navigated to the right folder
+ * <p>
+ * This method clicks a specific file with name 'filename'
+ *
+ * @param filename - name of file to open
+ */
+ public abstract void openFile(String filename);
+
+ /**
+ * Setup expectation: Video is playing
+ * <p>
+ * This method will wait for the video to stop playing or until timeoutInSeconds occur,
+ * whichever comes first. Function will just exit, no test failure in either case.
+ *
+ * @param timeoutInSeconds - timeout value in seconds the test will wait for video to end
+ */
+ public abstract void waitForVideoToStopPlaying(long timeoutInSeconds);
+
+ /**
+ * Setup expectation: Audio is playing
+ * <p>
+ * This method will wait for the audio to stop playing or until timeoutInSeconds occur,
+ * whichever comes first. Function will just exit, no test failure in either case.
+ *
+ * @param timeoutInSeconds - timeout value in seconds the test will wait for audio to end
+ */
+ public abstract void waitForAudioToStopPlaying(long timeoutInSeconds);
+
+ /**
+ * Setup expectation: Video is playing
+ * <p>
+ * This method will enable or disable video looping. It will bring up the options menu and
+ * check the "Loop video" option
+ *
+ * @param enableVideoLooping - true for continuous looping video, false for not looping video
+ */
+ public abstract void enableVideoLooping(boolean enableVideoLooping);
+}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGmailHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGmailHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGmailHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGmailHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleCameraHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGoogleCameraHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleCameraHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGoogleCameraHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleDocsHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGoogleDocsHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleDocsHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGoogleDocsHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleMessengerHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGoogleMessengerHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractGoogleMessengerHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractGoogleMessengerHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractMapsHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractMapsHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractMapsHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractMapsHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPhotosHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractPhotosHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPhotosHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractPhotosHelper.java
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayBooksHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractPlayBooksHelper.java
similarity index 100%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractPlayBooksHelper.java
rename to libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractPlayBooksHelper.java
diff --git a/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractSystemUpdateHelper.java b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractSystemUpdateHelper.java
new file mode 100644
index 0000000..8c157d2
--- /dev/null
+++ b/libraries/app-helpers/handheld/src/android/platform/test/helpers/handheld/AbstractSystemUpdateHelper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.platform.test.helpers;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+
+public abstract class AbstractSystemUpdateHelper extends AbstractStandardAppHelper {
+
+ protected final static String SYSTEM_UPDATE = "android.settings.SYSTEM_UPDATE_SETTINGS";
+ public AbstractSystemUpdateHelper(Instrumentation instr) {
+ super(instr);
+ }
+
+ /**
+ * As System Update is a subcomponent of Settings, it will not appear in the launcher.
+ * It needs to be opened directly via its activity.
+ */
+ @Override
+ public void open() {
+ Intent startIntent = new Intent(SYSTEM_UPDATE);
+ startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mInstrumentation.getContext().startActivity(
+ startIntent);
+ try {
+ // wait for app to open
+ Thread.sleep(7000);
+ } catch (InterruptedException ignored) {
+ // do nothing
+ }
+ }
+
+ /**
+ * Check whether or not an update is available
+ * @return true if the device has an update available, false otherwise.
+ */
+ public abstract boolean isUpdateAvailable();
+
+ /**
+ * If an update is available, download it. Otherwise, throw {@link IllegalStateException}.
+ * Precondition: The device is on the System Update screen.
+ * Postcondition: A system update will be ready to install.
+ * @return true if the download succeeded
+ */
+ public abstract boolean downloadUpdate();
+
+ /**
+ * Click on an existing OTA notification.
+ * Precondition: The notification drawer is open and an OTA notification exists.
+ * Postcondition: The device is on the System Update screen.
+ */
+ public abstract void clickOtaNotification();
+
+ /**
+ * Check whether or not an OTA notification is present
+ * @return true if an OTA notification is in the notification drawer, false otherwise.
+ */
+ public abstract boolean hasOtaNotification();
+
+ /**
+ * Check whether or not an attempted OTA download is completed
+ * @return true if an OTA is ready to install, false otherwise
+ */
+ public abstract boolean isOtaDownloadCompleted();
+
+ /**
+ * Install an OTA. This will cause the device to power off.
+ * Precondition: A system update is ready to install.
+ * Postcondition: The device will reboot.
+ * @return true if the "Install" button was successfully clicked, false otherwise
+ */
+ public abstract boolean installOta();
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/app-helpers/tv/Android.mk
similarity index 77%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/app-helpers/tv/Android.mk
index 9e797fb..4d05a2b 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/app-helpers/tv/Android.mk
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,13 +16,9 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
+LOCAL_MODULE := tv-app-helper-base
+LOCAL_STATIC_JAVA_LIBRARIES := app-helpers-common dpad-util
LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractLeanbackAppHelper.java b/libraries/app-helpers/tv/src/android/platform/test/helpers/tv/AbstractLeanbackAppHelper.java
similarity index 85%
rename from libraries/base-app-helpers/src/android/platform/test/helpers/AbstractLeanbackAppHelper.java
rename to libraries/app-helpers/tv/src/android/platform/test/helpers/tv/AbstractLeanbackAppHelper.java
index f8ea94f..9656412 100644
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractLeanbackAppHelper.java
+++ b/libraries/app-helpers/tv/src/android/platform/test/helpers/tv/AbstractLeanbackAppHelper.java
@@ -41,6 +41,7 @@
private static final int OPEN_SIDE_PANEL_MAX_ATTEMPTS = 5;
private static final long MAIN_ACTIVITY_WAIT_TIME_MS = 250;
private static final long SELECT_WAIT_TIME_MS = 5000;
+ private static final long FOCUS_WAIT_TIME_MS = 100;
// The notable widget classes in Leanback Library
public enum Widget {
@@ -63,6 +64,7 @@
mDPadUtil = new DPadUtil(instr);
mLauncherStrategy = LauncherStrategyFactory.getInstance(
mDevice).getLeanbackLauncherStrategy();
+ mLauncherStrategy.setInstrumentation(instr);
}
/**
@@ -213,16 +215,13 @@
if (container == null) {
throw new IllegalArgumentException("The container should not be null.");
}
- UiObject2 focus = container.findObject(By.focused(true));
- if (focus == null) {
- throw new UnknownUiException("The container should have a focused descendant.");
- }
+ UiObject2 focus = waitForFocus(container, FOCUS_WAIT_TIME_MS, true);
while (!focus.hasObject(target)) {
UiObject2 prev = focus;
mDPadUtil.pressDPad(direction);
focus = container.findObject(By.focused(true));
if (focus == null) {
- mDPadUtil.pressDPad(Direction.reverse(direction));
+ mDPadUtil.pressDPad(direction);
focus = container.findObject(By.focused(true));
}
if (focus.equals(prev)) {
@@ -233,6 +232,33 @@
return focus;
}
+ public UiObject2 waitForFocus(UiObject2 object) {
+ // By default this method returns immediately and throws an Exception if no focus is found
+ return waitForFocus(object, 0, true);
+ }
+
+ /**
+ * Wait for given object to have an element that is focused.
+ * @param object {@link UiObject2} under which it searches for an element that is focused.
+ * Null if it searches through the entire hierarchy of accessibility nodes
+ * @param timeoutMs Maximum amount of time to wait in milliseconds
+ * @param throwIfFail Throw {@link IllegalStateException} if no element under the object is
+ * focused in a given timeout
+ * @return The {@link UiObject2} that has a focused element, or null if no focus
+ */
+ public UiObject2 waitForFocus(UiObject2 object, long timeoutMs, boolean throwIfFail) {
+ UiObject2 focused;
+ if (object == null) {
+ focused = mDevice.wait(Until.findObject(By.focused(true)), timeoutMs);
+ } else {
+ focused = object.wait(Until.findObject(By.focused(true)), timeoutMs);
+ }
+ if (throwIfFail && focused == null) {
+ throw new IllegalStateException("The object should have a focused descendant.");
+ }
+ return focused;
+ }
+
/**
* Setup expectation: On guided fragment.
* <p>
@@ -388,24 +414,42 @@
return mDevice.wait(Until.hasObject(getBrowseHeadersSelector()), timeoutMs);
}
+ /**
+ * Attempts to select a given header text three times with the backoff timeout each retry.
+ * The timeout needs to be long enough if it runs under low bandwidth environments.
+ */
protected UiObject2 selectHeader(String headerName) {
+ long waitMs = 10 * 1000; // 10 sec
+ int maxAttempts = 3;
+ UiObject2 header;
+ while (maxAttempts-- > 0) {
+ header = selectHeader(headerName, waitMs);
+ if (header != null) {
+ return header;
+ }
+ waitMs = 2 * waitMs;
+ }
+ throw new UnknownUiException("Failed to select header : " + headerName);
+ }
+
+ protected UiObject2 selectHeader(String headerName, long waitMs) {
UiObject2 container = mDevice.wait(
Until.findObject(getBrowseHeadersSelector()), OPEN_HEADER_WAIT_TIME_MS);
BySelector header = By.clazz(".TextView").text(headerName);
- // Wait until the row header text appears at runtime. This needs to be long enough to run
- // under low bandwidth environments in the test lab.
- mDevice.wait(Until.findObject(header), 60 * 1000);
+ // Wait until the row header text appears on screen at runtime.
+ mDevice.wait(Until.findObject(header), waitMs);
- // Search up, then down
- UiObject2 focused = select(container, header, Direction.UP);
+ // Search down, then up to select the header
+ UiObject2 focused = select(container, header, Direction.DOWN);
if (focused != null) {
return focused;
}
- focused = select(container, header, Direction.DOWN);
+ focused = select(container, header, Direction.UP);
if (focused != null) {
return focused;
}
- throw new UnknownUiException("Failed to select header");
+ return null;
}
+
}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/AuptTestCase.java b/libraries/aupt-lib/src/android/support/test/aupt/AuptTestCase.java
index 7755fc7..0b6daab 100644
--- a/libraries/aupt-lib/src/android/support/test/aupt/AuptTestCase.java
+++ b/libraries/aupt-lib/src/android/support/test/aupt/AuptTestCase.java
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
@@ -37,13 +38,11 @@
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
-import android.os.Environment;
import android.os.SystemClock;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
@@ -58,346 +57,22 @@
* Base class for AuptTests.
*/
public class AuptTestCase extends InstrumentationTestCase {
- private static final String SDCARD =
- Environment.getExternalStorageDirectory().getAbsolutePath();
- private static final String RECORD_MEMINFO_PARAM = "record_meminfo";
- private static final long DEFAULT_SHORT_SLEEP = 5 * 1000;
- private static final long DEFAULT_LONG_SLEEP = 30 * 1000;
- protected static final int STEPS_BACK = 10;
- protected static final int RECOVERY_SLEEP = 2000;
- private static final String TAG = AuptTestCase.class.getSimpleName();
+ /* Constants */
+ static final int STEPS_BACK = 10;
+ static final int RECOVERY_SLEEP = 2000;
+ static final long DEFAULT_SHORT_SLEEP = 5 * 1000;
+ static final long DEFAULT_LONG_SLEEP = 30 * 1000;
+ static final String TAG = AuptTestCase.class.getSimpleName();
- private boolean mRecordMeminfo = false;
+ /* State */
private UiWatchers mWatchers;
- private IProcessStatusTracker mProcessStatusTracker;
- private DataCollector mDataCollector;
- private List<String> mPaths;
-
- // We want to periodically collect dumpheap output if the process grows to large to proactivelly
- // help with catching memory leaks, but don't want to do it too often so it does not disturb the
- // test. We are going to limit the total number of dumpheap commands per proccess to 5 by
- // default, rate limit it to one per hour be default, and only do it for monitored processes
- // which grow larger than a certain size (200MB by default).
- private boolean mDumpheapEnabled = false;
- private long mDumpheapThreshold;
- private long mDumpheapInterval ;
- private long mMaxDumpheaps;
- private Map<String, Long> mLastDumpheap = new HashMap<String, Long>();
- private Map<String, Long> mDumpheapCount = new HashMap<String, Long>();
-
private UiDevice mDevice;
- public static class MemHealthRecord {
- public enum Context { FOREGROUND, BACKGROUND };
+ /* ******************* InstrumentationTestCase Hooks ******************* */
- private final long mTimeMs;
- private final long mDalvikHeap;
- private final long mNativeHeap;
- private final long mPss;
-
- // App summary metrics
- private final long mAsJavaHeap; // Private java heap
- private final long mAsNativeHeap; // Private native heap
- private final long mAsCode; // Private code
- private final long mAsStack; // Private stack
- private final long mAsGraphics; // Private graphics
- private final long mAsOther; // Private other
- private final long mAsSystem; // System
- private final long mAsOverallPss; // Overall PSS
-
- private final Context mContext;
-
- public MemHealthRecord(long timeMs, long dalvikHeap, long nativeHeap, long pss,
- long asJavaHeap, long asNativeHeap, long asCode, long asStack,
- long asGraphics, long asOther, long asSystem, long asOverallPss,
- Context context) {
- mTimeMs = timeMs;
- mDalvikHeap = dalvikHeap;
- mNativeHeap = nativeHeap;
- mPss = pss;
- mAsJavaHeap = asJavaHeap;
- mAsNativeHeap = asNativeHeap;
- mAsCode = asCode;
- mAsStack = asStack;
- mAsGraphics = asGraphics;
- mAsOther = asOther;
- mAsSystem = asSystem;
- mAsOverallPss = asOverallPss;
- mContext = context;
- }
-
- public MemHealthRecord(long timeMs, long dalvikHeap, long nativeHeap, long pss,
- Context context) {
- mTimeMs = timeMs;
- mDalvikHeap = dalvikHeap;
- mNativeHeap = nativeHeap;
- mPss = pss;
- mAsJavaHeap = 0;
- mAsNativeHeap = 0;
- mAsCode = 0;
- mAsStack = 0;
- mAsGraphics = 0;
- mAsOther = 0;
- mAsSystem = 0;
- mAsOverallPss = 0;
- mContext = context;
- }
-
- public static List<Long> getForegroundDalvikHeap(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mDalvikHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundDalvikHeap(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mDalvikHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundNativeHeap(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mNativeHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundNativeHeap(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mNativeHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundPss(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mPss);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundPss(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mPss);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryJavaHeap(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsJavaHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryJavaHeap(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsJavaHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryNativeHeap(
- Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsNativeHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryNativeHeap(
- Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsNativeHeap);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryCode(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsCode);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryCode(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsCode);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryStack(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsStack);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryStack(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsStack);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryGraphics(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsGraphics);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryGraphics(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsGraphics);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryOther(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsOther);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryOther(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsOther);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummarySystem(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsSystem);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummarySystem(Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsSystem);
- }
- }
- return ret;
- }
-
- public static List<Long> getForegroundSummaryOverallPss(
- Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.FOREGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsOverallPss);
- }
- }
- return ret;
- }
-
- public static List<Long> getBackgroundSummaryOverallPss(
- Collection<MemHealthRecord> samples) {
- List<Long> ret = new ArrayList<>(samples.size());
- for (MemHealthRecord sample : samples) {
- if (Context.BACKGROUND.equals(sample.mContext)) {
- ret.add(sample.mAsOverallPss);
- }
- }
- return ret;
- }
-
- private static Long getMax(Collection<Long> samples) {
- Long max = null;
- for (Long sample : samples) {
- if (max == null || sample > max) {
- max = sample;
- }
- }
- return max;
- }
-
- private static Long getAverage(Collection<Long> samples) {
- if (samples.size() == 0) {
- return null;
- }
-
- double sum = 0;
- for (Long sample : samples) {
- sum += sample;
- }
- return (long) (sum / samples.size());
- }
- }
-
- private Map<String, List<MemHealthRecord>> mMemHealthRecords;
- private String[] mProcsToTrack;
- private String mResultsDirectory;
-
- public void setMemHealthRecords(Map<String, List<MemHealthRecord>> records) {
- mMemHealthRecords = records;
- }
-
+ /**
+ * {@inheritDoc}
+ */
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -405,47 +80,9 @@
mDevice = UiDevice.getInstance(getInstrumentation());
mWatchers = new UiWatchers();
mWatchers.registerAnrAndCrashWatchers(getInstrumentation());
+
mDevice.registerWatcher("LockScreenWatcher", new LockScreenWatcher());
- mRecordMeminfo = "true".equals(getParams().getString(RECORD_MEMINFO_PARAM, "false"));
-
mDevice.setOrientationNatural();
-
- mResultsDirectory = SDCARD + "/" + getParams().getString(
- "outputLocation", "aupt_results");
-
- String processes = getParams().getString("trackMemory", null);
- if (processes != null) {
- mProcsToTrack = processes.split(",");
- } else {
- readProcessesFromFile();
- }
-
- mDumpheapEnabled = "true".equals(getParams().getString("enableDumpheap"));
- if (mDumpheapEnabled) {
- mDumpheapThreshold = getLongParam("dumpheapThreshold", 200 * 1024 * 1024); // 200MB
- mDumpheapInterval = getLongParam("dumpheapInterval", 60 * 60 * 1000); // one hour
- mMaxDumpheaps = getLongParam("maxDumpheaps", 5);
- }
- }
-
- private void readProcessesFromFile() {
- File trackFile = new File(SDCARD + "/track_memory.txt");
- if (trackFile.exists()) {
- BufferedReader in = null;
- try {
- in = new BufferedReader(new InputStreamReader(new FileInputStream(trackFile)));
- String processes = in.readLine();
- in.close();
-
- if (!"".equals(processes)) {
- mProcsToTrack = processes.split(",");
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Error opening track file", e);
- } catch (IOException e) {
- Log.e(TAG, "Error opening track file", e);
- }
- }
}
/**
@@ -456,23 +93,52 @@
mDevice.removeWatcher("LockScreenWatcher");
mDevice.unfreezeRotation();
- saveMemoryStats();
-
super.tearDown();
}
- private class LockScreenWatcher implements UiWatcher {
+ /* ******************* Device Methods ******************* */
- @Override
- public boolean checkForCondition() {
- if (mDevice.hasObject(By.desc("Slide area."))) {
- mDevice.pressMenu();
- return true;
- }
- return false;
+ /**
+ * @deprecated you should be using an AppHelper library to do this.
+ */
+ @Deprecated
+ protected UiDevice getUiDevice() {
+ return mDevice;
+ }
+
+ /**
+ * @deprecated you should be using an AppHelper library to do this.
+ */
+ @Deprecated
+ public void launchIntent(Intent intent) {
+ getInstrumentation().getContext().startActivity(intent);
+ }
+
+ /**
+ * Press back button repeatedly in order to attempt to bring the app back to home screen.
+ * This is intended so that an app can recover if the previous session left an app in a weird
+ * state.
+ *
+ * @deprecated you should be using an AppHelper library to do this.
+ */
+ @Deprecated
+ public void navigateToHome() {
+ int iterations = 0;
+ String launcherPkg = mDevice.getLauncherPackageName();
+ while (!launcherPkg.equals(mDevice.getCurrentPackageName())
+ && iterations < STEPS_BACK) {
+ mDevice.pressBack();
+ SystemClock.sleep(RECOVERY_SLEEP);
+ iterations++;
}
}
+ /* ******************* Parameter Accessors ******************* */
+
+ protected Bundle getParams() {
+ return ((InstrumentationTestRunner)getInstrumentation()).getArguments();
+ }
+
/**
* Looks up a parameter or returns a default value if parameter is not
* present.
@@ -507,313 +173,18 @@
}
/**
- * Press back button repeatedly in order to attempt to bring the app back to home screen.
- * This is intended so that an app can recover if the previous session left an app in a weird
- * state.
+ * @return the jar-file arguments of this AUPT test run
*/
- public void navigateToHome() {
- int iterations = 0;
- String launcherPkg = mDevice.getLauncherPackageName();
- while (!launcherPkg.equals(mDevice.getCurrentPackageName())
- && iterations < STEPS_BACK) {
- mDevice.pressBack();
- SystemClock.sleep(RECOVERY_SLEEP);
- iterations++;
- }
+ public List<String> getDexedJarPaths() {
+ return DexTestRunner.parseDexedJarPaths(getParams().getString("jars", ""));
}
/**
- * Writes out condensed memory data about the running processes.
- * @param notes about when the dump was taken.
+ * @return the version corresponding to a given package name
+ *
+ * @deprecated you should be using an AppHelper library to do this.
*/
- public void dumpMemInfo(String notes) {
- if (mRecordMeminfo) {
- mDevice.waitForIdle();
- mDataCollector.dumpMeminfo(notes);
- }
- if (mProcsToTrack != null) {
- recordMemoryUsage();
- }
- }
-
- private void saveMemoryStats() {
- if (mProcsToTrack == null) {
- return;
- }
- try {
- PrintWriter out = new PrintWriter(new BufferedWriter(
- new FileWriter(mResultsDirectory + "/memory-health.txt")));
- out.println("Foreground");
- for (Map.Entry<String, List<MemHealthRecord>> record : mMemHealthRecords.entrySet()) {
- List<Long> nativeHeap = MemHealthRecord.getForegroundNativeHeap(record.getValue());
- List<Long> dalvikHeap = MemHealthRecord.getForegroundDalvikHeap(record.getValue());
- List<Long> pss = MemHealthRecord.getForegroundPss(record.getValue());
-
- List<Long> asJavaHeap = MemHealthRecord.getForegroundSummaryJavaHeap(
- record.getValue());
- List<Long> asNativeHeap = MemHealthRecord.getForegroundSummaryNativeHeap(
- record.getValue());
- List<Long> asCode = MemHealthRecord.getForegroundSummaryCode(record.getValue());
- List<Long> asStack = MemHealthRecord.getForegroundSummaryStack(record.getValue());
- List<Long> asGraphics = MemHealthRecord.getForegroundSummaryGraphics(
- record.getValue());
- List<Long> asOther = MemHealthRecord.getForegroundSummaryOther(record.getValue());
- List<Long> asSystem = MemHealthRecord.getForegroundSummarySystem(record.getValue());
- List<Long> asOverallPss = MemHealthRecord.getForegroundSummaryOverallPss(
- record.getValue());
-
- // nativeHeap, dalvikHeap, and pss all have the same size, just use one
- if (nativeHeap.size() == 0) {
- continue;
- }
-
- out.println(record.getKey());
- out.printf("Average Native Heap: %d\n", MemHealthRecord.getAverage(nativeHeap));
- out.printf("Average Dalvik Heap: %d\n", MemHealthRecord.getAverage(dalvikHeap));
- out.printf("Average PSS: %d\n", MemHealthRecord.getAverage(pss));
- out.printf("Peak Native Heap: %d\n", MemHealthRecord.getMax(nativeHeap));
- out.printf("Peak Dalvik Heap: %d\n", MemHealthRecord.getMax(dalvikHeap));
- out.printf("Peak PSS: %d\n", MemHealthRecord.getMax(pss));
- out.printf("Count %d\n", nativeHeap.size());
-
- out.printf("Average Summary Java Heap: %d\n", MemHealthRecord.getAverage(
- asJavaHeap));
- out.printf("Average Summary Native Heap: %d\n", MemHealthRecord.getAverage(
- asNativeHeap));
- out.printf("Average Summary Code: %d\n", MemHealthRecord.getAverage(asCode));
- out.printf("Average Summary Stack: %d\n", MemHealthRecord.getAverage(asStack));
- out.printf("Average Summary Graphics: %d\n", MemHealthRecord.getAverage(
- asGraphics));
- out.printf("Average Summary Other: %d\n", MemHealthRecord.getAverage(asOther));
- out.printf("Average Summary System: %d\n", MemHealthRecord.getAverage(asSystem));
- out.printf("Average Summary Overall Pss: %d\n", MemHealthRecord.getAverage(
- asOverallPss));
- }
- out.println("Background");
- for (Map.Entry<String, List<MemHealthRecord>> record : mMemHealthRecords.entrySet()) {
- List<Long> nativeHeap = MemHealthRecord.getBackgroundNativeHeap(record.getValue());
- List<Long> dalvikHeap = MemHealthRecord.getBackgroundDalvikHeap(record.getValue());
- List<Long> pss = MemHealthRecord.getBackgroundPss(record.getValue());
-
- List<Long> asJavaHeap = MemHealthRecord.getBackgroundSummaryJavaHeap(
- record.getValue());
- List<Long> asNativeHeap = MemHealthRecord.getBackgroundSummaryNativeHeap(
- record.getValue());
- List<Long> asCode = MemHealthRecord.getBackgroundSummaryCode(record.getValue());
- List<Long> asStack = MemHealthRecord.getBackgroundSummaryStack(record.getValue());
- List<Long> asGraphics = MemHealthRecord.getBackgroundSummaryGraphics(
- record.getValue());
- List<Long> asOther = MemHealthRecord.getBackgroundSummaryOther(record.getValue());
- List<Long> asSystem = MemHealthRecord.getBackgroundSummarySystem(record.getValue());
- List<Long> asOverallPss = MemHealthRecord.getBackgroundSummaryOverallPss(
- record.getValue());
-
- // nativeHeap, dalvikHeap, and pss all have the same size, just use one
- if (nativeHeap.size() == 0) {
- continue;
- }
-
- out.println(record.getKey());
- out.printf("Average Native Heap: %d\n", MemHealthRecord.getAverage(nativeHeap));
- out.printf("Average Dalvik Heap: %d\n", MemHealthRecord.getAverage(dalvikHeap));
- out.printf("Average PSS: %d\n", MemHealthRecord.getAverage(pss));
- out.printf("Peak Native Heap: %d\n", MemHealthRecord.getMax(nativeHeap));
- out.printf("Peak Dalvik Heap: %d\n", MemHealthRecord.getMax(dalvikHeap));
- out.printf("Peak PSS: %d\n", MemHealthRecord.getMax(pss));
- out.printf("Count %d\n", nativeHeap.size());
-
- out.printf("Average Summary Java Heap: %d\n", MemHealthRecord.getAverage(
- asJavaHeap));
- out.printf("Average Summary Native Heap: %d\n", MemHealthRecord.getAverage(
- asNativeHeap));
- out.printf("Average Summary Code: %d\n", MemHealthRecord.getAverage(asCode));
- out.printf("Average Summary Stack: %d\n", MemHealthRecord.getAverage(asStack));
- out.printf("Average Summary Graphics: %d\n", MemHealthRecord.getAverage(
- asGraphics));
- out.printf("Average Summary Other: %d\n", MemHealthRecord.getAverage(asOther));
- out.printf("Average Summary System: %d\n", MemHealthRecord.getAverage(asSystem));
- out.printf("Average Summary Overall Pss: %d\n", MemHealthRecord.getAverage(
- asOverallPss));
- }
- out.close();
- } catch (IOException e) {
- Log.e(TAG, "Error while saving memory stats", e);
- }
-
- // Temporary hack to write full logs
- try {
- PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
- mResultsDirectory + "/memory-health-details.txt")));
- for (Map.Entry<String, List<MemHealthRecord>> record : mMemHealthRecords.entrySet()) {
- out.println(record.getKey());
- out.printf("time,native_heap,dalvik_heap,pss,context\n");
- for (MemHealthRecord sample : record.getValue()) {
- out.printf("%d,%d,%d,%s\n", sample.mTimeMs, sample.mNativeHeap,
- sample.mDalvikHeap, sample.mContext.toString().toLowerCase());
- }
- }
- out.close();
- } catch (IOException e) {
- Log.e(TAG, "Error while saving memory stat details", e);
- }
- }
-
- private void recordMemoryUsage() {
- if (mProcsToTrack == null) {
- return;
- }
- long timeMs = System.currentTimeMillis();
- List<String> foregroundProcs = getForegroundProc();
- for (String proc : mProcsToTrack) {
- recordMemoryUsage(proc, timeMs, foregroundProcs);
- }
- }
-
- private void recordMemoryUsage(String proc, long timeMs, List<String> foregroundProcs) {
- try {
- String meminfo = getMeminfoOutput(proc);
- int nativeHeap = parseMeminfoLine(meminfo, "Native Heap\\s+\\d+\\s+(\\d+)");
- int dalvikHeap = parseMeminfoLine(meminfo, "Dalvik Heap\\s+\\d+\\s+(\\d+)");
- int pss = parseMeminfoLine(meminfo, "TOTAL\\s+(\\d+)");
-
- int asJavaHeap = parseMeminfoLine(meminfo, "Java Heap:\\s+(\\d+)");
- int asNativeHeap = parseMeminfoLine(meminfo, "Native Heap:\\s+(\\d+)");
- int asCode = parseMeminfoLine(meminfo, "Code:\\s+(\\d+)");
- int asStack = parseMeminfoLine(meminfo, "Stack:\\s+(\\d+)");
- int asGraphics = parseMeminfoLine(meminfo, "Graphics:\\s+(\\d+)");
- int asOther = parseMeminfoLine(meminfo, "Private Other:\\s+(\\d+)");
- int asSystem = parseMeminfoLine(meminfo, "System:\\s+(\\d+)");
- int asOverallPss = parseMeminfoLine(meminfo, "TOTAL:\\s+(\\d+)");
-
- if (nativeHeap < 0 || dalvikHeap < 0 || pss < 0) {
- return;
- }
- MemHealthRecord.Context context = foregroundProcs.contains(proc) ?
- MemHealthRecord.Context.FOREGROUND : MemHealthRecord.Context.BACKGROUND;
- if (!mMemHealthRecords.containsKey(proc)) {
- mMemHealthRecords.put(proc, new ArrayList<MemHealthRecord>());
- }
- mMemHealthRecords.get(proc).add(
- new MemHealthRecord(timeMs, dalvikHeap, nativeHeap, pss, asJavaHeap,
- asNativeHeap, asCode, asStack, asGraphics, asOther, asSystem,
- asOverallPss, context));
- recordDumpheap(proc, pss);
- } catch (IOException e) {
- Log.e(TAG, "exception while memory stats", e);
- }
- }
-
- private int parseMeminfoLine(String meminfo, String pattern)
- {
- Pattern p = Pattern.compile(pattern);
- Matcher m = p.matcher(meminfo);
- if (m.find()) {
- return Integer.parseInt(m.group(1));
- } else {
- return -1;
- }
- }
-
- private String getMeminfoOutput(String processName) throws IOException {
- return getProcessOutput("dumpsys meminfo " + processName);
- }
-
- private void recordDumpheap(String proc, long pss) throws IOException {
- if (!mDumpheapEnabled) {
- return;
- }
- Long count = mDumpheapCount.get(proc);
- if (count == null) {
- count = 0L;
- }
- Long lastDumpheap = mLastDumpheap.get(proc);
- if (lastDumpheap == null) {
- lastDumpheap = 0L;
- }
- long currentTime = SystemClock.uptimeMillis();
- if (pss > mDumpheapThreshold && count < mMaxDumpheaps &&
- currentTime - lastDumpheap > mDumpheapInterval) {
- recordDumpheap(proc);
- mDumpheapCount.put(proc, count + 1);
- mLastDumpheap.put(proc, currentTime);
- }
- }
-
- private void recordDumpheap(String proc) throws IOException {
- // Turns out getting dumpheap output is non-trivial. The command runs as shell user, and
- // only has access to /data/local/tmp directory to write files to. The test does not have
- // access to the output file by default because of the permissions dumpheap sets. So we need
- // to run dumpheap, change permissions on the output file and copy it to where test harness
- // can pick it up.
- Long count = mDumpheapCount.get(proc);
- if (count == null) {
- count = 0L;
- }
- String filename = String.format("dumpheap-%s-%d", proc, count);
- String tempFilename = "/data/local/tmp/" + filename;
- String finalFilename = mResultsDirectory +"/" + filename;
- String command = String.format("am dumpheap %s %s", proc, tempFilename);
- getProcessOutput(command);
- SystemClock.sleep(3000);
- getProcessOutput(String.format("cp %s %s", tempFilename, finalFilename));
- }
-
- public String getProcessOutput(String command) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- mDataCollector.saveProcessOutput(command, baos);
- baos.close();
- return baos.toString();
- }
-
- private List<String> getForegroundProc() {
- List<String> foregroundProcs = new ArrayList<String>();
- try {
- String compactMeminfo = getProcessOutput("dumpsys meminfo -c");
- for (String line : compactMeminfo.split("\\r?\\n")) {
- if (line.contains("proc,fore")) {
- String proc = line.split(",")[2];
- foregroundProcs.add(proc);
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "Error while getting foreground process", e);
- } finally {
- return foregroundProcs;
- }
- }
-
- public void setProcessStatusTracker(IProcessStatusTracker processStatusTracker) {
- mProcessStatusTracker = processStatusTracker;
- }
-
- public IProcessStatusTracker getProcessStatusTracker() {
- return mProcessStatusTracker;
- }
-
- public void launchIntent(Intent intent) {
- getInstrumentation().getContext().startActivity(intent);
- }
-
- protected Bundle getParams() {
- return ((InstrumentationTestRunner)getInstrumentation()).getArguments();
- }
-
- protected UiDevice getUiDevice() {
- return mDevice;
- }
-
- public void setDataCollector(DataCollector collector) {
- mDataCollector = collector;
- }
-
- public void setDexedJarPaths(List<String> paths) {
- mPaths = paths;
- }
-
- public List<String> getDexedJarPaths() {
- return mPaths;
- }
-
+ @Deprecated
public String getPackageVersion(String packageName) throws NameNotFoundException {
if (null == packageName || packageName.isEmpty()) {
throw new RuntimeException("Package name can't be null or empty");
@@ -833,7 +204,10 @@
* Get registered accounts
* Ensures there is at least one account registered
* returns the google account name
+ *
+ * @deprecated you should be using an AppHelper library to do this.
*/
+ @Deprecated
public String getRegisteredEmailAccount() {
Account[] accounts = AccountManager.get(getInstrumentation().getContext()).getAccounts();
Assert.assertTrue("Device doesn't have any account registered", accounts.length >= 1);
@@ -845,4 +219,24 @@
throw new RuntimeException("The device is not registered with a google account");
}
+
+ /* ******************* Logging ******************* */
+
+ protected void dumpMemInfo(String notes) {
+ FilesystemUtil.dumpMeminfo(getInstrumentation(), notes);
+ }
+
+ /* ******************* Utilities ******************* */
+
+ private class LockScreenWatcher implements UiWatcher {
+ @Override
+ public boolean checkForCondition() {
+ if (mDevice.hasObject(By.desc("Slide area."))) {
+ mDevice.pressMenu();
+ Log.v(TAG, "Lock screen dismissed.");
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/AuptTestRunner.java b/libraries/aupt-lib/src/android/support/test/aupt/AuptTestRunner.java
index a689a58..5ce1695 100644
--- a/libraries/aupt-lib/src/android/support/test/aupt/AuptTestRunner.java
+++ b/libraries/aupt-lib/src/android/support/test/aupt/AuptTestRunner.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,7 +16,6 @@
package android.support.test.aupt;
-import android.app.Instrumentation;
import android.app.Service;
import android.content.Context;
import android.content.ContextWrapper;
@@ -25,128 +24,167 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
+import android.os.SystemClock;
import android.support.test.uiautomator.UiDevice;
import android.test.AndroidTestRunner;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestRunner;
import android.util.Log;
-import dalvik.system.BaseDexClassLoader;
-
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import java.io.BufferedReader;
import java.io.File;
-import java.io.FileOutputStream;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
-import java.util.concurrent.TimeUnit;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
- * Test runner to use when running AUPT tests.
- * <p>
- * Adds support for multiple iterations, randomizing the order of the tests,
- * terminating tests after UI errors, detecting when processes are getting killed,
- * collecting bugreports and procrank data while the test is running.
+ * Ultra-fancy TestRunner to use when running AUPT: supports
+ *
+ * - Picking up tests from dexed JARs
+ * - Running tests for multiple iterations or in a custom order
+ * - Terminating tests after UI errors, timeouts, or when dependent processes die
+ * - Injecting additional information into custom TestCase subclasses
+ * - Passing through continuous metric-collection to a DataCollector instance
+ * - Collecting bugreports and heapdumps
+ *
*/
public class AuptTestRunner extends InstrumentationTestRunner {
- private static final String DEFAULT_JAR_PATH = "/data/local/tmp/";
+ /* Constants */
+ private static final String LOG_TAG = AuptTestRunner.class.getSimpleName();
+ private static final Long ANR_DELAY = 30000L;
+ private static final SimpleDateFormat SCREENSHOT_DATE_FORMAT =
+ new SimpleDateFormat("dd-mm-yy:HH:mm:ss:SSS");
- private static final String LOG_TAG = "AuptTestRunner";
- private static final String DEX_OPT_PATH = "aupt-opt";
- private static final String PARAM_JARS = "jars";
+ /* Keep a pointer to our argument bundle around for testing */
private Bundle mParams;
- private long mIterations;
- private Random mRandom;
- private boolean mShuffle;
- private boolean mGenerateAnr;
- private long mTestCaseTimeout = TimeUnit.MINUTES.toMillis(10);
- private DataCollector mDataCollector;
- private File mResultsDirectory;
-
+ /* Primitive Parameters */
private boolean mDeleteOldFiles;
private long mFileRetainCount;
+ private boolean mGenerateAnr;
+ private boolean mRecordMeminfo;
+ private long mIterations;
+ private long mSeed;
+ private long mTestCaseTimeout;
- private AuptPrivateTestRunner mRunner = new AuptPrivateTestRunner();
- private ClassLoader mLoader = null;
- private Context mTargetContextWrapper;
+ /* Dumpheap Parameters */
+ private boolean mDumpheapEnabled;
+ private long mDumpheapInterval;
+ private long mDumpheapThreshold;
+ private long mMaxDumpheaps;
- private IProcessStatusTracker mProcessTracker;
+ /* String Parameters */
+ private List<String> mJars = new ArrayList<>();
+ private List<String> mMemoryTrackedProcesses = new ArrayList<>();
+ private List<String> mFinishCommands;
- private Map<String, List<AuptTestCase.MemHealthRecord>> mMemHealthRecords;
+ /* Other Parameters */
+ private File mResultsDirectory;
- private boolean mTrackJank;
- private GraphicsStatsMonitor mGraphicsStatsMonitor;
- private List<String> mJars;
+ /* Helpers */
+ private Scheduler mScheduler;
+ private DataCollector mDataCollector;
+ private DexTestRunner mRunner;
- /**
- * {@inheritDoc}
- */
+ /* Logging */
+ private ProcessStatusTracker mProcessTracker;
+ private List<MemHealthRecord> mMemHealthRecords = new ArrayList<>();
+ private Map<String, Long> mDumpheapCount = new HashMap<>();
+ private Map<String, Long> mLastDumpheap = new HashMap<>();
+
+ /* Test Initialization */
@Override
public void onCreate(Bundle params) {
-
mParams = params;
- mMemHealthRecords = new HashMap<String, List<AuptTestCase.MemHealthRecord>>();
-
+ // Parse out primitive parameters
mIterations = parseLongParam("iterations", 1);
- mShuffle = parseBooleanParam("shuffle", false);
- long seed = parseLongParam("seed", (new Random()).nextLong());
- Log.d(LOG_TAG, String.format("Using seed value: %s", seed));
- mRandom = new Random(seed);
- // set to 'generateANR to 'true' when more info required for debugging on test timeout'
- mGenerateAnr = parseBooleanParam("generateANR", false);
- if (parseBooleanParam("quitOnError", false)) {
- mRunner.addTestListener(new QuitOnErrorListener());
+ mRecordMeminfo = parseBoolParam("record_meminfo", false);
+ mDumpheapEnabled = parseBoolParam("enableDumpheap", false);
+ mDumpheapThreshold = parseLongParam("dumpheapThreshold", 200 * 1024 * 1024);
+ mDumpheapInterval = parseLongParam("dumpheapInterval", 60 * 60 * 1000);
+ mMaxDumpheaps = parseLongParam("maxDumpheaps", 5);
+ mTestCaseTimeout = parseLongParam("testCaseTimeout", TimeUnit.MINUTES.toMillis(10));
+ mSeed = parseLongParam("seed", new Random().nextLong());
+
+ // Option: -e finishCommand 'a;b;c;d'
+ String finishCommandArg = parseStringParam("finishCommand", null);
+ mFinishCommands =
+ finishCommandArg == null
+ ? Arrays.<String>asList()
+ : Arrays.asList(finishCommandArg.split("\\s*;\\s*"));
+
+ // Option: -e shuffle true
+ mScheduler = parseBoolParam("shuffle", false)
+ ? Scheduler.shuffled(new Random(mSeed), mIterations)
+ : Scheduler.sequential(mIterations);
+
+ // Option: -e jars aupt-app-tests.jar:...
+ mJars.addAll(DexTestRunner.parseDexedJarPaths(parseStringParam("jars", "")));
+
+ // Option: -e trackMemory com.pkg1,com.pkg2,...
+ String memoryTrackedProcesses = parseStringParam("trackMemory", null);
+
+ if (memoryTrackedProcesses != null) {
+ mMemoryTrackedProcesses = Arrays.asList(memoryTrackedProcesses.split(","));
+ } else {
+ try {
+ // Deprecated approach: get tracked processes from a file.
+ String trackMemoryFileName =
+ Environment.getExternalStorageDirectory() + "/track_memory.txt";
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ new FileInputStream(new File(trackMemoryFileName))));
+
+ mMemoryTrackedProcesses = Arrays.asList(reader.readLine().split(","));
+ reader.close();
+ } catch (NullPointerException | IOException ex) {
+ mMemoryTrackedProcesses = Arrays.asList();
+ }
}
- if (parseBooleanParam("checkBattery", false)) {
- mRunner.addTestListener(new BatteryChecker());
- }
- mTestCaseTimeout = parseLongParam("testCaseTimeout", mTestCaseTimeout);
// Option: -e detectKill com.pkg1,...,com.pkg8
String processes = parseStringParam("detectKill", null);
+
if (processes != null) {
mProcessTracker = new ProcessStatusTracker(processes.split(","));
} else {
- mProcessTracker = new ProcessStatusTracker(null);
+ mProcessTracker = new ProcessStatusTracker(new String[] {});
}
- // Option: -e jankInterval integer
- long interval = parseLongParam("jankInterval", -1L);
- if (interval > 0) {
- mTrackJank = true;
- mGraphicsStatsMonitor = new GraphicsStatsMonitor();
- mGraphicsStatsMonitor.setIntervalRate(interval);
- }
-
- mRunner.addTestListener(new PidChecker());
+ // Option: -e outputLocation aupt_results
mResultsDirectory = new File(Environment.getExternalStorageDirectory(),
parseStringParam("outputLocation", "aupt_results"));
if (!mResultsDirectory.exists() && !mResultsDirectory.mkdirs()) {
- Log.w(LOG_TAG, "Did not create output directory");
+ Log.w(LOG_TAG, "Could not find or create output directory " + mResultsDirectory);
}
+ // Option: -e fileRetainCount 1
mFileRetainCount = parseLongParam("fileRetainCount", -1);
- if (mFileRetainCount == -1) {
- mDeleteOldFiles = false;
- } else {
- mDeleteOldFiles = true;
- }
+ mDeleteOldFiles = (mFileRetainCount != -1);
+ // Primary logging infrastructure
mDataCollector = new DataCollector(
TimeUnit.MINUTES.toMillis(parseLongParam("bugreportInterval", 0)),
+ TimeUnit.MINUTES.toMillis(parseLongParam("jankInterval", 0)),
TimeUnit.MINUTES.toMillis(parseLongParam("meminfoInterval", 0)),
TimeUnit.MINUTES.toMillis(parseLongParam("cpuinfoInterval", 0)),
TimeUnit.MINUTES.toMillis(parseLongParam("fragmentationInterval", 0)),
@@ -154,56 +192,55 @@
TimeUnit.MINUTES.toMillis(parseLongParam("pagetypeinfoInterval", 0)),
TimeUnit.MINUTES.toMillis(parseLongParam("traceInterval", 0)),
mResultsDirectory, this);
- String jars = params.getString(PARAM_JARS);
- if (jars != null) {
- loadDexJars(jars);
- }
- mTargetContextWrapper = new ClassLoaderContextWrapper();
+
+ // Make our TestRunner and make sure we injectInstrumentation.
+ mRunner = new DexTestRunner(this, mScheduler, mJars, mTestCaseTimeout) {
+ @Override
+ public void runTest(TestResult result) {
+ for (TestCase test: mTestCases) {
+ injectInstrumentation(test);
+ }
+
+ super.runTest(result);
+ }
+ };
+
+ // Aupt's TestListeners
+ mRunner.addTestListener(new PeriodicHeapDumper());
+ mRunner.addTestListener(new MemHealthRecorder());
+ mRunner.addTestListener(new DcimCleaner());
+ mRunner.addTestListener(new PidChecker());
+ mRunner.addTestListener(new TimeoutStackDumper());
+ mRunner.addTestListener(new MemInfoDumper());
+ mRunner.addTestListener(new FinishCommandRunner());
+ mRunner.addTestListenerIf(parseBoolParam("generateANR", false), new ANRTrigger());
+ mRunner.addTestListenerIf(parseBoolParam("quitOnError", false), new QuitOnErrorListener());
+ mRunner.addTestListenerIf(parseBoolParam("checkBattery", false), new BatteryChecker());
+ mRunner.addTestListenerIf(parseBoolParam("screenshots", false), new Screenshotter());
+
+ // Start our loggers
+ mDataCollector.start();
+
+ // Start the test
super.onCreate(params);
}
- private void loadDexJars(String jars) {
- // scan provided jar paths, translate relative to absolute paths, and check for existence
- String[] jarsArray = jars.split(":");
- StringBuilder classLoaderPath = new StringBuilder();
- mJars = new ArrayList<String>();
- for (int i = 0; i < jarsArray.length; i++) {
- String jar = jarsArray[i];
- if (!jar.startsWith("/")) {
- jar = DEFAULT_JAR_PATH + jar;
- }
- File jarFile = new File(jar);
- if (!jarFile.exists() || !jarFile.canRead()) {
- throw new IllegalArgumentException("Jar file does not exist or not accessible: "
- + jar);
- }
- if (i != 0) {
- classLoaderPath.append(File.pathSeparator);
- }
- classLoaderPath.append(jarFile.getAbsolutePath());
- mJars.add(jarFile.getAbsolutePath());
- }
- // now load them
- File optDir = new File(getContext().getCacheDir(), DEX_OPT_PATH);
- if (!optDir.exists() && !optDir.mkdirs()) {
- throw new RuntimeException(
- "Failed to create dex optimize directory: " + optDir.getAbsolutePath());
- }
- mLoader = new BaseDexClassLoader(classLoaderPath.toString(), optDir, null,
- super.getTargetContext().getClassLoader());
-
+ @Override
+ public void onDestroy() {
+ mDataCollector.stop();
}
+ /* Option-parsing helpers */
+
private long parseLongParam(String key, long alternative) throws NumberFormatException {
if (mParams.containsKey(key)) {
- return Long.parseLong(
- mParams.getString(key));
+ return Long.parseLong(mParams.getString(key));
} else {
return alternative;
}
}
- private boolean parseBooleanParam(String key, boolean alternative)
+ private boolean parseBoolParam(String key, boolean alternative)
throws NumberFormatException {
if (mParams.containsKey(key)) {
return Boolean.parseBoolean(mParams.getString(key));
@@ -220,244 +257,284 @@
}
}
- private void writeProgressMessage(String msg) {
- writeMessage("progress.txt", msg);
- }
+ /* Utility methods */
- private void writeGraphicsMessage(String msg) {
- writeMessage("graphics.txt", msg);
- }
+ /**
+ * Injects instrumentation into InstrumentationTestCase and AuptTestCase instances
+ */
+ private void injectInstrumentation(Test test) {
+ if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
+ InstrumentationTestCase instrTest = (InstrumentationTestCase) test;
- private void writeMessage(String filename, String msg) {
- try {
- FileOutputStream fos = new FileOutputStream(
- new File(mResultsDirectory, filename));
- fos.write(msg.getBytes());
- fos.flush();
- fos.close();
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "error saving progress file", ioe);
+ instrTest.injectInstrumentation(AuptTestRunner.this);
}
}
- /**
- * Provide a wrapped context so that we can provide an alternative class loader
- * @return
- */
- @Override
- public Context getTargetContext() {
- return mTargetContextWrapper;
- }
-
- /**
- * {@inheritDoc}
- */
+ /* Passthrough to our DexTestRunner */
@Override
protected AndroidTestRunner getAndroidTestRunner() {
- // AndroidTestRunner is what determines which tests get to run.
- // Unfortunately there is no hooks into it, so most of
- // the functionality has to be duplicated
return mRunner;
}
- /**
- * Sets up and starts monitoring jank metrics by clearing the currently existing data.
- */
- private void startJankMonitoring () {
- if (mTrackJank) {
- mGraphicsStatsMonitor.setUiAutomation(getUiAutomation());
- mGraphicsStatsMonitor.startMonitoring();
-
- // TODO: Clear graphics.txt file if extant
- }
- }
-
- /**
- * Stops future monitoring of jank metrics, but preserves current metrics intact.
- */
- private void stopJankMonitoring () {
- if (mTrackJank) {
- mGraphicsStatsMonitor.stopMonitoring();
- }
- }
-
- /**
- * Aggregates and merges jank metrics and writes them to the graphics file.
- */
- private void writeJankMetrics () {
- if (mTrackJank) {
- List<JankStat> mergedStats = mGraphicsStatsMonitor.aggregateStatsImages();
- String mergedStatsString = JankStat.statsListToString(mergedStats);
-
- Log.d(LOG_TAG, "Writing jank metrics to the graphics file");
- writeGraphicsMessage(mergedStatsString);
- }
- }
-
- /**
- * Determines which tests to run, configures the test class and then runs the test.
- */
- private class AuptPrivateTestRunner extends AndroidTestRunner {
-
- private List<TestCase> mTestCases;
- private List<TestListener> mTestListeners = new ArrayList<>();
- private Instrumentation mInstrumentation;
- private TestResult mTestResult;
-
- @Override
- public List<TestCase> getTestCases() {
- if (mTestCases != null) {
- return mTestCases;
- }
-
- List<TestCase> testCases = new ArrayList<TestCase>(super.getTestCases());
- List<TestCase> completeList = new ArrayList<TestCase>();
-
- for (int i = 0; i < mIterations; i++) {
- if (mShuffle) {
- Collections.shuffle(testCases, mRandom);
+ @Override
+ public Context getTargetContext() {
+ return new ContextWrapper(super.getTargetContext()) {
+ @Override
+ public ClassLoader getClassLoader() {
+ if(mRunner != null) {
+ return mRunner.getDexClassLoader();
+ } else {
+ throw new RuntimeException("DexTestRunner not initialized!");
}
- completeList.addAll(testCases);
+ }
+ };
+ }
+
+ /**
+ * A simple abstract instantiation of TestListener
+ *
+ * Primarily meant to work around Java 7's lack of interface-default methods.
+ */
+ abstract static class AuptListener implements TestListener {
+ /** Called when a test throws an exception. */
+ public void addError(Test test, Throwable t) {}
+
+ /** Called when a test fails. */
+ public void addFailure(Test test, AssertionFailedError t) {}
+
+ /** Called whenever a test ends. */
+ public void endTest(Test test) {}
+
+ /** Called whenever a test begins. */
+ public void startTest(Test test) {}
+ }
+
+ /**
+ * Periodically Heap-dump to assist with memory-leaks.
+ */
+ private class PeriodicHeapDumper extends AuptListener {
+ private Thread mHeapDumpThread;
+
+ private class InternalHeapDumper implements Runnable {
+ private void recordDumpheap(String proc, long pss) throws IOException {
+ if (!mDumpheapEnabled) {
+ return;
+ }
+ Long count = mDumpheapCount.get(proc);
+ if (count == null) {
+ count = 0L;
+ }
+ Long lastDumpheap = mLastDumpheap.get(proc);
+ if (lastDumpheap == null) {
+ lastDumpheap = 0L;
+ }
+ long currentTime = SystemClock.uptimeMillis();
+ if (pss > mDumpheapThreshold && count < mMaxDumpheaps &&
+ currentTime - lastDumpheap > mDumpheapInterval) {
+ recordDumpheap(proc);
+ mDumpheapCount.put(proc, count + 1);
+ mLastDumpheap.put(proc, currentTime);
+ }
}
- mTestCases = completeList;
- return mTestCases;
- }
+ private void recordDumpheap(String proc) throws IOException {
+ long count = mDumpheapCount.get(proc);
- @Override
- public void runTest(TestResult testResult) {
- mTestResult = testResult;
+ String filename = String.format("dumpheap-%s-%d", proc, count);
+ String tempFilename = "/data/local/tmp/" + filename;
+ String finalFilename = mResultsDirectory + "/" + filename;
- ((ProcessStatusTracker)mProcessTracker).setUiAutomation(getUiAutomation());
+ AuptTestRunner.this.getUiAutomation().executeShellCommand(
+ String.format("am dumpheap %s %s", proc, tempFilename));
- mDataCollector.start();
- startJankMonitoring();
+ SystemClock.sleep(3000);
- for (TestListener testListener : mTestListeners) {
- mTestResult.addListener(testListener);
+ AuptTestRunner.this.getUiAutomation().executeShellCommand(
+ String.format("cp %s %s", tempFilename, finalFilename));
}
- Runnable timeBomb = new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(mTestCaseTimeout);
- } catch (InterruptedException e) {
- return;
- }
- // if we ever wake up, a timeout has occurred, set off the bomb,
- // but trigger a service ANR first
- if (mGenerateAnr) {
- Context ctx = getTargetContext();
- Log.d(LOG_TAG, "About to induce artificial ANR for debugging");
- ctx.startService(new Intent(ctx, BadService.class));
- // intentional delay to allow the service ANR to happen then resolve
- try {
- Thread.sleep(BadService.DELAY + BadService.DELAY / 4);
- } catch (InterruptedException e) {
- // ignore
- Log.d(LOG_TAG, "interrupted in wait on BadService");
- return;
+ public void run() {
+ try {
+ while (true) {
+ Thread.sleep(mDumpheapInterval);
+
+ for(String proc : mMemoryTrackedProcesses) {
+ recordDumpheap(proc);
}
- } else {
- Log.d("THREAD_DUMP", getStackTraces());
}
- throw new RuntimeException(String.format("max testcase timeout exceeded: %s ms",
- mTestCaseTimeout));
+ } catch (InterruptedException iex) {
+ } catch (IOException ioex) {
+ Log.e(LOG_TAG, "Failed to write heap dump!", ioex);
}
- };
+ }
+ }
+
+ @Override
+ public void startTest(Test test) {
+ mHeapDumpThread = new Thread(new InternalHeapDumper());
+ mHeapDumpThread.start();
+ }
+
+ @Override
+ public void endTest(Test test) {
+ try {
+ mHeapDumpThread.interrupt();
+ mHeapDumpThread.join();
+ } catch (InterruptedException iex) { }
+ }
+ }
+
+ /**
+ * Dump memory info on test start/stop
+ */
+ private class MemInfoDumper extends AuptListener {
+ private void dumpMemInfo() {
+ if (mRecordMeminfo) {
+ FilesystemUtil.dumpMeminfo(AuptTestRunner.this, "MemInfoDumper");
+ }
+ }
+
+ @Override
+ public void startTest(Test test) {
+ dumpMemInfo();
+ }
+
+ @Override
+ public void endTest(Test test) {
+ dumpMemInfo();
+ }
+ }
+
+ /**
+ * Record all of our MemHealthRecords
+ */
+ private class MemHealthRecorder extends AuptListener {
+ @Override
+ public void startTest(Test test) {
+ recordMemHealth();
+ }
+
+ @Override
+ public void endTest(Test test) {
+ recordMemHealth();
try {
- // Try to run all TestCases, but ensure the finally block is reached
- for (TestCase testCase : mTestCases) {
- setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);
- setupAuptIfAuptTestCase(testCase);
+ MemHealthRecord.saveVerbose(mMemHealthRecords,
+ mResultsDirectory + "memory-health.txt");
- // Remove device storage as necessary
- removeOldImagesFromDcimCameraFolder();
+ MemHealthRecord.saveCsv(mMemHealthRecords,
+ mResultsDirectory + "memory-health-details.txt");
- Thread timeBombThread = null;
- if (mTestCaseTimeout > 0) {
- timeBombThread = new Thread(timeBomb);
- timeBombThread.setName("Boom!");
- timeBombThread.setDaemon(true);
- timeBombThread.start();
- }
+ mMemHealthRecords.clear();
+ } catch (IOException ioex) {
+ Log.e(LOG_TAG, "Error writing MemHealthRecords", ioex);
+ }
+ }
- try {
- testCase.run(mTestResult);
- } catch (AuptTerminator ex) {
- // Write to progress.txt to pass the exception message to the dashboard
- writeProgressMessage("Exception: " + ex);
- // Throw the exception, because we want to discontinue running tests
- throw ex;
- }
+ private void recordMemHealth() {
+ try {
+ mMemHealthRecords.addAll(MemHealthRecord.get(
+ AuptTestRunner.this,
+ mMemoryTrackedProcesses,
+ System.currentTimeMillis(),
+ getForegroundProcs()));
+ } catch (IOException ioex) {
+ Log.e(LOG_TAG, "Error collecting MemHealthRecords", ioex);
+ }
+ }
- if (mTestCaseTimeout > 0) {
- timeBombThread.interrupt();
- try {
- timeBombThread.join();
- } catch (InterruptedException e) {
- // ignore
- }
+ private List<String> getForegroundProcs() {
+ List<String> foregroundProcs = new ArrayList<String>();
+ try {
+ String compactMeminfo = MemHealthRecord.getProcessOutput(AuptTestRunner.this,
+ "dumpsys meminfo -c");
+
+ for (String line : compactMeminfo.split("\\r?\\n")) {
+ if (line.contains("proc,fore")) {
+ String proc = line.split(",")[2];
+ foregroundProcs.add(proc);
}
}
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Error while getting foreground process", e);
} finally {
- // Ensure the DataCollector ends all dangling Threads
- mDataCollector.stop();
- // Ensure the Timer in GraphicsStatsMonitor is canceled
- stopJankMonitoring(); // However, it is daemon
- // Ensure jank metrics are written to the graphics file
- writeJankMetrics();
+ return foregroundProcs;
}
}
+ }
- /**
- * Gets all thread stack traces.
- *
- * @return string of all thread stack traces
- */
- private String getStackTraces() {
- StringBuilder sb = new StringBuilder();
- Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
- for (Thread t : stacks.keySet()) {
- sb.append(t.toString()).append('\n');
- for (StackTraceElement ste : t.getStackTrace()) {
- sb.append("\tat ").append(ste.toString()).append('\n');
+ /**
+ * Kills application and dumps UI Hierarchy on test error
+ */
+ private class QuitOnErrorListener extends AuptListener {
+ @Override
+ public void addError(Test test, Throwable t) {
+ Log.e(LOG_TAG, "Caught exception from a test", t);
+
+ if ((t instanceof AuptTerminator)) {
+ throw (AuptTerminator)t;
+ } else {
+
+ // Check if our exception is caused by process dependency
+ if (test instanceof AuptTestCase) {
+ mProcessTracker.setUiAutomation(getUiAutomation());
+ mProcessTracker.verifyRunningProcess();
}
- sb.append('\n');
+
+ // If that didn't throw, then dump our hierarchy
+ Log.v(LOG_TAG, "Dumping UI hierarchy");
+ try {
+ UiDevice.getInstance(AuptTestRunner.this).dumpWindowHierarchy(
+ new File("/data/local/tmp/error_dump.xml"));
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to create UI hierarchy dump for UI error", e);
+ }
}
- return sb.toString();
+
+ // Quit on an error
+ throw new AuptTerminator(t.getMessage(), t);
}
- private void setInstrumentationIfInstrumentationTestCase(
- Test test, Instrumentation instrumentation) {
- if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
- ((InstrumentationTestCase) test).injectInstrumentation(instrumentation);
- }
+ @Override
+ public void addFailure(Test test, AssertionFailedError t) {
+ // Quit on an error
+ throw new AuptTerminator(t.getMessage(), t);
+ }
+ }
+
+ /**
+ * Makes sure the processes this test requires are all alive
+ */
+ private class PidChecker extends AuptListener {
+ @Override
+ public void startTest(Test test) {
+ mProcessTracker.setUiAutomation(getUiAutomation());
+ mProcessTracker.verifyRunningProcess();
}
- // Aupt specific set up.
- private void setupAuptIfAuptTestCase(Test test) {
- if (test instanceof AuptTestCase){
- ((AuptTestCase)test).setProcessStatusTracker(mProcessTracker);
- ((AuptTestCase)test).setMemHealthRecords(mMemHealthRecords);
- ((AuptTestCase)test).setDataCollector(mDataCollector);
- ((AuptTestCase)test).setDexedJarPaths(mJars);
- }
+ @Override
+ public void endTest(Test test) {
+ mProcessTracker.verifyRunningProcess();
}
+ }
- private void removeOldImagesFromDcimCameraFolder() {
+ /**
+ * Initialization for tests that touch the camera
+ */
+ private class DcimCleaner extends AuptListener {
+ @Override
+ public void startTest(Test test) {
if (!mDeleteOldFiles) {
return;
}
File dcimFolder = new File(Environment.getExternalStorageDirectory(), "DCIM");
- if (dcimFolder != null) {
- File cameraFolder = new File(dcimFolder, "Camera");
- if (cameraFolder != null) {
+ File cameraFolder = new File(dcimFolder, "Camera");
+
+ if (dcimFolder.exists()) {
+ if (cameraFolder.exists()) {
File[] allMediaFiles = cameraFolder.listFiles();
- Arrays.sort(allMediaFiles, new Comparator<File> () {
+ Arrays.sort(allMediaFiles, new Comparator<File>() {
public int compare(File f1, File f2) {
return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
}
@@ -472,109 +549,12 @@
Log.w(LOG_TAG, "No DCIM folder found to delete from.");
}
}
-
- @Override
- public void clearTestListeners() {
- mTestListeners.clear();
- }
-
- @Override
- public void addTestListener(TestListener testListener) {
- if (testListener != null) {
- mTestListeners.add(testListener);
- }
- }
-
- @Override
- public void setInstrumentation(Instrumentation instrumentation) {
- mInstrumentation = instrumentation;
- }
-
- @Override
- public TestResult getTestResult() {
- return mTestResult;
- }
-
- @Override
- protected TestResult createTestResult() {
- return new TestResult();
- }
}
/**
- * Test listener that monitors the AUPT tests for any errors. If the option is set it will
- * terminate the whole test run if it encounters an exception.
+ * Makes sure the battery hasn't died before and after each test.
*/
- private class QuitOnErrorListener implements TestListener {
-
- @Override
- public void addError(Test test, Throwable t) {
- Log.e(LOG_TAG, "Caught exception from a test", t);
- if ((t instanceof AuptTerminator)) {
- throw (AuptTerminator)t;
- } else {
- // check that if the UI exception is caused by process getting killed
- if (test instanceof AuptTestCase) {
- ((AuptTestCase)test).getProcessStatusTracker().verifyRunningProcess();
- }
- // if previous line did not throw an exception, we are interested to know what
- // caused the UI exception
- Log.v(LOG_TAG, "Dumping UI hierarchy");
- try {
- UiDevice.getInstance(AuptTestRunner.this).dumpWindowHierarchy(
- new File("/data/local/tmp/error_dump.xml"));
- } catch (IOException e) {
- Log.w(LOG_TAG, "Failed to create UI hierarchy dump for UI error", e);
- }
- }
-
- throw new AuptTerminator(t.getMessage(), t);
- }
-
- @Override
- public void addFailure(Test test, AssertionFailedError t) {
- throw new AuptTerminator(t.getMessage(), t);
- }
-
- @Override
- public void endTest(Test test) {
- // skip
- }
-
- @Override
- public void startTest(Test test) {
- // skip
- }
- }
-
- /**
- * A listener that checks that none of the monitored processes died during the test.
- * If a process dies it will terminate the test early.
- */
- private class PidChecker implements TestListener {
-
- @Override
- public void addError(Test test, Throwable t) {
- // no-op
- }
-
- @Override
- public void addFailure(Test test, AssertionFailedError t) {
- // no-op
- }
-
- @Override
- public void endTest(Test test) {
- mProcessTracker.verifyRunningProcess();
- }
-
- @Override
- public void startTest(Test test) {
- mProcessTracker.verifyRunningProcess();
- }
- }
-
- private class BatteryChecker implements TestListener {
+ private class BatteryChecker extends AuptListener {
private static final double BATTERY_THRESHOLD = 0.05;
private void checkBattery() {
@@ -596,62 +576,120 @@
}
@Override
- public void addError(Test test, Throwable t) {
- // skip
- }
-
- @Override
- public void addFailure(Test test, AssertionFailedError afe) {
- // skip
- }
-
- @Override
- public void endTest(Test test) {
- // skip
- }
-
- @Override
public void startTest(Test test) {
checkBattery();
}
}
/**
- * A {@link ContextWrapper} that overrides {@link Context#getClassLoader()}
+ * Generates heap dumps when a test times out
*/
- class ClassLoaderContextWrapper extends ContextWrapper {
-
- public ClassLoaderContextWrapper() {
- super(AuptTestRunner.super.getTargetContext());
+ private class TimeoutStackDumper extends AuptListener {
+ private String getStackTraces() {
+ StringBuilder sb = new StringBuilder();
+ Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
+ for (Thread t : stacks.keySet()) {
+ sb.append(t.toString()).append('\n');
+ for (StackTraceElement ste : t.getStackTrace()) {
+ sb.append("\tat ").append(ste.toString()).append('\n');
+ }
+ sb.append('\n');
+ }
+ return sb.toString();
}
- /**
- * Alternatively returns a custom class loader with classes loaded from additional jars
- */
@Override
- public ClassLoader getClassLoader() {
- if (mLoader != null) {
- return mLoader;
- } else {
- return super.getClassLoader();
+ public void addError(Test test, Throwable t) {
+ if (t instanceof TimeoutException) {
+ Log.d("THREAD_DUMP", getStackTraces());
}
}
}
- public static class BadService extends Service {
- public static final long DELAY = 30000;
+ /** Generates ANRs when a test takes too long. */
+ private class ANRTrigger extends AuptListener {
@Override
- public IBinder onBind(Intent intent) {
- return null;
+ public void addError(Test test, Throwable t) {
+ if (t instanceof TimeoutException) {
+ Context ctx = getTargetContext();
+ Log.d(LOG_TAG, "About to induce artificial ANR for debugging");
+ ctx.startService(new Intent(ctx, AnrGenerator.class));
+
+ try {
+ Thread.sleep(ANR_DELAY);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while waiting for AnrGenerator...");
+ }
+ }
+ }
+
+ /** Service that hangs to trigger an ANR. */
+ private class AnrGenerator extends Service {
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int id) {
+ Log.i(LOG_TAG, "in service start -- about to hang");
+ try {
+ Thread.sleep(ANR_DELAY);
+ } catch (InterruptedException e) {
+ Log.wtf(LOG_TAG, e);
+ }
+ Log.i(LOG_TAG, "service hang finished -- stopping and returning");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ }
+ }
+
+ /**
+ * Collect a screenshot on test failure.
+ */
+ private class Screenshotter extends AuptListener {
+ private void collectScreenshot(Test test, String suffix) {
+ UiDevice device = UiDevice.getInstance(AuptTestRunner.this);
+
+ if (device == null) {
+ Log.w(LOG_TAG, "Couldn't collect screenshot on test failure");
+ return;
+ }
+
+ String testName =
+ test instanceof TestCase
+ ? ((TestCase) test).getName()
+ : (test instanceof TestSuite ? ((TestSuite) test).getName() : test.toString());
+
+ String fileName =
+ mResultsDirectory.getPath()
+ + "/" + testName.replaceAll(".", "_")
+ + suffix + ".png";
+
+ device.takeScreenshot(new File(fileName));
}
@Override
- public int onStartCommand(Intent intent, int flags, int id) {
- Log.i(LOG_TAG, "in service start -- about to hang");
- try { Thread.sleep(DELAY); } catch (InterruptedException e) { Log.wtf(LOG_TAG, e); }
- Log.i(LOG_TAG, "service hang finished -- stopping and returning");
- stopSelf();
- return START_NOT_STICKY;
+ public void addError(Test test, Throwable t) {
+ collectScreenshot(test,
+ "_failure_screenshot_" + SCREENSHOT_DATE_FORMAT.format(new Date()));
+ }
+
+ @Override
+ public void addFailure(Test test, AssertionFailedError t) {
+ collectScreenshot(test,
+ "_failure_screenshot_" + SCREENSHOT_DATE_FORMAT.format(new Date()));
+ }
+ }
+
+ /** Runs a command when a test finishes. */
+ private class FinishCommandRunner extends AuptListener {
+ @Override
+ public void endTest(Test test) {
+ for (String command : mFinishCommands) {
+ AuptTestRunner.this.getUiAutomation().executeShellCommand(command);
+ }
}
}
}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/DataCollector.java b/libraries/aupt-lib/src/android/support/test/aupt/DataCollector.java
index bec2daa..7166230 100644
--- a/libraries/aupt-lib/src/android/support/test/aupt/DataCollector.java
+++ b/libraries/aupt-lib/src/android/support/test/aupt/DataCollector.java
@@ -17,333 +17,147 @@
package android.support.test.aupt;
import android.app.Instrumentation;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.util.Log;
-import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.HashMap;
+import java.util.Map;
public class DataCollector {
private static final String TAG = "AuptDataCollector";
- private long mBugreportInterval, mMeminfoInterval, mCpuinfoInterval, mFragmentationInterval,
- mIonHeapInterval, mPageTypeInfoInterval, mTraceInterval;
- private File mResultsDirectory;
- private Thread mLoggerThread;
- private Logger mLogger;
- private Instrumentation mInstrumentation;
+ private final AtomicBoolean mStopped = new AtomicBoolean(true);
+ private final Map<LogGenerator, Long> generatorsWithIntervals = new HashMap<>();
+ private final Map<LogGenerator, Long> mLastUpdate = new HashMap<>();
+ private final Instrumentation instrumentation;
+ private final String resultsDirectory;
+ private final long mSleepInterval;
- public DataCollector(long bugreportInterval, long meminfoInterval, long cpuinfoInterval,
- long fragmentationInterval, long ionHeapInterval, long pagetypeinfoInterval,
- long traceInterval, File outputLocation, Instrumentation intrumentation) {
- mBugreportInterval = bugreportInterval;
- mMeminfoInterval = meminfoInterval;
- mCpuinfoInterval = cpuinfoInterval;
- mFragmentationInterval = fragmentationInterval;
- mIonHeapInterval = ionHeapInterval;
- mPageTypeInfoInterval = pagetypeinfoInterval;
- mResultsDirectory = outputLocation;
- mTraceInterval = traceInterval;
- mInstrumentation = intrumentation;
- }
+ private Thread mThread;
- public void start() {
- mLogger = new Logger();
- mLoggerThread = new Thread(mLogger);
- mLoggerThread.start();
- }
-
- public void stop() {
- mLogger.stop();
- try {
- mLoggerThread.join();
- } catch (InterruptedException e) {
- // ignore
+ /**
+ * Add a generator iff the interval is valid (i.e. > 0).
+ */
+ private void put(LogGenerator key, Long interval) {
+ if (interval > 0) {
+ generatorsWithIntervals.put(key, interval);
}
}
- private class Logger implements Runnable {
- private final long mIntervals[] = {
- mBugreportInterval, mMeminfoInterval, mCpuinfoInterval, mFragmentationInterval,
- mIonHeapInterval, mPageTypeInfoInterval, mTraceInterval
- };
- private final LogGenerator mLoggers[] = {
- new BugreportGenerator(), new CompactMemInfoGenerator(), new CpuInfoGenerator(),
- new FragmentationGenerator(), new IonHeapGenerator(), new PageTypeInfoGenerator(),
- new TraceGenerator()
- };
+ public DataCollector(long bugreportInterval, long graphicsInterval, long meminfoInterval,
+ long cpuinfoInterval, long fragmentationInterval, long ionHeapInterval,
+ long pagetypeinfoInterval, long traceInterval,
+ File outputLocation, Instrumentation instr) {
- private final long mLastUpdate[] = new long[mLoggers.length];
- private final long mSleepInterval;
+ resultsDirectory = outputLocation.getPath();
+ instrumentation = instr;
- private boolean mStopped = false;
+ put(LogGenerator.BUGREPORT, bugreportInterval);
+ put(LogGenerator.CPU_INFO, cpuinfoInterval);
+ put(LogGenerator.FRAGMENTATION, fragmentationInterval);
+ put(LogGenerator.GRAPHICS_STATS, graphicsInterval);
+ put(LogGenerator.ION_HEAP, ionHeapInterval);
+ put(LogGenerator.MEM_INFO, meminfoInterval);
+ put(LogGenerator.PAGETYPE_INFO, pagetypeinfoInterval);
+ put(LogGenerator.TRACE, traceInterval);
- public Logger() {
- for (int i = 0; i < mIntervals.length; i++) {
- if (mIntervals[i] > 0) {
- try {
- mLoggers[i].createLog();
- } catch (InterruptedException e) {
- // ignore
- }
- mLastUpdate[i] = SystemClock.uptimeMillis();
+ mSleepInterval = gcd(generatorsWithIntervals.values());
+ }
+
+ public synchronized void start() {
+ if (mStopped.getAndSet(false)) {
+ /* Initialize the LastUpdates to the current time */
+ for (Map.Entry<LogGenerator, Long> entry : generatorsWithIntervals.entrySet()) {
+ if (entry.getValue() > 0) {
+ Log.d(TAG, "Collecting " + entry.getKey() + " logs every " +
+ entry.getValue() + " milliseconds");
+
+ mLastUpdate.put(entry.getKey(), SystemClock.uptimeMillis());
}
}
- mSleepInterval = gcd(mIntervals);
+ mThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ loop();
+ }
+ });
+ mThread.start();
+ } else {
+ Log.e(TAG, "Tried to start a started DataCollector!");
}
+ }
- public void stop() {
- synchronized(this) {
- mStopped = true;
- notifyAll();
+ public synchronized void stop() {
+ if (!mStopped.getAndSet(true)) {
+ mThread.interrupt();
+
+ try {
+ mThread.join();
+ } catch (InterruptedException e) {
+ // ignore
}
+ } else {
+ Log.e(TAG, "Tried to stop a stoppped DataCollector!");
+ }
+ }
+
+ private void loop() {
+ if (mSleepInterval <= 0) {
+ return;
}
- private long gcd(long values[]) {
- if (values.length < 2)
- return 0;
+ while (!mStopped.get()) {
+ try {
+ for (Map.Entry<LogGenerator, Long> entry : generatorsWithIntervals.entrySet()) {
+ Long t = SystemClock.uptimeMillis() - mLastUpdate.get(entry.getKey());
- long gcdSoFar = values[0];
- for (int i = 1; i < values.length; i++) {
- gcdSoFar = gcd(gcdSoFar, values[i]);
- }
-
- return gcdSoFar;
- }
-
- private long gcd(long a, long b) {
- if (a == 0)
- return b;
- if (b == 0)
- return a;
- if (a > b)
- return gcd(b, a % b);
- else
- return gcd(a, b % a);
- }
-
- @Override
- public void run() {
- if (mSleepInterval <= 0)
- return;
-
- synchronized(this) {
- while (!mStopped) {
- try {
- for (int i = 0; i < mIntervals.length; i++) {
- if (mIntervals[i] > 0 &&
- SystemClock.uptimeMillis() - mLastUpdate[i] > mIntervals[i]) {
- mLoggers[i].createLog();
- mLastUpdate[i] = SystemClock.uptimeMillis();
- }
+ if (entry.getValue() > 0 && t >= entry.getValue()) {
+ try {
+ entry.getKey().save(instrumentation, resultsDirectory);
+ } catch (IOException ex) {
+ Log.e(TAG, "Error writing results in " + resultsDirectory +
+ ": " + ex.toString());
}
- wait(mSleepInterval);
- } catch (InterruptedException e) {
- // Ignore.
+
+ mLastUpdate.put(entry.getKey(), SystemClock.uptimeMillis());
}
}
+
+ Thread.sleep(mSleepInterval);
+ } catch (InterruptedException e) {
+ // Ignore.
}
}
}
- private interface LogGenerator {
- public void createLog() throws InterruptedException;
- }
-
- private class CompactMemInfoGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- saveCompactMeminfo(mResultsDirectory + "/compact-meminfo-%s.txt");
- } catch (IOException ioe) {
- Log.w(TAG, "Error while saving dumpsys meminfo -c: " + ioe.getMessage());
- }
+ private long gcd(Collection<Long> values) {
+ if (values.size() < 1) {
+ return 0;
}
- }
- private class CpuInfoGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- saveCpuinfo(mResultsDirectory + "/cpuinfo-%s.txt");
- } catch (IOException ioe) {
- Log.w(TAG, "Error while saving dumpsys cpuinfo : " + ioe.getMessage());
- }
+ long gcdSoFar = values.iterator().next();
+
+ for (Long value : values) {
+ gcdSoFar = gcd(gcdSoFar, value);
}
+
+ return gcdSoFar;
}
- private class BugreportGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- saveBugreport(mResultsDirectory + "/bugreport-%s.txt");
- } catch (IOException e) {
- Log.w(TAG, String.format("Failed to take bugreport: %s", e.getMessage()));
- }
- }
- }
-
- private class FragmentationGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- saveFragmentation(mResultsDirectory + "/unusable-index-%s.txt");
- } catch (IOException e) {
- Log.w(TAG, String.format("Failed to save buddyinfo: %s", e.getMessage()));
- }
- }
- }
-
- private class IonHeapGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- saveIonHeap("audio", mResultsDirectory + "/ion-audio-%s.txt");
- saveIonHeap("system", mResultsDirectory + "/ion-system-%s.txt");
- } catch (IOException e) {
- Log.w(TAG, String.format("Failed to save ION heap: %s", e.getMessage()));
- }
- }
- }
-
- private class PageTypeInfoGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- savePageTypeInfo(mResultsDirectory + "/pagetypeinfo-%s.txt");
- } catch (IOException e) {
- Log.w(TAG, String.format("Failed to save pagetypeinfo: %s", e.getMessage()));
- }
- }
- }
-
- private class TraceGenerator implements LogGenerator {
- @Override
- public void createLog() throws InterruptedException {
- try {
- saveTrace(mResultsDirectory + "/trace-%s.txt");
- } catch (IOException e) {
- Log.w(TAG, String.format("Failed to save trace: %s", e.getMessage()));
- }
- }
- }
-
- public void saveCompactMeminfo(String filename)
- throws FileNotFoundException, IOException, InterruptedException {
- saveProcessOutput("dumpsys meminfo -c -S", filename);
- }
-
- public void saveCpuinfo(String filename)
- throws FileNotFoundException, IOException, InterruptedException {
- saveProcessOutput("dumpsys cpuinfo", filename);
- }
-
- public void saveFragmentation(String filename)
- throws FileNotFoundException, IOException, InterruptedException {
- saveProcessOutput("cat /d/extfrag/unusable_index", filename);
- }
-
- public void saveIonHeap(String type, String filename)
- throws FileNotFoundException, IOException, InterruptedException {
- saveProcessOutput(String.format("cat /d/ion/heaps/%s", type), filename);
- }
-
- public void savePageTypeInfo(String filename)
- throws FileNotFoundException, IOException, InterruptedException {
- saveProcessOutput("cat /proc/pagetypeinfo", filename);
- }
-
- public void saveTrace(String filename)
- throws FileNotFoundException, IOException, InterruptedException {
- saveProcessOutput("cat /sys/kernel/debug/tracing/trace", filename);
- }
-
- public void saveBugreport(String filename)
- throws IOException, InterruptedException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- // Spaces matter in the following command line. Make sure there are no spaces
- // in the filename and around the '>' sign.
- String cmdline = String.format("/system/bin/sh -c /system/bin/bugreport>%s",
- templateToFilename(filename));
- saveProcessOutput(cmdline, baos);
- baos.close();
- }
-
- public void dumpMeminfo(String notes) {
- long epochSeconds = System.currentTimeMillis() / 1000;
- File outputDir = new File(Environment.getExternalStorageDirectory(), "meminfo");
- Log.i(TAG, outputDir.toString());
- if (!outputDir.exists()) {
- boolean yes = outputDir.mkdirs();
- Log.i(TAG, yes ? "created" : "not created");
- }
- File outputFile = new File(outputDir, String.format("%d.txt", epochSeconds));
- Log.i(TAG, outputFile.toString());
- FileOutputStream fos = null;
-
- try {
- fos = new FileOutputStream(outputFile);
- fos.write(String.format("notes: %s\n\n", notes).getBytes());
-
- saveProcessOutput("dumpsys meminfo -c", fos);
- fos.close();
- } catch (FileNotFoundException e) {
- Log.e(TAG, "exception while dumping meminfo", e);
- } catch (IOException e) {
- Log.e(TAG, "exception while dumping meminfo", e);
- }
- }
-
- private void saveProcessOutput(String command, String filenameTemplate)
- throws IOException, FileNotFoundException {
- String outFilename = templateToFilename(filenameTemplate);
- File file = new File(outFilename);
- Log.d(TAG, String.format("Saving command \"%s\" output into file %s",
- command, file.getAbsolutePath()));
-
- OutputStream out = new FileOutputStream(file);
- saveProcessOutput(command, out);
- out.close();
- }
-
- private String templateToFilename(String filenameTemplate) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
- return String.format(filenameTemplate, sdf.format(new Date()));
- }
-
- public void saveProcessOutput(String command, OutputStream out) throws IOException {
- InputStream in = null;
- try {
- ParcelFileDescriptor pfd =
- mInstrumentation.getUiAutomation().executeShellCommand(command);
- in = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
- byte[] buffer = new byte[4096]; //4K buffer
- int bytesRead = -1;
- while (true) {
- bytesRead = in.read(buffer);
- if (bytesRead == -1) {
- break;
- }
- out.write(buffer, 0, bytesRead);
- }
- } finally {
- if (in != null) {
- in.close();
- }
- if (out != null) {
- out.flush();
- }
+ private long gcd(long a, long b) {
+ if (a == 0) {
+ return b;
+ } else if (b == 0) {
+ return a;
+ } else if (a > b) {
+ return gcd(b, a % b);
+ } else {
+ return gcd(a, b % a);
}
}
}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/DexTestRunner.java b/libraries/aupt-lib/src/android/support/test/aupt/DexTestRunner.java
new file mode 100644
index 0000000..9a8f1e6
--- /dev/null
+++ b/libraries/aupt-lib/src/android/support/test/aupt/DexTestRunner.java
@@ -0,0 +1,343 @@
+/*
+ * 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.support.test.aupt;
+
+import android.app.Instrumentation;
+import android.test.AndroidTestRunner;
+
+import dalvik.system.DexClassLoader;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestListener;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A DexTestRunner runs tests by name from a given list of JARs,
+ * with the following additional magic:
+ *
+ * - Custom ClassLoading from given dexed Jars
+ * - Custom test scheduling (via Scheduler)
+ *
+ * In addition to the parameters in the constructor, be sure to run setTest or setTestClassName
+ * before attempting to runTest.
+ */
+class DexTestRunner extends AndroidTestRunner {
+ private static final String LOG_TAG = DexTestRunner.class.getSimpleName();
+
+ /* Constants */
+ static final String DEFAULT_JAR_PATH = "/data/local/tmp/";
+ static final String DEX_OPT_PATH = "dex-test-opt";
+
+ /* Private fields */
+ private final List<TestListener> mTestListeners = new ArrayList<>();
+ private final DexClassLoader mLoader;
+ private final long mTimeoutMillis;
+
+ /* TestRunner State */
+ protected TestResult mTestResult = new TestResult();
+ protected List<TestCase> mTestCases = new ArrayList<>();
+ protected String mTestClassName;
+ protected Instrumentation mInstrumentation;
+ protected Scheduler mScheduler;
+
+ /** A temporary ExecutorService to manage running the current test. */
+ private ExecutorService mExecutorService;
+
+ /** The current test. */
+ private TestCase mTestCase;
+
+ /* Field initialization */
+ DexTestRunner(
+ Instrumentation instrumentation,
+ Scheduler scheduler,
+ List<String> jars,
+ long testTimeoutMillis) {
+ super();
+
+ mInstrumentation = instrumentation;
+ mScheduler = scheduler;
+ mLoader = makeLoader(jars);
+ mTimeoutMillis = testTimeoutMillis;
+ }
+
+ /* Main methods */
+
+ @Override
+ public void runTest() {
+ runTest(newResult());
+ }
+
+ @Override
+ public synchronized void runTest(final TestResult testResult) {
+ mTestResult = testResult;
+
+ for (final TestCase testCase : mScheduler.apply(mTestCases)) {
+ mExecutorService = Executors.newSingleThreadExecutor();
+ mTestCase = testCase;
+
+ // A Future that calls testCase::run. The reasoning behind using a thread here
+ // is that AuptTestRunner should be able to interrupt it (via killTest) if it runs
+ // too long; and interrupting the main thread here without actually exiting is tricky.
+ Future<TestResult> result =
+ mExecutorService.submit(
+ new Callable<TestResult>() {
+ @Override
+ public TestResult call() throws Exception {
+ testCase.run(testResult);
+ return testResult;
+ }
+ });
+
+ try {
+ // Run our test-running thread and wait on it.
+ result.get(mTimeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ killTest(e);
+ } catch (ExecutionException e) {
+ onError(testCase, e.getCause());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } finally {
+ mExecutorService.shutdownNow();
+ mTestCase = null;
+ }
+ }
+ }
+
+ /** Interrupt the current test with the given exception. */
+ void killTest(Exception e) {
+ if (mTestCase != null) {
+ // First, tell our listeners.
+ onError(mTestCase, e);
+
+ // Kill the test.
+ mExecutorService.shutdownNow();
+ }
+ }
+
+ /* TestCase Initialization */
+
+ @Override
+ public void setTestClassName(String className, String methodName) {
+ mTestCases.clear();
+ addTestClassByName(className, methodName);
+ }
+
+ void addTestClassByName(final String className, final String methodName) {
+ try {
+ final Class<?> testClass = mLoader.loadClass(className);
+
+ if (Test.class.isAssignableFrom(testClass)) {
+ Test test = null;
+
+ try {
+ // Make sure it works
+ test = (Test) testClass.getConstructor().newInstance();
+ } catch (Exception e1) { /* If we fail, test will just stay null */ }
+
+ try {
+ test = (Test) testClass.getConstructor(String.class).newInstance(methodName);
+ } catch (Exception e2) { /* If we fail, test will just stay null */ }
+
+ addTest(test);
+ } else {
+ throw new RuntimeException("Test class not found: " + className);
+ }
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException("Class not found: " + ex.getMessage());
+ }
+
+ if (mTestCases.isEmpty()) {
+ throw new RuntimeException("No tests found in " + className + "#" + methodName);
+ }
+ }
+
+ @Override
+ public void setTest(Test test) {
+ mTestCases.clear();
+ addTest(test);
+
+ // Update our test class name.
+ if (TestSuite.class.isAssignableFrom(test.getClass())) {
+ mTestClassName = ((TestSuite) test).getName();
+ } else if (TestCase.class.isAssignableFrom(test.getClass())) {
+ mTestClassName = ((TestCase) test).getName();
+ } else {
+ mTestClassName = test.getClass().getSimpleName();
+ }
+ }
+
+ public void addTest(Test test) {
+ if (test instanceof TestCase) {
+
+ mTestCases.add((TestCase) test);
+
+ } else if (test instanceof TestSuite) {
+ Enumeration<Test> tests = ((TestSuite) test).tests();
+
+ while (tests.hasMoreElements()) {
+ addTest(tests.nextElement());
+ }
+ } else {
+ throw new RuntimeException("Tried to add invalid test: " + test.toString());
+ }
+ }
+
+ /* State Manipulation Methods */
+
+ @Override
+ public void clearTestListeners() {
+ mTestListeners.clear();
+ }
+
+ @Override
+ public void addTestListener(TestListener testListener) {
+ if (testListener != null) {
+ mTestListeners.add(testListener);
+ mTestResult.addListener(testListener);
+ }
+ }
+
+ void addTestListenerIf(Boolean cond, TestListener testListener) {
+ if (cond && testListener != null) {
+ mTestListeners.add(testListener);
+ }
+ }
+
+ @Override
+ public List<TestCase> getTestCases() {
+ return mTestCases;
+ }
+
+ @Override
+ public void setInstrumentation(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ @Override
+ public TestResult getTestResult() {
+ return mTestResult;
+ }
+
+ @Override
+ protected TestResult createTestResult() {
+ return new TestResult();
+ }
+
+ @Override
+ public String getTestClassName() {
+ return mTestClassName;
+ }
+
+ /* Listener Exception Callback. */
+
+ void onError(Test test, Throwable t) {
+ if (t instanceof AssertionFailedError) {
+ for (TestListener listener : mTestListeners) {
+ listener.addFailure(test, (AssertionFailedError) t);
+ }
+ } else {
+ for (TestListener listener : mTestListeners) {
+ listener.addError(test, t);
+ }
+ }
+ }
+
+ /* Package-private Utilities */
+
+ TestResult newResult() {
+ TestResult result = new TestResult();
+
+ for (TestListener listener: mTestListeners) {
+ result.addListener(listener);
+ }
+
+ return result;
+ }
+
+ static List<String> parseDexedJarPaths(String jarString) {
+ List<String> jars = new ArrayList<>();
+
+ for (String jar : jarString.split(":")) {
+ // Check that jar isn't empty, but don't fail because String::split will yield
+ // spurious empty results if, for example, we don't specify any jars, accidentally
+ // start with a leading colon, etc.
+ if (!jar.trim().isEmpty()) {
+ File jarFile = jar.startsWith("/")
+ ? new File(jar)
+ : new File(DEFAULT_JAR_PATH + jar);
+
+ if (jarFile.exists()) {
+ jars.add(jarFile.getAbsolutePath());
+ } else {
+ throw new RuntimeException("Can't find jar file " + jarFile);
+ }
+ }
+ }
+
+ return jars;
+ }
+
+ DexClassLoader getDexClassLoader() {
+ return mLoader;
+ }
+
+ DexClassLoader makeLoader(List<String> jars) {
+ StringBuilder jarFiles = new StringBuilder();
+
+ for (String jar : jars) {
+ if (new File(jar).exists() && new File(jar).canRead()) {
+ if (jarFiles.length() != 0) {
+ jarFiles.append(File.pathSeparator);
+ }
+
+ jarFiles.append(jar);
+ } else {
+ throw new IllegalArgumentException(
+ "Jar file does not exist or not accessible: " + jar);
+ }
+ }
+
+ File optDir = new File(mInstrumentation.getTargetContext().getCacheDir(), DEX_OPT_PATH);
+
+ if (optDir.exists() || optDir.mkdirs()) {
+ return new DexClassLoader(
+ jarFiles.toString(),
+ optDir.getAbsolutePath(),
+ null,
+ DexTestRunner.class.getClassLoader());
+ } else {
+ throw new RuntimeException(
+ "Failed to create dex optimization directory: " + optDir.getAbsolutePath());
+ }
+ }
+}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/FilesystemUtil.java b/libraries/aupt-lib/src/android/support/test/aupt/FilesystemUtil.java
new file mode 100644
index 0000000..f8a0ab9
--- /dev/null
+++ b/libraries/aupt-lib/src/android/support/test/aupt/FilesystemUtil.java
@@ -0,0 +1,137 @@
+/*
+ * 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.support.test.aupt;
+
+import android.app.Instrumentation;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Locale;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+
+import android.app.Instrumentation;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+public class FilesystemUtil {
+ private static final String TAG = FilesystemUtil.class.getSimpleName();
+
+ /** Save the output of a process to a file */
+ public static void saveProcessOutput(Instrumentation instr, String command, File file)
+ throws IOException {
+ Log.d(TAG, String.format("Saving command \"%s\" output into file %s",
+ command, file.getAbsolutePath()));
+
+ OutputStream out = new FileOutputStream(file);
+ saveProcessOutput(instr, command, out);
+ out.close();
+ }
+
+ /** Send the output of a process to an OutputStream. */
+ public static void saveProcessOutput(Instrumentation instr, String command, OutputStream out)
+ throws IOException {
+ try {
+ // First, try to execute via our UiAutomation
+ ParcelFileDescriptor pfd = instr.getUiAutomation().executeShellCommand(command);
+ pipe(new ParcelFileDescriptor.AutoCloseInputStream(pfd), out);
+ } catch (IllegalStateException ise) {
+ // If we don't have a UiAutomation, we'll get an IllegalStatException;
+ // so try to do it via an exec()
+ Process process = Runtime.getRuntime().exec(command);
+ pipe(process.getInputStream(), out);
+
+ // Wait for our process to finish
+ try {
+ process.waitFor();
+ } catch (InterruptedException ie) {
+ throw new IOException("Thread interrupted waiting for command: " + command);
+ }
+
+ // Make sure it succeeded.
+ if(process.exitValue() != 0) {
+ throw new IOException("Failed to save output of command: " + command);
+ }
+ }
+ }
+
+ /** Save a bugreport to the given file */
+ public static void saveBugreport(Instrumentation instr, String filename)
+ throws IOException, InterruptedException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ String cmdline = String.format("/system/bin/sh -c /system/bin/bugreport>%s",
+ templateToFilename(filename));
+ saveProcessOutput(instr, cmdline, baos);
+ baos.close();
+ }
+
+ /** Save annotated Meminfo to our default logging directory */
+ public static void dumpMeminfo(Instrumentation instr, String notes) {
+ long epochSeconds = System.currentTimeMillis() / 1000;
+ File outputDir = new File(Environment.getExternalStorageDirectory(), "meminfo");
+ Log.i(TAG, outputDir.toString());
+ if (!outputDir.exists()) {
+ boolean yes = outputDir.mkdirs();
+ Log.i(TAG, yes ? "created" : "not created");
+ }
+ File outputFile = new File(outputDir, String.format("%d.txt", epochSeconds));
+ Log.i(TAG, outputFile.toString());
+ FileOutputStream fos = null;
+
+ try {
+ fos = new FileOutputStream(outputFile);
+ fos.write(String.format("notes: %s\n\n", notes).getBytes());
+
+ saveProcessOutput(instr, "dumpsys meminfo -c", fos);
+ fos.close();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "exception while dumping meminfo", e);
+ } catch (IOException e) {
+ Log.e(TAG, "exception while dumping meminfo", e);
+ }
+ }
+
+ /** Splice the date into the "%s" in a file name */
+ public static String templateToFilename(String filenameTemplate) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
+ return String.format(filenameTemplate, sdf.format(new Date()));
+ }
+
+ /** Pipe an inputstream to an outputstream. This matches Apache's IOUtils::copy */
+ private static void pipe(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[4096];
+ int bytesRead = 0;
+
+ try {
+ while (bytesRead >= 0) {
+ out.write(buffer, 0, bytesRead);
+ bytesRead = in.read(buffer);
+ }
+ } finally {
+ in.close();
+ out.flush();
+ }
+ }
+}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/GraphicsStatsMonitor.java b/libraries/aupt-lib/src/android/support/test/aupt/GraphicsStatsMonitor.java
deleted file mode 100644
index 2efeace..0000000
--- a/libraries/aupt-lib/src/android/support/test/aupt/GraphicsStatsMonitor.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.test.aupt;
-
-import android.app.UiAutomation;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * GraphicsStatsMonitor is an internal monitor for AUPT to poll and track the information coming out
- * of the shell command, "dumpsys graphicsstats." In particular, the purpose of this is to see jank
- * statistics across the lengthy duration of an AUPT run.
- * <p>
- * To use the monitor, simply specify the options: trackJank true and jankInterval n, where n is
- * the polling interval in milliseconds. The default is 5 minutes. Also, it should be noted that
- * the trackJank option is unnecessary and this comment should be removed at the same time as it.
- * <p>
- * This graphics service monitors jank levels grouped by foreground process. Even when the process
- * is killed, the monitor will continue to track information, unless the buffer runs out of space.
- * This should only occur when too many foreground processes have been killed and the service
- * decides to clear itself. When pulling the information out of the monitor, these separate images
- * are combined to provide a single image as output. The linear information is preserved by simply
- * adding the values together. However, certain information such as the jank percentiles are
- * approximated using a weighted average.
- */
-public class GraphicsStatsMonitor {
- private static final String TAG = "GraphicsStatsMonitor";
-
- public static final int MS_IN_SECS = 1000;
- public static final int SECS_IN_MIN = 60;
- public static final long DEFAULT_INTERVAL_RATE = 5 * SECS_IN_MIN * MS_IN_SECS;
-
- private Timer mIntervalTimer;
- private TimerTask mIntervalTask;
- private long mIntervalRate;
- private boolean mIsRunning;
-
- private Map<String, List<JankStat>> mGraphicsStatsRecords;
-
-
- public GraphicsStatsMonitor () {
- mIntervalTask = new TimerTask() {
- @Override
- public void run () {
- if (mIsRunning) {
- grabStatsImage();
- }
- }
- };
- mIntervalRate = DEFAULT_INTERVAL_RATE;
- mIsRunning = false;
- }
-
- /**
- * Sets the monitoring interval rate if the monitor isn't currently running
- */
- public void setIntervalRate (long intervalRate) {
- if (mIsRunning) {
- Log.e(TAG, "Can't set interval rate for monitor that is already running");
- } else if (intervalRate > 0L) {
- mIntervalRate = intervalRate;
- Log.v(TAG, String.format("Set jank monitor interval rate to %d", intervalRate));
- }
- }
-
- /**
- * Starts to monitor graphics stats on the interval timer after clearing the stats
- */
- public void startMonitoring () {
- if (mGraphicsStatsRecords == null) {
- mGraphicsStatsRecords = new HashMap<>();
- }
-
- clearGraphicsStats();
-
- // Schedule a daemon timer to grab stats periodically
- mIntervalTimer = new Timer(true);
- mIntervalTimer.schedule(mIntervalTask, 0, mIntervalRate);
-
- mIsRunning = true;
- Log.d(TAG, "Started monitoring graphics stats");
- }
-
- /**
- * Stops monitoring graphics stats by canceling the interval timer
- */
- public void stopMonitoring () {
- mIntervalTimer.cancel();
-
- mIsRunning = false;
- Log.d(TAG, "Stopped monitoring graphics stats");
- }
-
- /**
- * Takes a snapshot of the graphics stats and incorporates them into the process stats history
- */
- public void grabStatsImage () {
- Log.v(TAG, "Grabbing image of graphics stats");
- List<JankStat> allStats = gatherGraphicsStats();
-
- for (JankStat procStats : allStats) {
- List<JankStat> history;
- if (mGraphicsStatsRecords.containsKey(procStats.packageName)) {
- history = mGraphicsStatsRecords.get(procStats.packageName);
- // Has the process been killed and restarted?
- if (procStats.isContinuedFrom(history.get(history.size() - 1))) {
- // Process hasn't been killed and restarted; put the data
- history.set(history.size() - 1, procStats);
- Log.v(TAG, String.format("Process %s stats have not changed, overwriting data.",
- procStats.packageName));
- } else {
- // Process has been killed and restarted; append the data
- history.add(procStats);
- Log.v(TAG, String.format("Process %s stats were restarted, appending data.",
- procStats.packageName));
- }
- } else {
- // Initialize the process stats history list
- history = new ArrayList<>();
- history.add(procStats);
- // Put the history list in the JankStats map
- mGraphicsStatsRecords.put(procStats.packageName, history);
- Log.v(TAG, String.format("New process, %s. Creating jank history.",
- procStats.packageName));
- }
- }
- }
-
- /**
- * Aggregates the graphics stats for each process over its history. Merging specifications can
- * be found in the static method {@link JankStat#mergeStatHistory}.
- */
- public List<JankStat> aggregateStatsImages () {
- Log.d(TAG, "Aggregating graphics stats history");
- List<JankStat> mergedStatsList = new ArrayList<JankStat>();
-
- for (Map.Entry<String, List<JankStat>> record : mGraphicsStatsRecords.entrySet()) {
- String proc = record.getKey();
- List<JankStat> history = record.getValue();
-
- Log.v(TAG, String.format("Aggregating stats for %s (%d set%s)", proc, history.size(),
- (history.size() > 1 ? "s" : "")));
-
- JankStat mergedStats = JankStat.mergeStatHistory(history);
- mergedStatsList.add(mergedStats);
- }
-
- return mergedStatsList;
- }
-
- /**
- * Clears all graphics stats history data for all processes
- */
- public void clearStatsImages () {
- mGraphicsStatsRecords.clear();
- }
-
- /**
- * Resets graphics stats for all currently tracked processes
- */
- public void clearGraphicsStats () {
- Log.d(TAG, "Reset all graphics stats");
- List<JankStat> existingStats = gatherGraphicsStats();
- for (JankStat stat : existingStats) {
- executeShellCommand(String.format("dumpsys gfxinfo %s reset", stat.packageName));
- Log.v(TAG, String.format("Cleared graphics stats for %s", stat.packageName));
- }
- }
-
- /*
- * Return JankStat objects from a stream representing the output of `dumpsys graphicsstats`
- *
- * This is broken out from gatherGraphicsStats for testing purposes
- */
- private List<JankStat> parseGraphicsStatsFromStream(BufferedReader stream) throws IOException {
- // TODO: this kind of stream filtering is much nicer using the Java 8 functional
- // primitives. Once AUPT goes to jdk8, we should refactor this.
-
- JankStat.StatPattern patterns[] = JankStat.StatPattern.values();
- List<JankStat> result = new ArrayList<>();
- JankStat nextStat = new JankStat(null, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
- String line;
-
- // Split the stream by package
- while((line = stream.readLine()) != null) {
- if(JankStat.StatPattern.PACKAGE.parse(line) != null) {
-
- // When the output of `dumpsys graphicsstats` enters a new set of jank stats, we start
- // with a line matching the PACKAGE pattern; so we have to make a new JankStat for it and
- // save the old one.
-
- if(nextStat.packageName != null) {
- Log.v(TAG, String.format("Gathered jank info from process %s.", nextStat.packageName));
- result.add(nextStat);
- }
-
- nextStat = new JankStat(JankStat.StatPattern.PACKAGE.parse(line),
- 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
-
- } else {
-
- // NOTE: we know these theoretically come in order, so we don't have to iterate
- // through the whole pattern array every time; but there is enough variation in
- // the code generating these log lines to justify a more input-robust parser
-
- for (JankStat.StatPattern p : patterns) {
- if (p.getPattern() != null && p.parse(line) != null) {
- switch (p) {
- case STATS_SINCE:
- nextStat.statsSince = Long.parseLong(p.parse(line));
- break;
- case TOTAL_FRAMES:
- nextStat.totalFrames = Integer.valueOf(p.parse(line));
- break;
- case NUM_JANKY:
- nextStat.jankyFrames = Integer.valueOf(p.parse(line));
- break;
- case FRAME_TIME_50TH:
- nextStat.frameTime50th = Integer.valueOf(p.parse(line));
- break;
- case FRAME_TIME_90TH:
- nextStat.frameTime90th = Integer.valueOf(p.parse(line));
- break;
- case FRAME_TIME_95TH:
- nextStat.frameTime95th = Integer.valueOf(p.parse(line));
- break;
- case FRAME_TIME_99TH:
- nextStat.frameTime99th = Integer.valueOf(p.parse(line));
- break;
- case NUM_MISSED_VSYNC:
- nextStat.numMissedVsync = Integer.valueOf(p.parse(line));
- break;
- case NUM_HIGH_INPUT_LATENCY:
- nextStat.numHighLatency = Integer.valueOf(p.parse(line));
- break;
- case NUM_SLOW_UI_THREAD:
- nextStat.numSlowUiThread = Integer.valueOf(p.parse(line));
- break;
- case NUM_SLOW_BITMAP_UPLOADS:
- nextStat.numSlowBitmap = Integer.valueOf(p.parse(line));
- break;
- case NUM_SLOW_DRAW:
- nextStat.numSlowDraw = Integer.valueOf(p.parse(line));
- break;
- default:
- throw new RuntimeException(
- "Unexpected parsing state in GraphicsStateMonitor");
- }
- }
- }
- }
- }
-
- // Remember to add the last JankStat
- // We can't wrap this in the previous call because BufferedReader doesn't have a .peek()
- if(nextStat.packageName != null) {
- Log.v(TAG, String.format("Gathered jank info from process %s.", nextStat.packageName));
- result.add(nextStat);
- }
-
- return result;
- }
-
- /**
- * Return JankStat objects with metric data for all currently tracked processes
- */
- public List<JankStat> gatherGraphicsStats () {
- Log.v(TAG, "Gather all graphics stats");
- BufferedReader stream = executeShellCommand("dumpsys graphicsstats");
-
- try {
- return parseGraphicsStatsFromStream(stream);
- } catch (IOException exception) {
- Log.e(TAG, "Error with buffered reader", exception);
- return null;
- } finally {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException exception) {
- Log.e(TAG, "Error with closing the stream", exception);
- }
- }
- }
-
- /**
- * UiAutomation is included solely for the purpose of executing shell commands
- */
- private UiAutomation mUiAutomation;
-
- /**
- * Executes a shell command through UiAutomation and puts the results in an
- * InputStreamReader that is returned inside a BufferedReader.
- * @param command the command to be executed in the adb shell
- * @result a BufferedReader that reads the command output
- */
- public BufferedReader executeShellCommand (String command) {
- ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
-
- BufferedReader stream = new BufferedReader(new InputStreamReader(
- new ParcelFileDescriptor.AutoCloseInputStream(stdout)));
- return stream;
- }
-
- /**
- * Sets the UiAutomation member for shell execution
- */
- public void setUiAutomation (UiAutomation uiAutomation) {
- mUiAutomation = uiAutomation;
- }
-
- /**
- * @return UiAutomation instance from Aupt instrumentation
- */
- public UiAutomation getUiAutomation () {
- return mUiAutomation;
- }
-}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/JankStat.java b/libraries/aupt-lib/src/android/support/test/aupt/JankStat.java
deleted file mode 100644
index d65e102..0000000
--- a/libraries/aupt-lib/src/android/support/test/aupt/JankStat.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.test.aupt;
-
-import android.util.Log;
-
-import java.lang.StringBuilder;
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-import org.json.JSONObject;
-import org.json.JSONException;
-
-/**
- * This class is like a C-style struct that holds individual process information from the
- * dumpsys graphicsstats command. It also includes an enumeration, originally from the
- * JankTestHelper code, which pattern matches against the dump data to find the relevant
- * information.
- */
-public class JankStat {
- private static final String TAG = "JankStat";
-
- // Patterns used for parsing dumpsys graphicsstats
- public enum StatPattern {
- PACKAGE("package",
- Pattern.compile("\\s*Package: (.*)"), 1),
-
- STATS_SINCE("startTime",
- Pattern.compile("\\s*Stats since: (\\d+)ns"), 1),
-
- TOTAL_FRAMES("frameCount",
- Pattern.compile("\\s*Total frames rendered: (\\d+)"), 1),
-
- NUM_JANKY("jankyCount",
- Pattern.compile("\\s*Janky frames: (\\d+) (.*)"), 1),
-
- FRAME_TIME_50TH("percentile50",
- Pattern.compile("\\s*50th percentile: (\\d+)ms"), 1),
-
- FRAME_TIME_90TH("percentile90",
- Pattern.compile("\\s*90th percentile: (\\d+)ms"), 1),
-
- FRAME_TIME_95TH("percentile95",
- Pattern.compile("\\s*95th percentile: (\\d+)ms"), 1),
-
- FRAME_TIME_99TH("percentile99",
- Pattern.compile("\\s*99th percentile: (\\d+)ms"), 1),
-
- NUM_MISSED_VSYNC("missedVsyncCount",
- Pattern.compile("\\s*Number Missed Vsync: (\\d+)"), 1),
-
- NUM_HIGH_INPUT_LATENCY("highLatencyCount",
- Pattern.compile("\\s*Number High input latency: (\\d+)"), 1),
-
- NUM_SLOW_UI_THREAD("slowUIThreadCount",
- Pattern.compile("\\s*Number Slow UI thread: (\\d+)"), 1),
-
- NUM_SLOW_BITMAP_UPLOADS("slowBitmapUploadCount",
- Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)"), 1),
-
- NUM_SLOW_DRAW("slowDrawCmdCount",
- Pattern.compile("\\s*Number Slow issue draw commands: (\\d+)"), 1),
-
- AGGREGATE_COUNT("aggregateCount", null, 1);
-
- private String mName;
- private Pattern mParsePattern;
- private int mGroupIdx;
-
- /**
- * Constructs each pattern for parsing the statistics
- * generated by `dumpsys graphicsstats`
- *
- * "name" is a unique JSON key for the field
- * "pattern" is the regex for parsing out the field
- * "idx" the match-index for the relevant field in that pattern
- */
- StatPattern(String name, Pattern pattern, int idx) {
- mName = name;
- mParsePattern = pattern;
- mGroupIdx = idx;
- }
-
- String parse(String line) {
- String ret = null;
- Matcher matcher = mParsePattern.matcher(line);
- if (matcher.matches()) {
- ret = matcher.group(mGroupIdx);
- }
- return ret;
- }
-
- Pattern getPattern() {
- return mParsePattern;
- }
-
- String getName() {
- return mName;
- }
- }
-
- public String packageName;
- public Long statsSince;
- public Integer totalFrames;
- public Integer jankyFrames;
- public Integer frameTime50th;
- public Integer frameTime90th;
- public Integer frameTime95th;
- public Integer frameTime99th;
- public Integer numMissedVsync;
- public Integer numHighLatency;
- public Integer numSlowUiThread;
- public Integer numSlowBitmap;
- public Integer numSlowDraw;
- public Integer aggregateCount;
-
- public JankStat (String pkg, long since, int total, int janky, int ft50, int ft90, int ft95,
- int ft99, int vsync, int latency, int slowUi, int slowBmp, int slowDraw,
- int aggCount) {
- packageName = pkg;
- statsSince = since;
- totalFrames = total;
- jankyFrames = janky;
- frameTime50th = ft50;
- frameTime90th = ft90;
- frameTime95th = ft95;
- frameTime99th = ft99;
- numMissedVsync = vsync;
- numHighLatency = latency;
- numSlowUiThread = slowUi;
- numSlowBitmap = slowBmp;
- numSlowDraw = slowDraw;
- aggregateCount = aggCount;
- }
-
- /**
- * Determines if this set of janks stats is aggregated from the
- * previous set of metrics or if they are a new set, meaning the
- * old process was killed, had its stats reset, and was then
- * restarted.
- */
- public boolean isContinuedFrom (JankStat prevMetrics) {
- return statsSince == prevMetrics.statsSince;
- }
-
- /**
- * Returns the percent of frames that appeared janky
- */
- public float getPercentJankyFrames () {
- return jankyFrames / (float)totalFrames;
- }
-
- /**
- * Serialize this object into a JSONObject
- */
- public JSONObject toJson () throws JSONException {
- return new JSONObject().
- put(StatPattern.PACKAGE.getName(), packageName).
- put(StatPattern.STATS_SINCE.getName(), statsSince).
- put(StatPattern.TOTAL_FRAMES.getName(), totalFrames).
- put(StatPattern.NUM_JANKY.getName(), jankyFrames).
- put(StatPattern.FRAME_TIME_50TH.getName(), frameTime50th).
- put(StatPattern.FRAME_TIME_90TH.getName(), frameTime90th).
- put(StatPattern.FRAME_TIME_95TH.getName(), frameTime95th).
- put(StatPattern.FRAME_TIME_99TH.getName(), frameTime99th).
- put(StatPattern.NUM_MISSED_VSYNC.getName(), numMissedVsync).
- put(StatPattern.NUM_HIGH_INPUT_LATENCY.getName(), numHighLatency).
- put(StatPattern.NUM_SLOW_UI_THREAD.getName(), numSlowUiThread).
- put(StatPattern.NUM_SLOW_BITMAP_UPLOADS.getName(), numSlowBitmap).
- put(StatPattern.NUM_SLOW_DRAW.getName(), numSlowDraw).
- put(StatPattern.AGGREGATE_COUNT.getName(), aggregateCount);
- }
-
- /**
- * @{inheritDoc}
- */
- @Override
- public String toString () {
- try {
- return toJson().toString(4);
- } catch (JSONException e) {
- throw new RuntimeException("Error serializing JankStat: " + e.toString());
- }
- }
-
- /**
- * Merges the stat history of a sequence of stats.
- *
- * Final count value = sum of count values across stats
- * Final ##th percentile = weighted average of ##th, weight by total frames
- * ## = 90, 95, and 99
- */
- public static JankStat mergeStatHistory (List<JankStat> statHistory) {
- if (statHistory.size() == 0)
- return null;
- else if (statHistory.size() == 1)
- return statHistory.get(0);
-
- String pkg = statHistory.get(0).packageName;
- long totalStatsSince = statHistory.get(0).statsSince;
- int totalTotalFrames = 0;
- int totalJankyFrames = 0;
- int totalNumMissedVsync = 0;
- int totalNumHighLatency = 0;
- int totalNumSlowUiThread = 0;
- int totalNumSlowBitmap = 0;
- int totalNumSlowDraw = 0;
-
- for (JankStat stat : statHistory) {
- totalTotalFrames += stat.totalFrames;
- totalJankyFrames += stat.jankyFrames;
- totalNumMissedVsync += stat.numMissedVsync;
- totalNumHighLatency += stat.numHighLatency;
- totalNumSlowUiThread += stat.numSlowUiThread;
- totalNumSlowBitmap += stat.numSlowBitmap;
- totalNumSlowDraw += stat.numSlowDraw;
- }
-
- float wgtAvgPercentile50 = 0f;
- float wgtAvgPercentile90 = 0f;
- float wgtAvgPercentile95 = 0f;
- float wgtAvgPercentile99 = 0f;
-
- for (JankStat stat : statHistory) {
- float weight = ((float)stat.totalFrames / totalTotalFrames);
- Log.v(TAG, String.format("Calculated weight is %f", weight));
- wgtAvgPercentile50 += stat.frameTime50th * weight;
- wgtAvgPercentile90 += stat.frameTime90th * weight;
- wgtAvgPercentile95 += stat.frameTime95th * weight;
- wgtAvgPercentile99 += stat.frameTime99th * weight;
- }
-
- int perc50 = (int)Math.ceil(wgtAvgPercentile50);
- int perc90 = (int)Math.ceil(wgtAvgPercentile90);
- int perc95 = (int)Math.ceil(wgtAvgPercentile95);
- int perc99 = (int)Math.ceil(wgtAvgPercentile99);
-
- return new JankStat(pkg, totalStatsSince, totalTotalFrames,
- totalJankyFrames, perc50, perc90, perc95, perc99,
- totalNumMissedVsync, totalNumHighLatency, totalNumSlowUiThread, totalNumSlowBitmap,
- totalNumSlowDraw, statHistory.size());
- }
-
- /**
- * Returns a long String containing each JankStat object separated by a
- * newline. Ideally, this would omit objects with zero rendered total
- * frames, which is junk data.
- */
- public static String statsListToString (List<JankStat> statsList) {
- StringBuilder result = new StringBuilder();
- for (JankStat stats : statsList) {
- result.append(stats.toString());
- result.append("\n");
- }
-
- return result.toString();
- }
-}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/LogGenerator.java b/libraries/aupt-lib/src/android/support/test/aupt/LogGenerator.java
new file mode 100644
index 0000000..78bbd30
--- /dev/null
+++ b/libraries/aupt-lib/src/android/support/test/aupt/LogGenerator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.support.test.aupt;
+
+import android.app.Instrumentation;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+enum LogGenerator {
+ BUGREPORT (new BugreportGenerator()),
+ GRAPHICS_STATS (new GraphicsGenerator()),
+ MEM_INFO (new CompactMemInfoGenerator()),
+ CPU_INFO (new CpuInfoGenerator()),
+ FRAGMENTATION (new FragmentationGenerator()),
+ ION_HEAP (new IonHeapGenerator()),
+ PAGETYPE_INFO (new PageTypeInfoGenerator()),
+ TRACE (new TraceGenerator());
+
+ private static final String TAG = "AuptDataCollector";
+
+ /** Save the output of a process to a log file with the given name template. */
+ private static void saveLog(
+ Instrumentation instr,
+ String command,
+ String template) throws IOException {
+ FilesystemUtil.saveProcessOutput(
+ instr,
+ command,
+ new File(FilesystemUtil.templateToFilename(template)));
+ }
+
+ /* Generator Types */
+
+ protected interface Generator {
+ void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException;
+ }
+
+ private static class CompactMemInfoGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "dumpsys meminfo -c -S", logDir + "/compact-meminfo-%s.txt");
+ } catch (IOException ioe) {
+ Log.w(TAG, "Error while saving dumpsys meminfo -c: " + ioe.getMessage());
+ }
+ }
+ }
+
+ private static class CpuInfoGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "dumpsys cpuinfo", logDir + "/cpuinfo-%s.txt");
+ } catch (IOException ioe) {
+ Log.w(TAG, "Error while saving dumpsys cpuinfo : " + ioe.getMessage());
+ }
+ }
+ }
+
+ private static class BugreportGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ FilesystemUtil.saveBugreport(instr, logDir + "/bugreport-%s.txt");
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Failed to take bugreport: %s", e.getMessage()));
+ }
+ }
+ }
+
+ private static class FragmentationGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "cat /d/extfrag/unusable_index", logDir + "/unusable-index-%s.txt");
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Failed to save frangmentation: %s", e.getMessage()));
+ }
+ }
+ }
+
+ private static class GraphicsGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "dumpsys graphicsstats", logDir + "/graphics-%s.txt");
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Failed to save graphicsstats: %s", e.getMessage()));
+ }
+ }
+ }
+
+ private static class IonHeapGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "cat /d/ion/heaps/audio", logDir + "/ion-audio-%s.txt");
+ saveLog(instr, "cat /d/ion/heaps/system", logDir + "/ion-system-%s.txt");
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Failed to save ION heap: %s", e.getMessage()));
+ }
+ }
+ }
+
+ private static class PageTypeInfoGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "cat /proc/pagetypeinfo", logDir + "/pagetypeinfo-%s.txt");
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Failed to save pagetypeinfo: %s", e.getMessage()));
+ }
+ }
+ }
+
+ private static class TraceGenerator implements Generator {
+ @Override
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ try {
+ saveLog(instr, "cat /sys/kernel/debug/tracing/trace", logDir + "/trace-%s.txt");
+ } catch (IOException e) {
+ Log.w(TAG, String.format("Failed to save trace: %s", e.getMessage()));
+ }
+ }
+ }
+
+ // Individual LogGenerator instance methods
+ private final Generator mGenerator;
+
+ LogGenerator (Generator generator) {
+ mGenerator = generator;
+ }
+
+ public void save(Instrumentation instr, String logDir)
+ throws IOException, InterruptedException {
+ mGenerator.save(instr, logDir);
+ }
+}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/MemHealthRecord.java b/libraries/aupt-lib/src/android/support/test/aupt/MemHealthRecord.java
new file mode 100644
index 0000000..ef2fb563
--- /dev/null
+++ b/libraries/aupt-lib/src/android/support/test/aupt/MemHealthRecord.java
@@ -0,0 +1,521 @@
+/*
+ * 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.support.test.aupt;
+
+import android.app.Instrumentation;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class MemHealthRecord {
+ // Process State
+ private final String mProcName;
+ private final boolean mInForeground;
+
+ // Memory health state
+ private final long mTimeMs;
+ private final long mDalvikHeap;
+ private final long mNativeHeap;
+ private final long mPss;
+
+ // App summary metrics
+ private final long mAsJavaHeap;
+ private final long mAsNativeHeap;
+ private final long mAsCode;
+ private final long mAsStack;
+ private final long mAsGraphics;
+ private final long mAsOther;
+ private final long mAsSystem;
+ private final long mAsOverallPss;
+
+ public MemHealthRecord(String procName, long timeMs, long dalvikHeap, long nativeHeap, long pss,
+ long asJavaHeap, long asNativeHeap, long asCode, long asStack,
+ long asGraphics, long asOther, long asSystem, long asOverallPss,
+ boolean inForeground) {
+ mProcName = procName;
+ mTimeMs = timeMs;
+ mDalvikHeap = dalvikHeap;
+ mNativeHeap = nativeHeap;
+ mPss = pss;
+ mAsJavaHeap = asJavaHeap;
+ mAsNativeHeap = asNativeHeap;
+ mAsCode = asCode;
+ mAsStack = asStack;
+ mAsGraphics = asGraphics;
+ mAsOther = asOther;
+ mAsSystem = asSystem;
+ mAsOverallPss = asOverallPss;
+ mInForeground = inForeground;
+ }
+
+ public MemHealthRecord(
+ String procName, long timeMs, long dalvikHeap,
+ long nativeHeap, long pss, boolean inForeground) {
+ this(procName, timeMs, dalvikHeap, nativeHeap, pss, 0, 0, 0, 0, 0, 0, 0, 0, inForeground);
+ }
+
+ /* Static methods */
+
+ static List<MemHealthRecord> get(
+ Instrumentation instr,
+ List<String> procNames,
+ long timeMs,
+ List<String> foregroundProcs) throws IOException {
+
+ List<MemHealthRecord> records = new ArrayList<>();
+
+ for (String procName : procNames) {
+ String meminfo = getMeminfoOutput(instr, procName);
+ int nativeHeap = parseMeminfoLine(meminfo, "Native Heap\\s+\\d+\\s+(\\d+)");
+ int dalvikHeap = parseMeminfoLine(meminfo, "Dalvik Heap\\s+\\d+\\s+(\\d+)");
+ int pss = parseMeminfoLine(meminfo, "TOTAL\\s+(\\d+)");
+
+ int asJavaHeap = parseMeminfoLine(meminfo, "Java Heap:\\s+(\\d+)");
+ int asNativeHeap = parseMeminfoLine(meminfo, "Native Heap:\\s+(\\d+)");
+ int asCode = parseMeminfoLine(meminfo, "Code:\\s+(\\d+)");
+ int asStack = parseMeminfoLine(meminfo, "Stack:\\s+(\\d+)");
+ int asGraphics = parseMeminfoLine(meminfo, "Graphics:\\s+(\\d+)");
+ int asOther = parseMeminfoLine(meminfo, "Private Other:\\s+(\\d+)");
+ int asSystem = parseMeminfoLine(meminfo, "System:\\s+(\\d+)");
+ int asOverallPss = parseMeminfoLine(meminfo, "TOTAL:\\s+(\\d+)");
+
+ if (nativeHeap < 0 || dalvikHeap < 0 || pss < 0) {
+ continue;
+ }
+
+ records.add(new MemHealthRecord(
+ procName, timeMs, dalvikHeap, nativeHeap, pss, asJavaHeap,
+ asNativeHeap, asCode, asStack, asGraphics, asOther, asSystem,
+ asOverallPss, foregroundProcs.contains(procName)));
+ }
+
+ return records;
+ }
+
+ static void saveVerbose(Collection<MemHealthRecord> allRecords, String fileName)
+ throws IOException {
+ PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName, true)));
+
+ Map<String, List<MemHealthRecord>> fgRecords = getRecordMap(allRecords, true);
+ Map<String, List<MemHealthRecord>> bgRecords = getRecordMap(allRecords, false);
+
+ out.println("Foreground");
+
+ for (Map.Entry<String, List<MemHealthRecord>> entry : fgRecords.entrySet()) {
+ String procName = entry.getKey();
+ List<MemHealthRecord> records = entry.getValue();
+
+ List<Long> nativeHeap = getForegroundNativeHeap(records);
+ List<Long> dalvikHeap = getForegroundDalvikHeap(records);
+ List<Long> pss = getForegroundPss(records);
+ List<Long> asJavaHeap = getForegroundSummaryJavaHeap(records);
+ List<Long> asNativeHeap = getForegroundSummaryNativeHeap(records);
+ List<Long> asCode = getForegroundSummaryCode(records);
+ List<Long> asStack = getForegroundSummaryStack(records);
+ List<Long> asGraphics = getForegroundSummaryGraphics(records);
+ List<Long> asOther = getForegroundSummaryOther(records);
+ List<Long> asSystem = getForegroundSummarySystem(records);
+ List<Long> asOverallPss = getForegroundSummaryOverallPss(records);
+
+ // nativeHeap, dalvikHeap, and pss all have the same size, just use one
+ if (nativeHeap.isEmpty()) {
+ continue;
+ }
+
+ out.println(procName);
+ out.printf("Average Native Heap: %d\n", getAverage(nativeHeap));
+ out.printf("Average Dalvik Heap: %d\n", getAverage(dalvikHeap));
+ out.printf("Average PSS: %d\n", getAverage(pss));
+ out.printf("Peak Native Heap: %d\n", getMax(nativeHeap));
+ out.printf("Peak Dalvik Heap: %d\n", getMax(dalvikHeap));
+ out.printf("Peak PSS: %d\n", getMax(pss));
+ out.printf("Count %d\n", nativeHeap.size());
+
+ out.printf("Average Summary Java Heap: %d\n", getAverage(asJavaHeap));
+ out.printf("Average Summary Native Heap: %d\n", getAverage(asNativeHeap));
+ out.printf("Average Summary Code: %d\n", getAverage(asCode));
+ out.printf("Average Summary Stack: %d\n", getAverage(asStack));
+ out.printf("Average Summary Graphics: %d\n", getAverage(asGraphics));
+ out.printf("Average Summary Other: %d\n", getAverage(asOther));
+ out.printf("Average Summary System: %d\n", getAverage(asSystem));
+ out.printf("Average Summary Overall Pss: %d\n", getAverage(asOverallPss));
+ }
+
+ out.println("Background");
+ for (Map.Entry<String, List<MemHealthRecord>> entry : fgRecords.entrySet()) {
+ String procName = entry.getKey();
+ List<MemHealthRecord> records = entry.getValue();
+
+ List<Long> nativeHeap = getBackgroundNativeHeap(records);
+ List<Long> dalvikHeap = getBackgroundDalvikHeap(records);
+ List<Long> pss = getBackgroundPss(records);
+ List<Long> asJavaHeap = getBackgroundSummaryJavaHeap(records);
+ List<Long> asNativeHeap = getBackgroundSummaryNativeHeap(records);
+ List<Long> asCode = getBackgroundSummaryCode(records);
+ List<Long> asStack = getBackgroundSummaryStack(records);
+ List<Long> asGraphics = getBackgroundSummaryGraphics(records);
+ List<Long> asOther = getBackgroundSummaryOther(records);
+ List<Long> asSystem = getBackgroundSummarySystem(records);
+ List<Long> asOverallPss = getBackgroundSummaryOverallPss(records);
+
+ // nativeHeap, dalvikHeap, and pss all have the same size, just use one
+ if (nativeHeap.isEmpty()) {
+ continue;
+ }
+
+ out.println(procName);
+ out.printf("Average Native Heap: %d\n", getAverage(nativeHeap));
+ out.printf("Average Dalvik Heap: %d\n", getAverage(dalvikHeap));
+ out.printf("Average PSS: %d\n", getAverage(pss));
+ out.printf("Peak Native Heap: %d\n", getMax(nativeHeap));
+ out.printf("Peak Dalvik Heap: %d\n", getMax(dalvikHeap));
+ out.printf("Peak PSS: %d\n", getMax(pss));
+ out.printf("Count %d\n", nativeHeap.size());
+
+ out.printf("Average Summary Java Heap: %d\n", getAverage(asJavaHeap));
+ out.printf("Average Summary Native Heap: %d\n", getAverage(asNativeHeap));
+ out.printf("Average Summary Code: %d\n", getAverage(asCode));
+ out.printf("Average Summary Stack: %d\n", getAverage(asStack));
+ out.printf("Average Summary Graphics: %d\n", getAverage(asGraphics));
+ out.printf("Average Summary Other: %d\n", getAverage(asOther));
+ out.printf("Average Summary System: %d\n", getAverage(asSystem));
+ out.printf("Average Summary Overall Pss: %d\n", getAverage(asOverallPss));
+ }
+
+ out.close();
+ }
+
+ /**
+ * NOTE (rsloan): I've meaningfully changed this format because the previous iteration was a
+ * horrific mix of CSV and not-CSV
+ */
+ static void saveCsv(Collection<MemHealthRecord> allRecords, String fileName) throws IOException{
+ PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName, true)));
+
+ out.printf("name,time,native_heap,dalvik_heap,pss,context\n");
+ for (MemHealthRecord record : allRecords) {
+ out.printf("%s,%d,%d,%d,%s\n",
+ record.mProcName, record.mTimeMs, record.mNativeHeap,
+ record.mDalvikHeap, record.mInForeground ? "foreground" : "background");
+ }
+
+ out.close();
+ }
+
+ /* Getters defined on a Collection<MemHealthRecord> */
+
+ public static List<Long> getForegroundDalvikHeap(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mDalvikHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundDalvikHeap(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mDalvikHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundNativeHeap(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mNativeHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundNativeHeap(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mNativeHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundPss(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mPss);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundPss(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mPss);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryJavaHeap(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsJavaHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryJavaHeap(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsJavaHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryNativeHeap(
+ Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsNativeHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryNativeHeap(
+ Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsNativeHeap);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryCode(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsCode);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryCode(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsCode);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryStack(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsStack);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryStack(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsStack);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryGraphics(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsGraphics);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryGraphics(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsGraphics);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryOther(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsOther);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryOther(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsOther);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummarySystem(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsSystem);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummarySystem(Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsSystem);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getForegroundSummaryOverallPss(
+ Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (sample.mInForeground) {
+ ret.add(sample.mAsOverallPss);
+ }
+ }
+ return ret;
+ }
+
+ public static List<Long> getBackgroundSummaryOverallPss(
+ Collection<MemHealthRecord> samples) {
+ List<Long> ret = new ArrayList<>(samples.size());
+ for (MemHealthRecord sample : samples) {
+ if (!sample.mInForeground) {
+ ret.add(sample.mAsOverallPss);
+ }
+ }
+ return ret;
+ }
+
+ /* Utility Methods */
+
+ private static Long getMax(Collection<Long> samples) {
+ Long max = null;
+ for (Long sample : samples) {
+ if (max == null || sample > max) {
+ max = sample;
+ }
+ }
+ return max;
+ }
+
+ private static Long getAverage(Collection<Long> samples) {
+ if (samples.size() == 0) {
+ return null;
+ }
+
+ double sum = 0;
+ for (Long sample : samples) {
+ sum += sample;
+ }
+ return (long) (sum / samples.size());
+ }
+
+ private static int parseMeminfoLine(String meminfo, String pattern)
+ {
+ Pattern p = Pattern.compile(pattern);
+ Matcher m = p.matcher(meminfo);
+ if (m.find()) {
+ return Integer.parseInt(m.group(1));
+ } else {
+ return -1;
+ }
+ }
+
+ public static String getMeminfoOutput(Instrumentation instr, String processName)
+ throws IOException {
+ return getProcessOutput(instr, "dumpsys meminfo " + processName);
+ }
+
+ public static String getProcessOutput(Instrumentation instr, String command)
+ throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ FilesystemUtil.saveProcessOutput(instr, command, baos);
+ baos.close();
+ return baos.toString();
+ }
+
+ private static Map<String, List<MemHealthRecord>> getRecordMap(
+ Collection<MemHealthRecord> allRecords,
+ boolean inForeground) {
+
+ Map<String, List<MemHealthRecord>> records = new HashMap<>();
+
+ for (MemHealthRecord record : allRecords) {
+ if (record.mInForeground == inForeground) {
+ if (!records.containsKey(record.mProcName)) {
+ records.put(record.mProcName, new ArrayList<MemHealthRecord>());
+ }
+
+ records.get(record.mProcName).add(record);
+ }
+ }
+
+ return records;
+ }
+
+}
diff --git a/libraries/aupt-lib/src/android/support/test/aupt/Scheduler.java b/libraries/aupt-lib/src/android/support/test/aupt/Scheduler.java
new file mode 100644
index 0000000..02c2d8e
--- /dev/null
+++ b/libraries/aupt-lib/src/android/support/test/aupt/Scheduler.java
@@ -0,0 +1,161 @@
+/*
+ * 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.support.test.aupt;
+
+import junit.framework.TestCase;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * A Scheduler produces an execution ordering for our test cases.
+ *
+ * For example, the Sequential scheduler will just repeat our test cases a fixed number of times.
+ */
+public abstract class Scheduler {
+
+ /** Shuffle and/or iterate through a list of test cases. */
+ public final Iterable<TestCase> apply(final List<TestCase> cases) {
+ return new Iterable<TestCase>() {
+ public Iterator<TestCase> iterator() {
+ return applyInternal(cases);
+ }
+ };
+ }
+
+ /**
+ * A Scheduler that just loops through test cases a fixed number of times.
+ *
+ * @param iterations the number of times to loop through every test case.
+ */
+ public static Scheduler sequential(Long iterations) {
+ return new Sequential(iterations);
+ }
+
+ /**
+ * A Scheduler that permutes the test case list, then just iterates.
+ *
+ * @param iterations the number of times to loop through every test case.
+ * @param random the Random to use: with the same random, we'll return the same ordering.
+ */
+ public static Scheduler shuffled(Random random, Long iterations) {
+ return new Shuffled(random, iterations);
+ }
+
+ /** Private interface to Scheduler::apply */
+ protected abstract Iterator<TestCase> applyInternal(List<TestCase> cases);
+
+ private static class Sequential extends Scheduler {
+ private final Long mIterations;
+
+ Sequential(Long iterations) {
+ mIterations = iterations;
+ }
+
+ protected Iterator<TestCase> applyInternal (final List<TestCase> cases) {
+ return new Iterator<TestCase>() {
+ private int count = 0;
+
+ public boolean hasNext() {
+ return count < (cases.size() * mIterations);
+ }
+
+ public TestCase next() {
+ return cases.get(count++ % cases.size());
+ }
+
+ public void remove() { }
+ };
+ }
+ }
+
+ private static class Shuffled extends Scheduler {
+ private final Random mRandom;
+ private final Long mIterations;
+
+ Shuffled(Random random, Long iterations) {
+ mRandom = random;
+ mIterations = iterations;
+ }
+
+ /**
+ * Find a GCD by the nieve Euclidean Algorithm
+ * TODO: get this from guava or some other library
+ */
+ private int gcd(final int _a, final int _b) {
+ int a = _a;
+ int b = _b;
+
+ while (b > 0) {
+ int tmp = b;
+ b = a % b;
+ a = tmp;
+ }
+
+ return a;
+ }
+
+ /**
+ * Find a random number relatively prime to our modulus
+ * TODO: get this from guava or some other library
+ */
+ private int randomRelPrime(Integer modulus) {
+ if (modulus <= 1) {
+ return 1;
+ } else {
+ int x = 0;
+
+ // Sample random numbers until we get something coprime to our modulus
+ while (gcd(x, modulus) != 1) {
+ x = mRandom.nextInt() % modulus;
+ }
+
+ return x % modulus;
+ }
+ }
+
+ /**
+ * Return the tests in a shuffled order using a simple linear congruential generator: i.e.
+ * the elements are permuted by (a x + b % n), will produce a permutation of the elements of n
+ * iff a is coprime to n.
+ * <p>
+ * The reason to do this is that it also produces a permutation each cases.size() rounds, which
+ * is *not* the case for Collections.shuffle(); and implementing a comparable iterator with
+ * those primitives is somewhat less elegant.
+ */
+ protected Iterator<TestCase> applyInternal(final List<TestCase> cases) {
+ final int a = randomRelPrime(cases.size());
+ final int b = Math.abs(mRandom.nextInt());
+
+ return new Iterator<TestCase>() {
+ private int count = 0;
+
+ public boolean hasNext() {
+ return count < (cases.size() * mIterations);
+ }
+
+ public TestCase next() {
+ return cases.get((a * (count++) + b) % cases.size());
+ }
+
+ public void remove() {
+ }
+ };
+ }
+ }
+}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractAngryBirdsHelper.java b/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractAngryBirdsHelper.java
deleted file mode 100644
index dc1f037..0000000
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractAngryBirdsHelper.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.platform.test.helpers;
-
-import android.app.Instrumentation;
-
-public abstract class AbstractAngryBirdsHelper extends AbstractStandardAppHelper {
-
- public AbstractAngryBirdsHelper(Instrumentation instr) {
- super(instr);
- }
-
- /**
- * Setup expectation: Angry Birds open and on main menu
- *
- * This method will set up game demo by going to level selection menu
- */
- public abstract void setUpDemo();
-
- /**
- * Setup expectation: Angry Birds open and on level selection menu
- *
- * This method plays a game demo for demoDurationInMinutes minutes
- * @param demoDurationInMinutes: game demo duration in minutes
- */
- public abstract void playDemo(int demoDurationInMinutes);
-
- /**
- * Setup expectation: Angry Birds open and on level selection menu
- *
- * This method goes back to the main menu
- */
- public abstract void tearDownDemo();
-}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractFacebookHelper.java b/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractFacebookHelper.java
deleted file mode 100644
index a532263..0000000
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractFacebookHelper.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.platform.test.helpers;
-
-import android.app.Instrumentation;
-import android.support.test.uiautomator.Direction;
-
-public abstract class AbstractFacebookHelper extends AbstractStandardAppHelper {
-
- public AbstractFacebookHelper(Instrumentation instr) {
- super(instr);
- }
-
- /**
- * Setup expectations: Facebook is on the home page.
- *
- * This method scrolls the home page.
- *
- * @param dir the direction to scroll
- */
- public abstract void scrollHomePage(Direction dir);
-
- /**
- * Setup expectations: Facebook app is open.
- *
- * This method keeps pressing the back button until Facebook is on the homepage.
- */
- public abstract void goToHomePage();
-
- /**
- * Setup expectations: Facebook app is on the home page.
- *
- * This method moves the Facebook app to the News Feed tab of the home page.
- */
- public abstract void goToNewsFeed();
-
- /**
- * Setup expectations: Facebook is on the News Feed tab of the home page.
- *
- * This method moves the Facebook app to the status update page.
- */
- public abstract void goToStatusUpdate();
-
- /**
- * Setup expectations: Facebook is on the status update page.
- *
- * This method clicks on the status update text field to move the keyboard cursor there
- */
- public abstract void clickStatusUpdateTextField();
-
- /**
- * Setup expections: Facebook is on the status update page.
- *
- * This method sets the status update text.
- *
- * @param statusText text for status update
- */
- public abstract void setStatusText(String statusText);
-
- /**
- * Setup expectations: Facebook is on the status update page.
- *
- * This method posts the status update.
- */
- public abstract void postStatusUpdate();
-
- /**
- * Setup expectations: Facebook app is on the login page.
- *
- * This method attempts to log in using the specified username and password.
- *
- * @param username username of Facebook account
- * @param password password of Facebook account
- */
- public abstract void login(String username, String password);
-}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractFlightDemoHelper.java b/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractFlightDemoHelper.java
deleted file mode 100644
index 84c31d9..0000000
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractFlightDemoHelper.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.platform.test.helpers;
-
-import android.app.Instrumentation;
-
-public abstract class AbstractFlightDemoHelper extends AbstractStandardAppHelper {
-
- public AbstractFlightDemoHelper(Instrumentation instr) {
- super(instr);
- }
-
- /**
- * Setup expectation: On the opening screen.
- *
- * Best effort attempt to start the flight simulator demo
- */
- public abstract void startDemo();
-
- /**
- * Setup expectation: Currently running the flight simulator demo
- *
- * Best effort attempt to stop the flight simulator demo
- */
- public abstract void stopDemo();
-}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractRedditHelper.java b/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractRedditHelper.java
deleted file mode 100644
index 7cf83a0..0000000
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractRedditHelper.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.platform.test.helpers;
-
-import android.app.Instrumentation;
-import android.support.test.uiautomator.Direction;
-
-public abstract class AbstractRedditHelper extends AbstractStandardAppHelper {
-
- public AbstractRedditHelper(Instrumentation instr) {
- super(instr);
- }
-
- /*
- * Setup expectations: Reddit app is open.
- *
- * This method moves the Reddit app to the front page.
- */
- public abstract void goToFrontPage();
-
- /*
- * Setup expectations: Reddit app is on the front pages.
- *
- * This method moves the Reddit app to the first visible article's comment page.
- */
- public abstract void goToFirstArticleComments();
-
- /*
- * Setup expectations: Reddit app is on the front page.
- *
- * This method scrolls the front page.
- *
- * @param direction Direction in which to scroll, must be UP or DOWN
- * @param percent Percent of page to scroll
- * @return boolean Whether the page can still scroll in the given direction
- */
- public abstract boolean scrollFrontPage(Direction direction, float percent);
-
- /*
- * Setup expectations: Reddit app is on an article's comment page.
- *
- * This method scrolls the comment page.
- *
- * @param direction Direction in which to scroll, must be UP or DOWN
- * @param percent Percent of page to scroll
- * @return boolean Whether the page can still scroll in the given direction
- */
- public abstract boolean scrollCommentPage(Direction direction, float percent);
-}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractSlackerHelper.java b/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractSlackerHelper.java
deleted file mode 100644
index caceb0a..0000000
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractSlackerHelper.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.platform.test.helpers;
-
-import android.app.Instrumentation;
-
-public abstract class AbstractSlackerHelper extends AbstractStandardAppHelper {
-
- public AbstractSlackerHelper(Instrumentation instr) {
- super(instr);
- }
-
- /**
- * Setup expectation: Slacker is on page with playable radio channels
- *
- * This method starts playing one of the playable radio channels.
- * If app is already on channel page, this function starts playing
- * the radio on the current page. If it cannot start streaming a radio
- * channel, it throws an exception.
- */
- public abstract void startAnyChannel();
-
- /**
- * Setup expectation: Slacker is on channel page
- *
- * This method pauses the audio streaming and returns to main page.
- * If app is already on main page, this function does nothing. If it
- * cannot stop streamming channel, it throws an exception.
- */
- public abstract void stopChannel();
-}
diff --git a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractTuneInHelper.java b/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractTuneInHelper.java
deleted file mode 100644
index 1e5ec95..0000000
--- a/libraries/base-app-helpers/src/android/platform/test/helpers/AbstractTuneInHelper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.platform.test.helpers;
-
-import android.app.Instrumentation;
-
-public abstract class AbstractTuneInHelper extends AbstractStandardAppHelper {
-
- public AbstractTuneInHelper(Instrumentation instr) {
- super(instr);
- }
-
- /**
- * Setup expectation: TuneIn app is open, originally on Browse Page
- *
- * This method attempts a few times until go back to Browse Page
- * and assert fails if it doesn't end up on Browse Page
- */
- public abstract void goToBrowsePage();
-
- /**
- * Setup expectation: TuneIn is on Browse page
- *
- * This method blocks until on local radio page
- */
- public abstract void goToLocalRadio();
-
- /**
- * Setup expectation: TuneIn is on Local Radio page
- *
- * This method selects the ith FM from the radio list
- * and goes to radio profile page
- * @param i ith FM
- */
- public abstract void selectFM(int i);
-
- /**
- * Setup expectation: TuneIn is on radio profile page
- *
- * This method starts playing the radio channel
- */
- public abstract void startChannel();
-
- /**
- * Setup expectation: TuneIn is on channel page
- *
- * This method stops the channel and stays on the page
- */
- public abstract void stopChannel();
-}
diff --git a/libraries/launcher-helper/Android.mk b/libraries/launcher-helper/Android.mk
index bd9d62f..9c45503 100644
--- a/libraries/launcher-helper/Android.mk
+++ b/libraries/launcher-helper/Android.mk
@@ -18,7 +18,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := launcher-helper-lib
-LOCAL_JAVA_LIBRARIES := ub-uiautomator
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ activity-helper
LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/AllAppsScreenHelper.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/AllAppsScreenHelper.java
new file mode 100644
index 0000000..56ec6df
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/AllAppsScreenHelper.java
@@ -0,0 +1,82 @@
+/*
+ * 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.support.test.launcherhelper;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.system.helpers.ActivityHelper;
+import android.test.InstrumentationTestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Assert;
+
+public class AllAppsScreenHelper {
+
+ private static final int TIMEOUT = 3000;
+ private static final int LONG_TIMEOUT = 10000;
+ private UiDevice mDevice;
+ private Instrumentation mInstrumentation;
+ private ILauncherStrategy mLauncherStrategy = LauncherStrategyFactory
+ .getInstance(mDevice).getLauncherStrategy();
+ private String allApps = "apps_view";
+ private String appsListView = "apps_list_view";
+ private String searchBox = "search_box_input";
+ private ActivityHelper mActivityHelper;
+
+ public AllAppsScreenHelper() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivityHelper = ActivityHelper.getInstance();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public String getLauncherPackage() {
+ return mDevice.getLauncherPackageName();
+ }
+
+ public void launchAllAppsScreen() {
+ mDevice.pressHome();
+ mDevice.findObject(mLauncherStrategy.getAllAppsButtonSelector()).click();
+ mDevice.wait(Until.hasObject(By.res(getLauncherPackage(), allApps)), TIMEOUT);
+ }
+
+ public void searchAllAppsScreen(String searchString,
+ String[] appNamesExpected) throws Exception {
+ launchAllAppsScreen();
+ UiObject2 searchBoxObject = mDevice.wait(Until.findObject
+ (By.res(getLauncherPackage(), searchBox)), TIMEOUT);
+ searchBoxObject.setText(searchString);
+ for (String appName : appNamesExpected) {
+ Assert.assertNotNull("The following app couldn't be found in the search results: "
+ + appName, mDevice.wait(Until.findObject
+ (By.text(appName)), TIMEOUT));
+ }
+ }
+}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/BaseLauncher3Strategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/BaseLauncher3Strategy.java
index 8bac06e..492519f 100644
--- a/libraries/launcher-helper/src/android/support/test/launcherhelper/BaseLauncher3Strategy.java
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/BaseLauncher3Strategy.java
@@ -88,7 +88,7 @@
// taps on the "apps" button at the bottom of the screen
UiObject2 allAppsButton =
mDevice.wait(Until.findObject(getAllAppsButtonSelector()), 2000);
- Assert.assertNotNull("openAllApps: did not find open all apps button.");
+ Assert.assertNotNull("openAllApps: did not find open all apps button.", allAppsButton);
allAppsButton.click();
// wait until hotseat disappears, so that we know that we are no longer on home screen
mDevice.wait(Until.gone(getHotSeatSelector()), 2000);
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/ILeanbackLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/ILeanbackLauncherStrategy.java
index 54fccde..28b915b 100644
--- a/libraries/launcher-helper/src/android/support/test/launcherhelper/ILeanbackLauncherStrategy.java
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/ILeanbackLauncherStrategy.java
@@ -16,12 +16,13 @@
package android.support.test.launcherhelper;
+import android.app.Instrumentation;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
/**
- * Defines the common use cases a leanback launcher UI automation helper should fulfill.
+ * Defines the common use cases that any types of TV launcher UI automation helper should fulfill.
* <p>Class will be instantiated by {@link LauncherStrategyFactory} based on current launcher
* package, and a {@link UiDevice} instance will be provided via {@link #setUiDevice(UiDevice)}
* method.
@@ -29,7 +30,12 @@
public interface ILeanbackLauncherStrategy extends ILauncherStrategy {
/**
- * Searches for a given query on leanback launcher
+ * Sets an instance of instrumentation
+ */
+ public void setInstrumentation(Instrumentation instrumentation);
+
+ /**
+ * Searches for a given query on TV launcher
*/
public void search(String query);
@@ -76,7 +82,8 @@
public BySelector getNowPlayingCardSelector();
/**
- * Returns a {@link UiObject2} describing the focused search row
+ * Returns a {@link UiObject2} describing the focused search row,
+ * or the top row on new TV Launcher
* @return
*/
public UiObject2 selectSearchRow();
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/Launcher3Strategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/Launcher3Strategy.java
index 6d7fbfc..f6af664 100644
--- a/libraries/launcher-helper/src/android/support/test/launcherhelper/Launcher3Strategy.java
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/Launcher3Strategy.java
@@ -50,6 +50,6 @@
*/
@Override
public BySelector getAllAppsButtonSelector() {
- return By.desc("Apps");
+ return By.res(getSupportedLauncherPackage(), "all_apps_handle");
}
}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java
index cab96cd..c6a52c0 100644
--- a/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java
@@ -46,6 +46,7 @@
registerLauncherStrategy(PixelCLauncherStrategy.class);
registerLauncherStrategy(LeanbackLauncherStrategy.class);
registerLauncherStrategy(WearLauncherStrategy.class);
+ registerLauncherStrategy(TvLauncherStrategy.class);
}
/**
@@ -86,16 +87,22 @@
* {@link ILauncherStrategy} maybe registered via
* {@link LauncherStrategyFactory#registerLauncherStrategy(Class)} by identifying the
* launcher package name supported
+ * @throw RuntimeException if no valid launcher strategy is found
* @return
*/
public ILauncherStrategy getLauncherStrategy() {
String launcherPkg = mUiDevice.getLauncherPackageName();
- return mInstanceMap.get(launcherPkg);
+ if (mInstanceMap.containsKey(launcherPkg)) {
+ return mInstanceMap.get(launcherPkg);
+ } else {
+ throw new RuntimeException(String.format(
+ "Could not find a launcher strategy for package, %s", launcherPkg));
+ }
}
/**
- * Retrieves a {@link ILeanbackLauncherStrategy} that supports the current default leanback
- * launcher
+ * Retrieves a {@link ILeanbackLauncherStrategy} that supports the current default launcher
+ * for TV. Either Leanback Launcher or new TV Launcher
* @return
*/
public ILeanbackLauncherStrategy getLeanbackLauncherStrategy() {
@@ -103,6 +110,6 @@
if (launcherStrategy instanceof ILeanbackLauncherStrategy) {
return (ILeanbackLauncherStrategy)launcherStrategy;
}
- throw new RuntimeException("This LauncherStrategy is not for leanback launcher.");
+ throw new RuntimeException("This LauncherStrategy is suitable for TV.");
}
}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/LeanbackLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/LeanbackLauncherStrategy.java
index cac78a1..2ecc0c0 100644
--- a/libraries/launcher-helper/src/android/support/test/launcherhelper/LeanbackLauncherStrategy.java
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/LeanbackLauncherStrategy.java
@@ -16,6 +16,9 @@
package android.support.test.launcherhelper;
+import android.app.Instrumentation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.graphics.Point;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -40,10 +43,11 @@
private static final int MAX_SCROLL_ATTEMPTS = 20;
private static final int APP_LAUNCH_TIMEOUT = 10000;
private static final int SHORT_WAIT_TIME = 5000; // 5 sec
- private static final int NOTIFICATION_WAIT_TIME = 30000;
+ private static final int NOTIFICATION_WAIT_TIME = 60000;
protected UiDevice mDevice;
protected DPadUtil mDPadUtil;
+ private Instrumentation mInstrumentation;
/**
@@ -195,7 +199,15 @@
@Override
public long launch(String appName, String packageName) {
BySelector app = By.res(getSupportedLauncherPackage(), "app_banner").desc(appName);
- return launchApp(this, app, packageName);
+ return launchApp(this, app, packageName, isGame(packageName));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setInstrumentation(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
}
/**
@@ -355,12 +367,12 @@
}
protected long launchApp(ILauncherStrategy launcherStrategy, BySelector app,
- String packageName) {
- return launchApp(launcherStrategy, app, packageName, MAX_SCROLL_ATTEMPTS);
+ String packageName, boolean isGame) {
+ return launchApp(launcherStrategy, app, packageName, isGame, MAX_SCROLL_ATTEMPTS);
}
protected long launchApp(ILauncherStrategy launcherStrategy, BySelector app,
- String packageName, int maxScrollAttempts) {
+ String packageName, boolean isGame, int maxScrollAttempts) {
unlockDeviceIfAsleep();
if (isAppOpen(packageName)) {
@@ -370,14 +382,26 @@
// Go to the home page
launcherStrategy.open();
- // attempt to find the app icon if it's not already on the screen
- UiObject2 container = launcherStrategy.openAllApps(false);
+
+ // attempt to find the app/game icon if it's not already on the screen
+ UiObject2 container;
+ if (isGame) {
+ container = selectGamesRow();
+ } else {
+ container = launcherStrategy.openAllApps(false);
+ }
UiObject2 appIcon = container.findObject(app);
int attempts = 0;
while (attempts++ < maxScrollAttempts) {
+ UiObject2 focused = container.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ if (focused == null) {
+ throw new IllegalStateException(
+ "The App/Game row may have lost focus while activity is in transition");
+ }
+
// Compare the focused icon and the app icon to search for.
- UiObject2 focusedIcon = container.findObject(By.focused(true))
- .findObject(By.res(getSupportedLauncherPackage(), "app_banner"));
+ UiObject2 focusedIcon = focused.findObject(
+ By.res(getSupportedLauncherPackage(), "app_banner"));
if (appIcon == null) {
appIcon = findApp(container, focusedIcon, app);
@@ -541,21 +565,54 @@
}
protected UiObject2 findNotificationCard(BySelector selector) {
- // Move to the first notification, Search to the right
+ // Move to the first notification row, start searching to the right, then to the left
mDPadUtil.pressHome();
+ UiObject2 card;
+ if ((card = findNotificationCard(selector, Direction.RIGHT)) != null) {
+ return card;
+ }
+ if ((card = findNotificationCard(selector, Direction.LEFT)) != null) {
+ return card;
+ }
+ return null;
+ }
- // Find if a focused card matches a given selector
- UiObject2 currentFocus = mDevice.findObject(getNotificationRowSelector())
- .findObject(By.res(getSupportedLauncherPackage(), "card").focused(true));
+ /**
+ * Find the card in the Notification row that matches BySelector in a given direction.
+ * If a card is already selected, it returns regardless of the direction parameter.
+ * @param selector
+ * @param direction
+ * @return
+ */
+ protected UiObject2 findNotificationCard(BySelector selector, Direction direction) {
+ if (direction != Direction.RIGHT && direction != Direction.LEFT) {
+ throw new IllegalArgumentException("Required to go either left or right to find a card"
+ + "in the Notification row");
+ }
+
+ // Find the Notification row
+ UiObject2 notification = mDevice.findObject(getNotificationRowSelector());
+ if (notification == null) {
+ mDPadUtil.pressHome();
+ notification = mDevice.wait(Until.findObject(getNotificationRowSelector()),
+ SHORT_WAIT_TIME);
+ if (notification == null) {
+ throw new IllegalStateException("The Notification row is not found");
+ }
+ }
+
+ // Find a focused card in the Notification row that matches a given selector
+ UiObject2 currentFocus = notification.findObject(
+ By.res(getSupportedLauncherPackage(), "card").focused(true));
UiObject2 previousFocus = null;
while (!currentFocus.equals(previousFocus)) {
if (currentFocus.hasObject(selector)) {
return currentFocus; // Found
}
- mDPadUtil.pressDPadRight();
+ mDPadUtil.pressDPad(direction);
previousFocus = currentFocus;
- currentFocus = mDevice.findObject(getNotificationRowSelector())
- .findObject(By.res(getSupportedLauncherPackage(), "card").focused(true));
+ currentFocus = notification.findObject(
+ By.res(getSupportedLauncherPackage(), "card").focused(true));
}
Log.d(LOG_TAG, "Failed to find the Notification card until it reaches the end.");
return null;
@@ -604,7 +661,8 @@
throw new IllegalArgumentException("Required to go either up or down to find rows");
}
- UiObject2 currentFocused = mDevice.findObject(By.focused(true));
+ UiObject2 currentFocused = mDevice.wait(Until.findObject(By.focused(true)),
+ SHORT_WAIT_TIME);
UiObject2 prevFocused = null;
while (!currentFocused.equals(prevFocused)) {
UiObject2 rowObject = mDevice.findObject(row);
@@ -614,7 +672,7 @@
mDPadUtil.pressDPad(direction);
prevFocused = currentFocused;
- currentFocused = mDevice.findObject(By.focused(true));
+ currentFocused = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
}
Log.d(LOG_TAG, "Failed to find the row until it reaches the end.");
return null;
@@ -667,4 +725,25 @@
Log.d(LOG_TAG, "Failed to find the setting in Settings row.");
return null;
}
+
+ private boolean isGame(String packageName) {
+ boolean isGame = false;
+ if (mInstrumentation != null) {
+ try {
+ ApplicationInfo appInfo =
+ mInstrumentation.getTargetContext().getPackageManager().getApplicationInfo(
+ packageName, 0);
+ // TV game apps should use the "isGame" tag added since the L release. They are
+ // listed on the Games row on the Leanback Launcher.
+ isGame = ((appInfo.flags & ApplicationInfo.FLAG_IS_GAME) != 0) ||
+ (appInfo.metaData != null && appInfo.metaData.getBoolean("isGame", false));
+ Log.i(LOG_TAG, String.format("The package %s isGame: %b", packageName, isGame));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG,
+ String.format("No package found: %s, error:%s", packageName, e.toString()));
+ return false;
+ }
+ }
+ return isGame;
+ }
}
diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java
new file mode 100644
index 0000000..04e3faa
--- /dev/null
+++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/TvLauncherStrategy.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.test.launcherhelper;
+
+import android.app.Instrumentation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.utils.DPadUtil;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import org.junit.Assert;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+public class TvLauncherStrategy implements ILeanbackLauncherStrategy {
+
+ private static final String LOG_TAG = TvLauncherStrategy.class.getSimpleName();
+ private static final String PACKAGE_LAUNCHER = "com.google.android.tvlauncher";
+
+ private static final int APP_LAUNCH_TIMEOUT = 10000;
+ private static final int SHORT_WAIT_TIME = 5000; // 5 sec
+ private static final int UI_TRANSITION_WAIT_TIME = 1000;
+
+ // Note that the selector specifies criteria for matching an UI element from/to a focused item
+ private static final BySelector SELECTOR_TOP_ROW = By.res(PACKAGE_LAUNCHER, "top_row");
+ private static final BySelector SELECTOR_APPS_ROW = By.res(PACKAGE_LAUNCHER, "apps_row");
+ private static final BySelector SELECTOR_ALL_APPS_VIEW =
+ By.res(PACKAGE_LAUNCHER, "row_list_view");
+ private static final BySelector SELECTOR_ALL_APPS_LOGO =
+ By.res(PACKAGE_LAUNCHER, "channel_logo").focused(true).descContains("Apps");
+ private static final BySelector SELECTOR_CONFIG_CHANNELS_ROW =
+ By.res(PACKAGE_LAUNCHER, "configure_channels_row");
+
+ protected UiDevice mDevice;
+ protected DPadUtil mDPadUtil;
+ private Instrumentation mInstrumentation;
+
+ /**
+ * A TvLauncherUnsupportedOperationException is an exception specific to TV Launcher. This will
+ * be thrown when the feature/method is not available on the TV Launcher.
+ */
+ class TvLauncherUnsupportedOperationException extends UnsupportedOperationException {
+ TvLauncherUnsupportedOperationException() {
+ super();
+ }
+ TvLauncherUnsupportedOperationException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSupportedLauncherPackage() {
+ return PACKAGE_LAUNCHER;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ // TODO(hyungtaekim): Move this common implementation to abstract class for TV launchers
+ @Override
+ public void setUiDevice(UiDevice uiDevice) {
+ mDevice = uiDevice;
+ mDPadUtil = new DPadUtil(mDevice);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void open() {
+ // if we see main list view, assume at home screen already
+ if (!mDevice.hasObject(getWorkspaceSelector())) {
+ mDPadUtil.pressHome();
+ // ensure launcher is shown
+ if (!mDevice.wait(Until.hasObject(getWorkspaceSelector()), SHORT_WAIT_TIME)) {
+ // HACK: dump hierarchy to logcat
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ mDevice.dumpWindowHierarchy(baos);
+ baos.flush();
+ baos.close();
+ String[] lines = baos.toString().split("\\r?\\n");
+ for (String line : lines) {
+ Log.d(LOG_TAG, line.trim());
+ }
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "error dumping XML to logcat", ioe);
+ }
+ throw new RuntimeException("Failed to open TV launcher");
+ }
+ mDevice.waitForIdle();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * There are two different ways to open All Apps view. If longpress is true, it will long press
+ * the HOME key to open it. Otherwise it will navigate to the "APPS" logo on the Apps row.
+ */
+ @Override
+ public UiObject2 openAllApps(boolean longpress) {
+ if (longpress) {
+ mDPadUtil.longPressKeyCode(KeyEvent.KEYCODE_HOME);
+ } else {
+ Assert.assertNotNull("Could not find all apps logo", selectAppsLogo());
+ mDPadUtil.pressDPadCenter();
+ }
+ return mDevice.wait(Until.findObject(getAllAppsSelector()), SHORT_WAIT_TIME);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector getWorkspaceSelector() {
+ return By.res(getSupportedLauncherPackage(), "home_view_container");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector getSearchRowSelector() {
+ return SELECTOR_TOP_ROW;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector getAppsRowSelector() {
+ return SELECTOR_APPS_ROW;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector getGamesRowSelector() {
+ // Note that the apps and games are now in the same row on new TV Launcher.
+ return getAppsRowSelector();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Direction getAllAppsScrollDirection() {
+ return Direction.DOWN;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector getAllAppsSelector() {
+ return SELECTOR_ALL_APPS_VIEW;
+ }
+
+ public BySelector getAllAppsLogoSelector() {
+ return SELECTOR_ALL_APPS_LOGO;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long launch(String appName, String packageName) {
+ BySelector appSelector = By.res(getSupportedLauncherPackage(),
+ "app_banner_image").desc(appName);
+ return launchApp(this, appSelector, packageName, isGame(packageName));
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This function must be called before any UI test runs on TV.
+ * </p>
+ */
+ @Override
+ public void setInstrumentation(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public UiObject2 selectSearchRow() {
+ // The Search orb is now on top row on TV Launcher
+ return selectTopRow();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public UiObject2 selectAppsRow() {
+ return selectBidirect(getAppsRowSelector().hasDescendant(By.focused(true)),
+ Direction.DOWN);
+ }
+
+ public UiObject2 selectChannelsRow(String channelName) {
+ // TODO:
+ return null;
+ }
+
+ public UiObject2 selectAppsLogo() {
+ Assert.assertNotNull("Could not find all apps row", selectAppsRow());
+ return selectBidirect(getAllAppsLogoSelector().hasDescendant(By.focused(true)),
+ Direction.LEFT);
+ }
+
+ /**
+ * Returns a {@link UiObject2} describing the Top row on TV Launcher
+ * @return
+ */
+ public UiObject2 selectTopRow() {
+ return select(getSearchRowSelector().hasDescendant(By.focused(true)),
+ Direction.UP, UI_TRANSITION_WAIT_TIME);
+ }
+
+ /**
+ * Returns a {@link UiObject2} describing the Config Channels row on TV Launcher
+ * @return
+ */
+ public UiObject2 selectConfigChannelsRow() {
+ return select(SELECTOR_CONFIG_CHANNELS_ROW.hasDescendant(By.focused(true)),
+ Direction.DOWN, UI_TRANSITION_WAIT_TIME);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public UiObject2 selectGamesRow() {
+ return selectAppsRow();
+ }
+
+ /**
+ * Select the given app in All Apps activity.
+ * When the All Apps opens, the focus is always at the top right.
+ * Search from left to right, and down to the next row, from right to left, and
+ * down to the next row like a zigzag pattern until the app is found.
+ */
+ protected UiObject2 selectAppInAllApps(BySelector appSelector, String packageName) {
+ openAllApps(true);
+
+ // Assume that the focus always starts at the top left of the Apps view.
+ final int maxScrollAttempts = 20;
+ final int margin = 10;
+ int attempts = 0;
+ UiObject2 focused = null;
+ UiObject2 expected = null;
+ while (attempts++ < maxScrollAttempts) {
+ focused = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ expected = mDevice.wait(Until.findObject(appSelector), SHORT_WAIT_TIME);
+ if (focused.getVisibleCenter().equals(expected.getVisibleCenter())) {
+ // The app icon is on the screen, and selected.
+ Log.i(LOG_TAG, String.format("The app %s is selected", packageName));
+ break;
+ } else {
+ // The app icon is on the screen, but not selected yet
+ // Move one step closer to the app icon
+ Point currentPosition = focused.getVisibleCenter();
+ Point targetPosition = expected.getVisibleCenter();
+ int dx = targetPosition.x - currentPosition.x;
+ int dy = targetPosition.y - currentPosition.y;
+ if (dy > margin) {
+ mDPadUtil.pressDPadDown();
+ continue;
+ }
+ if (dx > margin) {
+ mDPadUtil.pressDPadRight();
+ continue;
+ }
+ if (dy < -margin) {
+ mDPadUtil.pressDPadUp();
+ continue;
+ }
+ if (dx < -margin) {
+ mDPadUtil.pressDPadLeft();
+ continue;
+ }
+ throw new RuntimeException(
+ "Failed to navigate to the app icon on screen: " + packageName);
+ }
+ }
+ // The app icon is already found and focused. Then wait for it to open.
+ BySelector appMain = By.pkg(packageName).depth(0);
+ mDPadUtil.pressDPadCenter();
+ if (!mDevice.wait(Until.hasObject(appMain), SHORT_WAIT_TIME)) {
+ Log.w(LOG_TAG, "no new window detected after app launch attempt.");
+ return null;
+ }
+ return expected;
+ }
+
+ protected long launchApp(ILauncherStrategy launcherStrategy, BySelector appSelector,
+ String packageName, boolean isGame) {
+ unlockDeviceIfAsleep();
+
+ if (isAppOpen(packageName)) {
+ // Application is already open
+ return 0;
+ }
+
+ // Go to the home page, and select the Apps row
+ launcherStrategy.open();
+ selectAppsRow();
+
+ // Search for the app in the Apps row first.
+ // If not exists, open the 'All Apps' and search for the app there
+ UiObject2 app = null;
+ if (mDevice.hasObject(appSelector)) {
+ app = selectBidirect(By.focused(true).hasDescendant(appSelector), Direction.RIGHT);
+ } else {
+ app = selectAppInAllApps(appSelector, packageName);
+ }
+ if (app == null) {
+ throw new RuntimeException(
+ "Failed to navigate to the app icon on screen: " + packageName);
+ }
+
+ // The app icon is already found and focused. Then wait for it to open.
+ long ready = SystemClock.uptimeMillis();
+ mDPadUtil.pressDPadCenter();
+ if (!mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT)) {
+ Log.w(LOG_TAG, "no new window detected after app launch attempt.");
+ return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP;
+ }
+ mDevice.waitForIdle();
+ if (packageName != null) {
+ Log.w(LOG_TAG, String.format(
+ "No UI element with package name %s detected.", packageName));
+ boolean success = mDevice.wait(Until.hasObject(
+ By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT);
+ if (success) {
+ return ready;
+ } else {
+ return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP;
+ }
+ } else {
+ return ready;
+ }
+ }
+
+ protected boolean isTopRowSelected() {
+ UiObject2 row = mDevice.findObject(getSearchRowSelector());
+ if (row == null) {
+ return false;
+ }
+ return row.hasObject(By.focused(true));
+ }
+
+ protected boolean isAppsRowSelected() {
+ UiObject2 row = mDevice.findObject(getAppsRowSelector());
+ if (row == null) {
+ return false;
+ }
+ return row.hasObject(By.focused(true));
+ }
+
+ protected boolean isGamesRowSelected() {
+ return isAppsRowSelected();
+ }
+
+ // TODO(hyungtaekim): Move in the common helper
+ protected boolean isAppOpen(String appPackage) {
+ return mDevice.hasObject(By.pkg(appPackage).depth(0));
+ }
+
+ // TODO(hyungtaekim): Move in the common helper
+ protected void unlockDeviceIfAsleep() {
+ // Turn screen on if necessary
+ try {
+ if (!mDevice.isScreenOn()) {
+ mDevice.wakeUp();
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed to unlock the screen-off device.", e);
+ }
+ }
+
+ private boolean isGame(String packageName) {
+ boolean isGame = false;
+ if (mInstrumentation != null) {
+ try {
+ ApplicationInfo appInfo =
+ mInstrumentation.getTargetContext().getPackageManager().getApplicationInfo(
+ packageName, 0);
+ // TV game apps should use the "isGame" tag added since the L release. They are
+ // listed on the Games row on the TV Launcher.
+ isGame = (appInfo.metaData != null && appInfo.metaData.getBoolean("isGame", false))
+ || ((appInfo.flags & ApplicationInfo.FLAG_IS_GAME) != 0);
+ Log.i(LOG_TAG, String.format("The package %s isGame: %b", packageName, isGame));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG,
+ String.format("No package found: %s, error:%s", packageName, e.toString()));
+ return false;
+ }
+ }
+ return isGame;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void search(String query) {
+ // TODO: Implement this method when the feature is available
+ throw new UnsupportedOperationException("search is not yet implemented");
+ }
+
+ public void selectRestrictedProfile() {
+ // TODO: Implement this method when the feature is available
+ throw new UnsupportedOperationException(
+ "The Restricted Profile is not yet available on TV Launcher.");
+ }
+
+
+ // Convenient methods for UI actions
+
+ /**
+ * Select an UI element with given {@link BySelector}. This action keeps moving a focus
+ * in a given {@link Direction} until it finds a matched element.
+ * @param selector the search criteria to match an element
+ * @param direction the direction to find
+ * @param timeoutMs timeout in milliseconds to select
+ * @return a UiObject2 which represents the matched element
+ */
+ public UiObject2 select(BySelector selector, Direction direction, long timeoutMs) {
+ UiObject2 focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ while (!mDevice.wait(Until.hasObject(selector), timeoutMs)) {
+ Log.d(LOG_TAG, String.format("select: moving a focus from %s to %s", focus, direction));
+ UiObject2 focused = focus;
+ mDPadUtil.pressDPad(direction);
+ focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ // Hack: A focus might be lost in some UI. Take one more step forward.
+ if (focus == null) {
+ mDPadUtil.pressDPad(direction);
+ focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_WAIT_TIME);
+ }
+ // Check if it reaches to an end where it no longer moves a focus to next element
+ if (focused.equals(focus)) {
+ Log.d(LOG_TAG, "select: not found until it reaches to an end.");
+ return null;
+ }
+ }
+ Log.i(LOG_TAG, String.format("select: %s is selected", focus));
+ return focus;
+ }
+
+ /**
+ * Select an element with a given {@link BySelector} in both given direction and reverse.
+ */
+ public UiObject2 selectBidirect(BySelector selector, Direction direction) {
+ Log.d(LOG_TAG, String.format("selectBidirect [direction]%s", direction));
+ UiObject2 object = select(selector, direction, UI_TRANSITION_WAIT_TIME);
+ if (object == null) {
+ object = select(selector, Direction.reverse(direction), UI_TRANSITION_WAIT_TIME);
+ }
+ return object;
+ }
+
+
+ // Unsupported methods
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getNotificationRowSelector() {
+ throw new TvLauncherUnsupportedOperationException("No Notification row");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getSettingsRowSelector() {
+ throw new TvLauncherUnsupportedOperationException("No Settings row");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getAppWidgetSelector() {
+ throw new TvLauncherUnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getNowPlayingCardSelector() {
+ throw new TvLauncherUnsupportedOperationException("No Now Playing Card");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public UiObject2 selectNotificationRow() {
+ throw new TvLauncherUnsupportedOperationException("No Notification row");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public UiObject2 selectSettingsRow() {
+ throw new TvLauncherUnsupportedOperationException("No Settings row");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public boolean hasAppWidgetSelector() {
+ throw new TvLauncherUnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public boolean hasNowPlayingCard() {
+ throw new TvLauncherUnsupportedOperationException("No Now Playing Card");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getAllAppsButtonSelector() {
+ throw new TvLauncherUnsupportedOperationException("No All Apps button");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public UiObject2 openAllWidgets(boolean reset) {
+ throw new TvLauncherUnsupportedOperationException("No All Widgets");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getAllWidgetsSelector() {
+ throw new TvLauncherUnsupportedOperationException("No All Widgets");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public Direction getAllWidgetsScrollDirection() {
+ throw new TvLauncherUnsupportedOperationException("No All Widgets");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public BySelector getHotSeatSelector() {
+ throw new TvLauncherUnsupportedOperationException("No Hot seat");
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public Direction getWorkspaceScrollDirection() {
+ throw new TvLauncherUnsupportedOperationException("No Workspace");
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/metrics-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/metrics-helper/Android.mk
index 9e797fb..3246485 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/metrics-helper/Android.mk
@@ -13,16 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+
+LOCAL_MODULE := metrics-helper-lib
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := services.core legacy-android-test junit
include $(BUILD_STATIC_JAVA_LIBRARY)
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/libraries/metrics-helper/src/android/support/test/metricshelper/MetricsAsserts.java b/libraries/metrics-helper/src/android/support/test/metricshelper/MetricsAsserts.java
new file mode 100644
index 0000000..613f2e0
--- /dev/null
+++ b/libraries/metrics-helper/src/android/support/test/metricshelper/MetricsAsserts.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.metricshelper;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Useful test utilities for metrics tests.
+ */
+public class MetricsAsserts {
+
+ /**
+ * Assert unless there is a log with the matching category and with ACTION type.
+ */
+ public static void assertHasActionLog(String message, MetricsReader reader, int view) {
+ reader.read(0);
+ assertHasActionLog(message, new ReaderQueue(reader), view);
+ }
+ /**
+ * Assert unless there is a log with the matching category and with ACTION type.
+ */
+ public static void assertHasActionLog(String message, Queue<LogMaker> queue, int view) {
+ Queue<LogMaker> logs = findMatchingLogs(queue,
+ new LogMaker(view)
+ .setType(MetricsEvent.TYPE_ACTION));
+ assertTrue(message, !logs.isEmpty());
+ }
+
+ /**
+ * Assert unless there is a log with the matching category and with visibility type.
+ */
+ public static void assertHasVisibilityLog(String message, MetricsReader reader,
+ int view, boolean visible) {
+ reader.read(0);
+ assertHasVisibilityLog(message, new ReaderQueue(reader), view, visible);
+ }
+
+ /**
+ * Assert unless there is a log with the matching category and with visibility type.
+ */
+ public static void assertHasVisibilityLog(String message, Queue<LogMaker> queue,
+ int view, boolean visible) {
+ Queue<LogMaker> logs = findMatchingLogs(queue,
+ new LogMaker(view)
+ .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE));
+ assertTrue(message, !logs.isEmpty());
+ }
+
+ /**
+ * @returns logs that have at least all the matching fields in the template.
+ */
+ public static Queue<LogMaker> findMatchingLogs(MetricsReader reader, LogMaker template) {
+ reader.read(0);
+ return findMatchingLogs(new ReaderQueue(reader), template);
+ }
+
+ /**
+ * @returns logs that have at least all the matching fields in the template.
+ */
+ public static Queue<LogMaker> findMatchingLogs(Queue<LogMaker> queue, LogMaker template) {
+ LinkedList<LogMaker> logs = new LinkedList<>();
+ if (template == null) {
+ return logs;
+ }
+ while (!queue.isEmpty()) {
+ LogMaker b = queue.poll();
+ if (template.isSubsetOf(b)) {
+ logs.push(b);
+ }
+ }
+ return logs;
+ }
+
+ /**
+ * Assert unless there is at least one log that matches the template.
+ */
+ public static void assertHasLog(String message, MetricsReader reader, LogMaker expected) {
+ reader.read(0);
+ assertHasLog(message, new ReaderQueue(reader), expected);
+ }
+
+ /**
+ * Assert unless there is at least one log that matches the template.
+ */
+ public static void assertHasLog(String message, Queue<LogMaker> queue, LogMaker expected) {
+ assertTrue(message, !findMatchingLogs(queue, expected).isEmpty());
+ }
+
+ private static class ReaderQueue implements Queue<LogMaker> {
+
+ private final MetricsReader mMetricsReader;
+
+ ReaderQueue(MetricsReader metricsReader) {
+ mMetricsReader = metricsReader;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return !mMetricsReader.hasNext();
+ }
+
+ @Override
+ public LogMaker poll() {
+ return mMetricsReader.next();
+ }
+
+ @Override
+ public boolean add(LogMaker logMaker) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends LogMaker> collection) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean contains(Object object) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> collection) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public Iterator<LogMaker> iterator() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public int size() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public Object[] toArray() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public <T> T[] toArray(T[] array) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public boolean offer(LogMaker logMaker) {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public LogMaker remove() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public LogMaker element() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+
+ @Override
+ public LogMaker peek() {
+ throw new UnsupportedOperationException("unimplemented fake method");
+ }
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/metrics-helper/tests/Android.mk
similarity index 64%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/metrics-helper/tests/Android.mk
index 9e797fb..00567d6 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/metrics-helper/tests/Android.mk
@@ -1,5 +1,4 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,17 +11,23 @@
# 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)
+LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PACKAGE_NAME := MetricsHelperTests
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ platform-test-annotations \
+ metrics-helper-lib \
+ framework-protos
-######################################
+include $(BUILD_PACKAGE)
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/metrics-helper/tests/AndroidManifest.xml b/libraries/metrics-helper/tests/AndroidManifest.xml
new file mode 100644
index 0000000..7b1bf5d
--- /dev/null
+++ b/libraries/metrics-helper/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.support.test.metricshelper">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.support.test.metricshelper"
+ android:label="Metrics Helper Tests" />
+</manifest>
diff --git a/libraries/metrics-helper/tests/src/android/support/test/metricshelper/MetricsAssertsTest.java b/libraries/metrics-helper/tests/src/android/support/test/metricshelper/MetricsAssertsTest.java
new file mode 100644
index 0000000..7dc5b66
--- /dev/null
+++ b/libraries/metrics-helper/tests/src/android/support/test/metricshelper/MetricsAssertsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.test.metricshelper;
+
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import android.support.test.runner.AndroidJUnit4;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.fail;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MetricsAssertsTest {
+ @Mock MetricsReader mReader;
+
+ private LogMaker a;
+ private LogMaker b;
+ private LogMaker c;
+ private LogMaker d;
+
+ private int mActionView = MetricsEvent.ACTION_WIFI_ON;
+ private int mOpenView = MetricsEvent.MAIN_SETTINGS;
+ private int mCloseView = MetricsEvent.NOTIFICATION_PANEL;
+ private int mSubtype = 4;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ a = new LogMaker(MetricsEvent.SCREEN)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .setTimestamp(1000);
+ b = new LogMaker(mOpenView)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .setTimestamp(2000);
+ c = new LogMaker(mActionView)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(mSubtype)
+ .setTimestamp(3000);
+ d = new LogMaker(mCloseView)
+ .setType(MetricsEvent.TYPE_CLOSE)
+ .setTimestamp(4000);
+
+ when(mReader.hasNext())
+ .thenReturn(true)
+ .thenReturn(true)
+ .thenReturn(true)
+ .thenReturn(true)
+ .thenReturn(false);
+ when(mReader.next())
+ .thenReturn(a)
+ .thenReturn(b)
+ .thenReturn(c)
+ .thenReturn(d)
+ .thenReturn(null);
+ }
+
+ @Test
+ public void testHasActionLogTrue() {
+ MetricsAsserts.assertHasActionLog("foo", mReader, mActionView);
+ }
+
+ @Test
+ public void testHasActionLogFalse() {
+ final String message = "foo";
+ try {
+ MetricsAsserts.assertHasActionLog(message, mReader, mOpenView);
+ } catch (AssertionError e) {
+ assertEquals(message, e.getMessage());
+ return; // success!
+ }
+ }
+
+ @Test
+ public void testHasVisibileLogTrue() {
+ MetricsAsserts.assertHasVisibilityLog("foo", mReader, mOpenView, true);
+ }
+
+ @Test
+ public void testHasVisibleLogFalse() {
+ final String message = "foo";
+ try {
+ MetricsAsserts.assertHasVisibilityLog(message, mReader, mActionView, true);
+ } catch (AssertionError e) {
+ assertEquals(message, e.getMessage());
+ return; // success!
+ }
+ }
+
+ @Test
+ public void testHasHiddenLogTrue() {
+ MetricsAsserts.assertHasVisibilityLog("foo", mReader, mCloseView, false);
+ }
+
+ @Test
+ public void testHasHiddenLogFalse() {
+ final String message = "foo";
+ try {
+ MetricsAsserts.assertHasVisibilityLog(message, mReader, mOpenView, false);
+ } catch (AssertionError e) {
+ assertEquals(message, e.getMessage());
+ return; // success!
+ }
+ }
+
+ @Test
+ public void testHasTemplateLogCategoryOnly() {
+ MetricsAsserts.assertHasLog("didn't find existing log", mReader,
+ new LogMaker(mActionView));
+ }
+
+ @Test
+ public void testHasTemplateLogCategoryAndType() {
+ MetricsAsserts.assertHasLog("didn't find existing log", mReader,
+ new LogMaker(mActionView)
+ .setType(MetricsEvent.TYPE_ACTION));
+ }
+
+ @Test
+ public void testHasTemplateLogCategoryTypeAndSubtype() {
+ MetricsAsserts.assertHasLog("didn't find existing log", mReader,
+ new LogMaker(mActionView)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(mSubtype));
+ }
+
+ @Test
+ public void testDoesNotHaveTemplateLog() {
+ final String message = "foo";
+ try {
+ MetricsAsserts.assertHasLog(message, mReader,
+ new LogMaker(mActionView)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(mSubtype));
+ } catch (AssertionError e) {
+ assertEquals(message, e.getMessage());
+ return; // success!
+ }
+
+ }
+}
diff --git a/libraries/power-helper/Android.mk b/libraries/power-helper/Android.mk
index 9ea8816..65ae0ba 100644
--- a/libraries/power-helper/Android.mk
+++ b/libraries/power-helper/Android.mk
@@ -25,5 +25,6 @@
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := android.test.runner ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libraries/power-helper/sample/Android.mk b/libraries/power-helper/sample/Android.mk
index 174ce17..b73f734 100644
--- a/libraries/power-helper/sample/Android.mk
+++ b/libraries/power-helper/sample/Android.mk
@@ -5,6 +5,6 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := PowerTestHelper-src ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := PowerTestHelper-src ub-uiautomator junit
include $(BUILD_PACKAGE)
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/Android.mk
similarity index 70%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/Android.mk
index 9e797fb..d2de885 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/Android.mk
@@ -16,10 +16,20 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := system-helpers
+LOCAL_STATIC_JAVA_LIBRARIES := account-helper \
+ activity-helper \
+ commands-helper \
+ connectivity-helper \
+ device-helper \
+ permission-helper \
+ settings-helper \
+ sysui-helper \
+ user-helper \
+ package-helper \
+ accessibility-helper
+LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/accessibility-helper/Android.mk
similarity index 71%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/accessibility-helper/Android.mk
index 9e797fb..185fe2a 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/accessibility-helper/Android.mk
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,13 +16,12 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := accessibility-helper
+LOCAL_JAVA_LIBRARIES := android-support-test \
+ ub-uiautomator \
+ settings-helper \
+ package-helper \
+ activity-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/libraries/system-helpers/accessibility-helper/src/android/system/helpers/AccessibilityHelper.java b/libraries/system-helpers/accessibility-helper/src/android/system/helpers/AccessibilityHelper.java
new file mode 100644
index 0000000..c14d2e4
--- /dev/null
+++ b/libraries/system-helpers/accessibility-helper/src/android/system/helpers/AccessibilityHelper.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.helpers;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.provider.Settings;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+/**
+ * Implement common helper functions for accessibility.
+ */
+public class AccessibilityHelper {
+ public static final String SETTINGS_PACKAGE = "com.android.settings";
+ public static final String BUTTON = "android.widget.Button";
+ public static final String CHECK_BOX = "android.widget.CheckBox";
+ public static final String IMAGE_BUTTON = "android.widget.ImageButton";
+ public static final String TEXT_VIEW = "android.widget.TextView";
+ public static final String SWITCH = "android.widget.Switch";
+ public static final String CHECKED_TEXT_VIEW = "android.widget.CheckedTextView";
+ public static final String RADIO_BUTTON = "android.widget.RadioButton";
+ public static final String SEEK_BAR = "android.widget.SeekBar";
+ public static final String SPINNER = "android.widget.Spinner";
+ public static final int SHORT_TIMEOUT = 2000;
+ public static final int LONG_TIMEOUT = 5000;
+ public static AccessibilityHelper sInstance = null;
+ private Context mContext = null;
+ private Instrumentation mInstrumentation = null;
+ private UiDevice mDevice = null;
+ private SettingsHelper mSettingsHelper = null;
+
+ public enum SwitchStatus {
+ ON,
+ OFF;
+ }
+
+ private AccessibilityHelper(Instrumentation instr) {
+ mInstrumentation = instr;
+ mSettingsHelper = SettingsHelper.getInstance();
+ mDevice = UiDevice.getInstance(instr);
+ mContext = mInstrumentation.getTargetContext();
+ }
+
+ public static AccessibilityHelper getInstance(Instrumentation instr) {
+ if (sInstance == null) {
+ sInstance = new AccessibilityHelper(instr);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Set Talkback "ON"/"OFF".
+ *
+ * @param value "ON"/"OFF"
+ * @throws Exception
+ */
+ public void setTalkBackSetting(SwitchStatus value) throws Exception {
+ launchSpecificAccessibilitySetting("TalkBack");
+ UiObject2 swtBar = mDevice.wait(
+ Until.findObject(By.res(SETTINGS_PACKAGE, "switch_bar")), SHORT_TIMEOUT)
+ .findObject(By.res(SETTINGS_PACKAGE, "switch_widget"));
+ if (swtBar != null && !swtBar.getText().equals(value.toString())) {
+ swtBar.click();
+ UiObject2 confirmBtn = mDevice.wait(
+ Until.findObject(By.res("android:id/button1")), LONG_TIMEOUT);
+ if (confirmBtn != null) {
+ confirmBtn.click();
+ }
+ // First time enable talkback, tutorial open.
+ if (mDevice.wait(Until.hasObject(By.text("TalkBack tutorial")), SHORT_TIMEOUT)) {
+ mDevice.pressBack(); // back to talkback setting page
+ }
+ }
+ mDevice.pressBack();
+ }
+
+ /**
+ * Set high contrast "ON"/"OFF".
+ *
+ * @param value "ON"/"OFF"
+ * @throws Exception
+ */
+ public void setHighContrast(SwitchStatus value) throws Exception {
+ launchSpecificAccessibilitySetting("Accessibility");
+ setSettingSwitchValue("High contrast text", value);
+ }
+
+ /**
+ * Launch specific accessibility setting page.
+ *
+ * @param settingName Specific accessibility setting name
+ * @throws Exception
+ */
+ public void launchSpecificAccessibilitySetting(String settingName) throws Exception {
+ mSettingsHelper.launchSettingsPage(mContext, Settings.ACTION_ACCESSIBILITY_SETTINGS);
+ int maxTry = 3;
+ while (maxTry-- >= 0) {
+ Thread.sleep(SHORT_TIMEOUT);
+ UiObject2 actionBar = mDevice.wait(Until.findObject(
+ By.res(SETTINGS_PACKAGE, "action_bar").enabled(true)), SHORT_TIMEOUT);
+ if (actionBar == null) {
+ mSettingsHelper.launchSettingsPage(mContext,
+ Settings.ACTION_ACCESSIBILITY_SETTINGS);
+ } else {
+ String actionBarText = actionBar.findObject(By.clazz(TEXT_VIEW)).getText();
+ if (actionBarText.equals(settingName)) {
+ break;
+ } else if (actionBarText.equals("Accessibility")) {
+ getSettingFromList(settingName).click();
+ } else {
+ mDevice.wait(Until.findObject(By.res(SETTINGS_PACKAGE, "action_bar")
+ .enabled(true)), SHORT_TIMEOUT)
+ .findObject(By.clazz(IMAGE_BUTTON)).click();
+ }
+ }
+ }
+ }
+
+ /**
+ * Set switch "ON"/"OFF".
+ *
+ * @param settingTag setting name
+ * @param value "ON"/"OFF"
+ * @return true/false
+ * @throws UiObjectNotFoundException
+ * @throws InterruptedException
+ */
+ private boolean setSettingSwitchValue(String settingTag, SwitchStatus value)
+ throws UiObjectNotFoundException, InterruptedException {
+ UiObject2 cellSwitch = getSettingFromList(settingTag)
+ .getParent().getParent().findObject(By.clazz(SWITCH));
+ if (cellSwitch != null) {
+ if (!cellSwitch.getText().equals(value.toString())) {
+ cellSwitch.click();
+ UiObject2 okBtn = mDevice.wait(Until.findObject(
+ By.res("android:id/button1")), LONG_TIMEOUT);
+ if (okBtn != null) {
+ okBtn.click();
+ }
+ }
+ return cellSwitch.getText().equals(value.toString());
+ }
+ return false;
+ }
+
+ /**
+ * Get setting name text object from list.
+ *
+ * @param settingName setting name
+ * @return UiObject2
+ * @throws UiObjectNotFoundException
+ */
+ private UiObject2 getSettingFromList(String settingName)
+ throws UiObjectNotFoundException {
+ UiScrollable listScrollable = new UiScrollable(
+ new UiSelector().resourceId(SETTINGS_PACKAGE+":id/list"));
+ if (listScrollable != null) {
+ listScrollable.scrollToBeginning(100);
+ listScrollable.scrollIntoView(
+ new UiSelector().resourceId("android:id/title").text(settingName));
+ return mDevice.findObject(By.res("android:id/title").text(settingName));
+ } else {
+ throw new UiObjectNotFoundException("Fail to get scrollable list %s.");
+ }
+ }
+}
diff --git a/libraries/system-helpers/accessibility-helper/src/android/system/helpers/AccessibilityScannerHelper.java b/libraries/system-helpers/accessibility-helper/src/android/system/helpers/AccessibilityScannerHelper.java
new file mode 100644
index 0000000..a6da216
--- /dev/null
+++ b/libraries/system-helpers/accessibility-helper/src/android/system/helpers/AccessibilityScannerHelper.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system.helpers;
+
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Implement common helper functions for Accessibility scanner.
+ */
+public class AccessibilityScannerHelper {
+ public static final String ACCESSIBILITY_SCANNER_PACKAGE
+ = "com.google.android.apps.accessibility.auditor";
+ public static final String MAIN_ACTIVITY_CLASS = "%s.ui.MainActivity";
+ public static final String CHECK_BUTTON_RES_ID = "accessibilibutton";
+ private static final int SCANNER_WAIT_TIME = 5000;
+ private static final int SHORT_TIMEOUT = 2000;
+ private static final String LOG_TAG = AccessibilityScannerHelper.class.getSimpleName();
+ private static final String RESULT_TAG = "A11Y_SCANNER_RESULT";
+ public static AccessibilityScannerHelper sInstance = null;
+ private UiDevice mDevice = null;
+ private ActivityHelper mActivityHelper = null;
+ private PackageHelper mPackageHelper = null;
+ private AccessibilityHelper mAccessibilityHelper = null;
+
+ private AccessibilityScannerHelper(Instrumentation instr) {
+ mDevice = UiDevice.getInstance(instr);
+ mActivityHelper = ActivityHelper.getInstance();
+ mPackageHelper = PackageHelper.getInstance(instr);
+ mAccessibilityHelper = AccessibilityHelper.getInstance(instr);
+ }
+
+ public static AccessibilityScannerHelper getInstance(Instrumentation instr) {
+ if (sInstance == null) {
+ sInstance = new AccessibilityScannerHelper(instr);
+ }
+ return sInstance;
+ }
+
+ /**
+ * If accessibility scanner installed.
+ *
+ * @return true/false
+ */
+ public boolean scannerInstalled() {
+ return mPackageHelper.isPackageInstalled(ACCESSIBILITY_SCANNER_PACKAGE);
+ }
+
+ /**
+ * Click scanner check button and parse and log results.
+ *
+ * @param resultPrefix
+ * @throws Exception
+ */
+ public void runScanner(String resultPrefix) throws Exception {
+ int tries = 3; // retries
+ while (tries-- > 0) {
+ try {
+ clickScannerCheck();
+ logScannerResult(resultPrefix);
+ break;
+ } catch (UiObjectNotFoundException e) {
+ continue;
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Click scanner check button and open share app in the share menu.
+ *
+ * @param resultPrefix
+ * @param shareAppTag
+ * @throws Exception
+ */
+ public void runScannerAndOpenShareApp(String resultPrefix, String shareAppTag)
+ throws Exception {
+ runScanner(resultPrefix);
+ UiObject2 shareApp = getShareApp(shareAppTag);
+ if (shareApp != null) {
+ shareApp.click();
+ }
+ }
+
+ /**
+ * Set Accessibility Scanner setting ON/OFF.
+ *
+ * @throws Exception
+ */
+ public void setAccessibilityScannerSetting(AccessibilityHelper.SwitchStatus value)
+ throws Exception {
+ if (!scannerInstalled()) {
+ throw new Exception("Accessibility Scanner not installed.");
+ }
+ mAccessibilityHelper.launchSpecificAccessibilitySetting("Accessibility Scanner");
+ for (int tries = 0; tries < 2; tries++) {
+ UiObject2 swt = mDevice.wait(Until.findObject(
+ By.res(AccessibilityHelper.SETTINGS_PACKAGE, "switch_widget")),
+ SHORT_TIMEOUT * 2);
+ if (swt.getText().equals(value.toString())) {
+ break;
+ } else if (tries == 1) {
+ throw new Exception(String.format("Fail to set scanner to: %s.", value.toString()));
+ } else {
+ swt.click();
+ UiObject2 okBtn = mDevice.wait(Until.findObject(By.text("OK")), SHORT_TIMEOUT);
+ if (okBtn != null) {
+ okBtn.click();
+ }
+ if (initialSetups()) {
+ mDevice.pressBack();
+ }
+ grantPermissions();
+ }
+ }
+ }
+
+ /**
+ * Click through all permission pop ups for scanner. Grant all necessary permissions.
+ */
+ private void grantPermissions() {
+ UiObject2 auth1 = mDevice.wait(Until.findObject(
+ By.text("BEGIN AUTHORIZATION")), SHORT_TIMEOUT);
+ if (auth1 != null) {
+ auth1.click();
+ }
+ UiObject2 chk = mDevice.wait(Until.findObject(
+ By.clazz(AccessibilityHelper.CHECK_BOX)), SHORT_TIMEOUT);
+ if (chk != null) {
+ chk.click();
+ mDevice.findObject(By.text("START NOW")).click();
+ }
+ UiObject2 auth2 = mDevice.wait(Until.findObject(
+ By.text("BEGIN AUTHORIZATION")), SHORT_TIMEOUT);
+ if (auth2 != null) {
+ auth2.click();
+ }
+ UiObject2 tapOk = mDevice.wait(Until.findObject(
+ By.pkg(ACCESSIBILITY_SCANNER_PACKAGE).text("OK")), SHORT_TIMEOUT);
+ if (tapOk != null) {
+ tapOk.click();
+ }
+ }
+
+ /**
+ * Launch accessibility scanner.
+ *
+ * @throws UiObjectNotFoundException
+ */
+ public void launchScannerApp() throws Exception {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ ComponentName settingComponent = new ComponentName(ACCESSIBILITY_SCANNER_PACKAGE,
+ String.format(MAIN_ACTIVITY_CLASS, ACCESSIBILITY_SCANNER_PACKAGE));
+ intent.setComponent(settingComponent);
+ mActivityHelper.launchIntent(intent);
+ initialSetups();
+ }
+
+ /**
+ * Steps for first time launching scanner app.
+ *
+ * @return true/false return false immediately, if initial setup screen doesn't show up.
+ * @throws Exception
+ */
+ private boolean initialSetups() throws Exception {
+ UiObject2 getStartBtn = mDevice.wait(
+ Until.findObject(By.text("GET STARTED")), SHORT_TIMEOUT);
+ if (getStartBtn != null) {
+ getStartBtn.click();
+ UiObject2 msg = mDevice.wait(Until.findObject(
+ By.text("Turn on Accessibility Scanner")), SHORT_TIMEOUT);
+ if (msg != null) {
+ mDevice.findObject(By.text("OK")).click();
+ setAccessibilityScannerSetting(AccessibilityHelper.SwitchStatus.ON);
+ }
+ mDevice.wait(Until.findObject(By.text("OK, GOT IT")), SCANNER_WAIT_TIME).click();
+ mDevice.wait(Until.findObject(By.text("DISMISS")), SHORT_TIMEOUT).click();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Clear history of accessibility scanner.
+ *
+ * @throws InterruptedException
+ */
+ public void clearHistory() throws Exception {
+ launchScannerApp();
+ int maxTry = 20;
+ while (maxTry > 0) {
+ List<UiObject2> historyItemList = mDevice.findObjects(
+ By.res(ACCESSIBILITY_SCANNER_PACKAGE, "history_item_row"));
+ if (historyItemList.size() == 0) {
+ break;
+ }
+ historyItemList.get(0).click();
+ Thread.sleep(SHORT_TIMEOUT);
+ deleteHistory();
+ Thread.sleep(SHORT_TIMEOUT);
+ maxTry--;
+ }
+ }
+
+ /**
+ * Log results of accessibility scanner.
+ *
+ * @param pageName
+ * @throws Exception
+ */
+ public void logScannerResult(String pageName) throws Exception {
+ int res = getNumberOfSuggestions();
+ if (res > 0) {
+ Log.i(RESULT_TAG, String.format("%s: %s suggestions!", pageName, res));
+ } else if (res == 0) {
+ Log.i(RESULT_TAG, String.format("%s: Pass.", pageName));
+ } else {
+ throw new UiObjectNotFoundException("Fail to get number of suggestions.");
+ }
+ }
+
+ /**
+ * Move scanner button to avoid blocking the object.
+ *
+ * @param avoidObj object to move the check button away from
+ */
+ public void adjustScannerButton(UiObject2 avoidObj)
+ throws UiObjectNotFoundException, InterruptedException {
+ Rect origBounds = getScannerCheckBtn().getVisibleBounds();
+ Rect avoidBounds = avoidObj.getVisibleBounds();
+ if (origBounds.intersect(avoidBounds)) {
+ Point dest = calculateDest(origBounds, avoidBounds);
+ moveScannerCheckButton(dest.x, dest.y);
+ }
+ }
+
+ /**
+ * Move scanner check button back to the middle of the screen.
+ */
+ public void resetScannerCheckButton() throws UiObjectNotFoundException, InterruptedException {
+ int midY = (int) Math.ceil(mDevice.getDisplayHeight() * 0.5);
+ int midX = (int) Math.ceil(mDevice.getDisplayWidth() * 0.5);
+ moveScannerCheckButton(midX, midY);
+ }
+
+ /**
+ * Move scanner check button to a target location.
+ *
+ * @param locX target location x-axis
+ * @param locY target location y-axis
+ * @throws UiObjectNotFoundException
+ */
+ public void moveScannerCheckButton(int locX, int locY)
+ throws UiObjectNotFoundException, InterruptedException {
+ int tries = 2;
+ while (tries-- > 0) {
+ UiObject2 btn = getScannerCheckBtn();
+ Rect bounds = btn.getVisibleBounds();
+ int origX = bounds.centerX();
+ int origY = bounds.centerY();
+ int buttonWidth = bounds.width();
+ int buttonHeight = bounds.height();
+ if (Math.abs(locX - origX) > buttonWidth || Math.abs(locY - origY) > buttonHeight) {
+ btn.drag(new Point(locX, locY));
+ }
+ Thread.sleep(SCANNER_WAIT_TIME);
+ // drag cause a click on the scanner button, bring the UI into scanner app
+ if (getScannerCheckBtn() == null
+ && mDevice.findObject(By.pkg(ACCESSIBILITY_SCANNER_PACKAGE)) != null) {
+ mDevice.pressBack();
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Calculate the moving destination of check button.
+ *
+ * @param origRect original bounds of the check button
+ * @param avoidRect bounds to move away from
+ * @return destination of check button center point.
+ */
+ private Point calculateDest(Rect origRect, Rect avoidRect) {
+ int bufferY = (int)Math.ceil(mDevice.getDisplayHeight() * 0.1);
+ int destY = avoidRect.bottom + bufferY + origRect.height()/2;
+ if (destY >= mDevice.getDisplayHeight()) {
+ destY = avoidRect.top - bufferY - origRect.height()/2;
+ }
+ return new Point(origRect.centerX(), destY);
+ }
+
+ /**
+ * Return scanner check button.
+ *
+ * @return UiObject2
+ */
+ private UiObject2 getScannerCheckBtn() {
+ return mDevice.findObject(By.res(ACCESSIBILITY_SCANNER_PACKAGE, CHECK_BUTTON_RES_ID));
+ }
+
+ private void clickScannerCheck() throws UiObjectNotFoundException, InterruptedException {
+ UiObject2 accessibilityScannerButton = getScannerCheckBtn();
+ if (accessibilityScannerButton != null) {
+ accessibilityScannerButton.click();
+ } else {
+ // TODO: check if app crash error, restart scanner service
+ Log.i(LOG_TAG, "Fail to find accessibility scanner check button.");
+ throw new UiObjectNotFoundException(
+ "Fail to find accessibility scanner check button.");
+ }
+ Thread.sleep(SCANNER_WAIT_TIME);
+ }
+
+ /**
+ * Check if no suggestion.
+ * @deprecated Use {@link #getNumberOfSuggestions} instead
+ */
+ @Deprecated
+ private Boolean testPass() throws UiObjectNotFoundException {
+ UiObject2 txtView = getToolBarTextView();
+ return txtView.getText().equals("No suggestions");
+ }
+
+ /**
+ * Return accessibility scanner tool bar text view.
+ *
+ * @return UiObject2
+ * @throws UiObjectNotFoundException
+ */
+ private UiObject2 getToolBarTextView() throws UiObjectNotFoundException {
+ UiObject2 toolBar = mDevice.wait(Until.findObject(
+ By.res(ACCESSIBILITY_SCANNER_PACKAGE, "toolbar")), SHORT_TIMEOUT);
+ if (toolBar != null) {
+ return toolBar.findObject(By.clazz(AccessibilityHelper.TEXT_VIEW));
+ } else {
+ throw new UiObjectNotFoundException(
+ "Failed to find Scanner tool bar. Scanner app might not be active.");
+ }
+ }
+
+ /**
+ * Delete active scanner history.
+ */
+ private void deleteHistory() {
+ UiObject2 moreBtn = mDevice.wait(Until.findObject(By.desc("More options")), SHORT_TIMEOUT);
+ if (moreBtn != null) {
+ moreBtn.click();
+ mDevice.wait(Until.findObject(
+ By.clazz(AccessibilityHelper.TEXT_VIEW).text("Delete")), SHORT_TIMEOUT).click();
+ }
+ }
+
+ /**
+ * Return number suggestions.
+ *
+ * @return number of suggestions
+ * @throws UiObjectNotFoundException
+ */
+ private int getNumberOfSuggestions() throws UiObjectNotFoundException {
+ int tries = 2; // retries
+ while (tries-- > 0) {
+ UiObject2 txtView = getToolBarTextView();
+ if (txtView != null) {
+ String result = txtView.getText();
+ if (result.equals("No suggestions")) {
+ return 0;
+ } else {
+ String str = result.split("\\s+")[0];
+ return Integer.parseInt(str);
+ }
+ }
+ }
+ Log.i(LOG_TAG, String.format("Error in getting number of suggestions."));
+ return -1;
+ }
+
+ /**
+ * Return share app UiObject2
+ *
+ * @param appName
+ * @return
+ */
+ private UiObject2 getShareApp(String appName) throws UiObjectNotFoundException {
+ UiObject2 shareBtn = mDevice.wait(Until.findObject(By.res(ACCESSIBILITY_SCANNER_PACKAGE,
+ "action_share_results")), SHORT_TIMEOUT);
+ if (shareBtn != null) {
+ shareBtn.click();
+ mDevice.wait(Until.hasObject(By.res("android:id/resolver_list")), SHORT_TIMEOUT * 3);
+ UiScrollable scrollable = new UiScrollable(
+ new UiSelector().className("android.widget.ScrollView"));
+ int tries = 3;
+ while (!mDevice.hasObject(By.text(appName)) && tries-- > 0) {
+ scrollable.scrollForward();
+ }
+ return mDevice.findObject(By.text(appName));
+ }
+ return null;
+ }
+
+ /**
+ * Return if scanner enabled by check if home screen has check button.
+ *
+ * @return true/false
+ */
+ public boolean ifScannerEnabled() throws InterruptedException {
+ mDevice.pressHome();
+ Thread.sleep(SHORT_TIMEOUT);
+ mDevice.waitForIdle();
+ return getScannerCheckBtn() != null;
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/account-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/account-helper/Android.mk
index 9e797fb..e7e20eb 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/account-helper/Android.mk
@@ -16,13 +16,11 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := account-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ activity-helper \
+ device-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/account-helper/src/android/system/helpers/AccountHelper.java b/libraries/system-helpers/account-helper/src/android/system/helpers/AccountHelper.java
new file mode 100644
index 0000000..b210bdd
--- /dev/null
+++ b/libraries/system-helpers/account-helper/src/android/system/helpers/AccountHelper.java
@@ -0,0 +1,114 @@
+/*
+ * 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.system.helpers;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.system.helpers.ActivityHelper;
+import android.system.helpers.DeviceHelper;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import junit.framework.Assert;
+
+/**
+ * Implement common helper methods for account.
+ */
+public class AccountHelper {
+ private static final String TAG = AccountHelper.class.getSimpleName();
+ public static final int TIMEOUT = 1000;
+ private static AccountHelper sInstance = null;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+ private ActivityHelper mActivityHelper = null;
+ private DeviceHelper mDeviceHelper = null;
+
+ public AccountHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mActivityHelper = ActivityHelper.getInstance();
+ mDeviceHelper = DeviceHelper.getInstance();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public static AccountHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new AccountHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Checks whether a google account has been enabled in device for backup
+ * @return true/false
+ * @throws InterruptedException
+ */
+ public boolean hasDeviceBackupAccount() throws InterruptedException {
+ mActivityHelper.launchIntent(android.provider.Settings.ACTION_PRIVACY_SETTINGS);
+ dismissInitalDialogs();
+ Pattern pattern = Pattern.compile("Backup account", Pattern.CASE_INSENSITIVE);
+ if (mDeviceHelper.isNexusExperienceDevice()) {
+ pattern = Pattern.compile("Device backup", Pattern.CASE_INSENSITIVE);
+ }
+ UiObject2 deviceBackup = mDevice.wait(Until.findObject(By.text(pattern)),
+ TIMEOUT * 5);
+ if (deviceBackup!=null){
+ String backupAcct = deviceBackup.getParent().getChildren().get(1).getText();
+ if (backupAcct.equals(getRegisteredGoogleAccountOnDevice())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get registered accounts ensures there is at least one account registered returns the google
+ * account name
+ * @return The registered gogole/gmail account on device
+ */
+ public String getRegisteredGoogleAccountOnDevice() {
+ Account[] accounts = AccountManager.get(mContext).getAccounts();
+ Assert.assertTrue("Device doesn't have any account registered", accounts.length >= 1);
+ for (int i = 0; i < accounts.length; ++i) {
+ if (accounts[i].type.equals("com.google")) {
+ return accounts[i].name;
+ }
+ }
+ throw new RuntimeException("The device is not registered with a google account");
+ }
+
+ private void dismissInitalDialogs() throws InterruptedException{
+ UiObject2 backupDialog = mDevice.wait(
+ Until.findObject(By.text("Backup & reset")),
+ TIMEOUT);
+ if (backupDialog!=null){
+ backupDialog.click();
+ Thread.sleep(TIMEOUT);
+ UiObject2 alwaysBtn = mDevice.wait(
+ Until.findObject(By.res("android","button_always")),
+ TIMEOUT);
+ if (alwaysBtn!=null){
+ alwaysBtn.click();
+ }
+ }
+ Thread.sleep(TIMEOUT);
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/activity-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/activity-helper/Android.mk
index 9e797fb..efb71ca 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/activity-helper/Android.mk
@@ -16,13 +16,10 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := activity-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ commands-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/activity-helper/src/android/system/helpers/ActivityHelper.java b/libraries/system-helpers/activity-helper/src/android/system/helpers/ActivityHelper.java
new file mode 100644
index 0000000..c768057
--- /dev/null
+++ b/libraries/system-helpers/activity-helper/src/android/system/helpers/ActivityHelper.java
@@ -0,0 +1,213 @@
+/*
+ * 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.system.helpers;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import junit.framework.Assert;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Implement common helper methods for activities.
+ */
+public class ActivityHelper {
+ private static final String TAG = ActivityHelper.class.getSimpleName();
+
+ public static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+ public static final int FULLSCREEN = 1;
+ public static final int SPLITSCREEN = 3;
+ public static final int TIMEOUT = 1000;
+ public static final int INVALID_TASK_ID = -1;
+
+ private static ActivityHelper sInstance = null;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+
+ public ActivityHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public static ActivityHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new ActivityHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Gets task id for an activity
+ *
+ * @param pkgName
+ * @param activityName
+ * @return taskId or -1 when no activity is found
+ */
+ public int getTaskIdForActivity(String pkgName, String activityName) {
+ int taskId = INVALID_TASK_ID;
+ // Find task id for given package and activity
+ final Pattern TASK_REGEX = Pattern.compile(
+ String.format("taskId=(\\d+): %s/%s", pkgName, activityName));
+ Matcher matcher = TASK_REGEX.matcher(CommandsHelper.execute("am stack list"));
+ if (matcher.find()) {
+ taskId = Integer.parseInt(matcher.group(1));
+ Log.i(TAG, String.format("TaskId found: %d for %s/%s",
+ taskId, pkgName, activityName));
+ }
+ Assert.assertTrue("Taskid hasn't been found", taskId != -1);
+ return taskId;
+ }
+
+ /**
+ * Helper to change window mode between fullscreen and splitscreen for a given task
+ *
+ * @param taskId
+ * @param mode
+ * @throws InterruptedException
+ */
+ public void changeWindowMode(int taskId, int mode) throws InterruptedException {
+ CommandsHelper.execute(
+ String.format("am stack move-task %d %d true", taskId, mode));
+ Thread.sleep(TIMEOUT);
+ }
+
+ /**
+ * Clears apps in overview/recents
+ *
+ * @throws InterruptedException
+ * @throws RemoteException
+ */
+ public void clearRecents() throws InterruptedException, RemoteException {
+ // Launch recents if it's not already
+ int retry = 5;
+ while (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "recents_view")),
+ TIMEOUT * 5) && --retry > 0) {
+ mDevice.pressRecentApps();
+ Thread.sleep(TIMEOUT);
+ }
+ // Return if there is no apps in recents
+ if (mDevice.wait(Until.hasObject(By.text("No recent items")), TIMEOUT * 5)) {
+ return;
+ } else {
+ Assert.assertTrue("Device expects recent items", mDevice.wait(Until.hasObject(
+ By.res(SYSTEMUI_PACKAGE, "recents_view")), TIMEOUT * 5));
+ }
+ // Get recents items
+ int recents = mDevice.wait(Until.findObjects(
+ By.res(SYSTEMUI_PACKAGE, "task_view_thumbnail")), TIMEOUT * 5).size();
+ // Clear recents
+ for (int i = 0; i < recents; ++i) {
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_APP_SWITCH);
+ Thread.sleep(TIMEOUT);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_DEL);
+ Thread.sleep(TIMEOUT);
+ }
+ }
+
+ /**
+ * Clear recent apps by click 'CLEAR ALL' button in the recents view.
+ *
+ * @throws Exception
+ */
+ public void clearRecentsByClearAll() throws Exception {
+ int retry = 5;
+ while (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "recents_view")),
+ TIMEOUT * 5) && --retry > 0) {
+ mDevice.pressRecentApps();
+ Thread.sleep(TIMEOUT);
+ }
+ int maxTries = 20;
+ while (!mDevice.hasObject(By.text("No recent items")) && maxTries-- >= 0) {
+ UiScrollable thumbnailScrollable = new UiScrollable(
+ new UiSelector().className("android.widget.ScrollView"));
+ thumbnailScrollable.scrollToBeginning(100);
+ if (!mDevice.wait(Until.hasObject(By.text("CLEAR ALL")), TIMEOUT * 2)) {
+ continue;
+ } else {
+ int tries = 3;
+ while (mDevice.hasObject(By.text("CLEAR ALL")) && tries-- > 0) {
+ mDevice.findObject(By.text("CLEAR ALL")).click();
+ Thread.sleep(TIMEOUT * 2);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Enable/disable bmgr service
+ *
+ * @param enable true to enable, false to disable
+ */
+ public void enableBmgr(boolean enable) {
+ String output = CommandsHelper.execute("bmgr enable " + Boolean.toString(enable));
+ if (enable) {
+ Assert.assertTrue("Bmgr not enabled",
+ output.indexOf("Backup Manager now enabled") >= 0);
+ } else {
+ Assert.assertTrue("Bmgr not disabled",
+ output.indexOf("Backup Manager now disabled") >= 0);
+ }
+ }
+
+ /**
+ * Launch an intent when intent is of string type
+ *
+ * @param intentName
+ * @throws InterruptedException
+ */
+ public void launchIntent(String intentName) throws InterruptedException {
+ mDevice.pressHome();
+ Intent intent = new Intent(intentName);
+ launchIntent(intent);
+ }
+
+ /**
+ * Find intent of a package and launch
+ *
+ * @param pkgName
+ * @throws InterruptedException
+ */
+ public void launchPackage(String pkgName) throws InterruptedException {
+ Intent pkgIntent = mContext.getPackageManager()
+ .getLaunchIntentForPackage(pkgName);
+ launchIntent(pkgIntent);
+ }
+
+ /**
+ * launch an intent when intent is of Intent type
+ *
+ * @param intent
+ * @throws InterruptedException
+ */
+ public void launchIntent(Intent intent) throws InterruptedException {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ Thread.sleep(TIMEOUT * 5);
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/commands-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/commands-helper/Android.mk
index 9e797fb..15f1c66 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/commands-helper/Android.mk
@@ -16,13 +16,9 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := commands-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/commands-helper/src/android/system/helpers/CommandsHelper.java b/libraries/system-helpers/commands-helper/src/android/system/helpers/CommandsHelper.java
new file mode 100644
index 0000000..0f8783d
--- /dev/null
+++ b/libraries/system-helpers/commands-helper/src/android/system/helpers/CommandsHelper.java
@@ -0,0 +1,102 @@
+package android.system.helpers;
+
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implement common helper for executing shell commands on device
+ */
+public class CommandsHelper {
+ private static final String TAG = CommandsHelper.class.getSimpleName();
+ private static CommandsHelper sInstance = null;
+ private Instrumentation mInstrumentation = null;
+
+ private static final String LINE_SEPARATORS = "\\r?\\n";
+
+
+ private CommandsHelper(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ /**
+ * @deprecated Should use {@link CommandsHelper#getInstance(Instrumentation)} instead.
+ */
+ @Deprecated
+ public static CommandsHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new CommandsHelper(InstrumentationRegistry.getInstrumentation());
+ }
+ return sInstance;
+ }
+
+ public static CommandsHelper getInstance(Instrumentation instrumentation) {
+ if (sInstance == null) {
+ sInstance = new CommandsHelper(instrumentation);
+ } else {
+ sInstance.injectInstrumentation(instrumentation);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Injects instrumentation into this helper.
+ *
+ * @param instrumentation the instrumentation to use with this instance
+ */
+ public void injectInstrumentation(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ }
+
+ /**
+ * Executes a shell command on device, and return the standard output in string.
+ * @param command the command to run
+ * @return the standard output of the command, or empty string if
+ * failed without throwing an IOException
+ */
+ public String executeShellCommand(String command) {
+ try {
+ return UiDevice.getInstance(mInstrumentation).executeShellCommand(command);
+ } catch (IOException e) {
+ // ignore
+ Log.e(TAG, String.format("The shell command failed to run: %s exception: %s",
+ command, e.getMessage()));
+ return "";
+ }
+ }
+
+ /**
+ * Executes a shell command on device, and split the multi-line output into collection
+ * @param command the command to run
+ * @param separatorChars the line separator
+ * @return the List of strings from the standard output of the command
+ */
+ public List<String> executeShellCommandAndSplitOutput(String command,
+ final String separatorChars) {
+ return Arrays.asList(executeShellCommand(command).split(separatorChars));
+ }
+
+ /**
+ * Convenience version of {@link #executeShellCommand} for use without having a reference to
+ * CommandsHelper.
+ * @param command the command to run
+ */
+ @Deprecated
+ public static String execute(String command) {
+ return getInstance().executeShellCommand(command);
+ }
+
+ /**
+ * Convenience version of {@link #executeShellCommandAndSplitOutput} for use
+ * without having a reference to CommandsHelper.
+ * @param command the command to run
+ */
+ @Deprecated
+ public static List<String> executeAndSplitLines(String command) {
+ return getInstance().executeShellCommandAndSplitOutput(command, LINE_SEPARATORS);
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/connectivity-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/connectivity-helper/Android.mk
index 9e797fb..4ed55aa 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/connectivity-helper/Android.mk
@@ -16,13 +16,10 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := connectivity-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ commands-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/connectivity-helper/src/android/system/helpers/ConnectivityHelper.java b/libraries/system-helpers/connectivity-helper/src/android/system/helpers/ConnectivityHelper.java
new file mode 100644
index 0000000..9006afc
--- /dev/null
+++ b/libraries/system-helpers/connectivity-helper/src/android/system/helpers/ConnectivityHelper.java
@@ -0,0 +1,217 @@
+/*
+ * 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.system.helpers;
+
+import android.app.DownloadManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.test.InstrumentationRegistry;
+import android.system.helpers.CommandsHelper;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import junit.framework.Assert;
+
+/**
+ * Implement common helper methods for connectivity.
+ */
+public class ConnectivityHelper {
+ private static final String TAG = ConnectivityHelper.class.getSimpleName();
+ private final static String DEFAULT_PING_SITE = "http://www.google.com";
+
+ public static final int TIMEOUT = 1000;
+ private static ConnectivityHelper sInstance = null;
+ private Context mContext = null;
+ private CommandsHelper mCommandsHelper = null;
+
+ public ConnectivityHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mCommandsHelper = CommandsHelper.getInstance();
+ }
+
+ public static ConnectivityHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new ConnectivityHelper();
+ }
+ return sInstance;
+ }
+
+ public TelecomManager getTelecomManager() {
+ return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ }
+
+ public WifiManager getWifiManager() {
+ return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ }
+
+ public ConnectivityManager getConnectivityManager() {
+ return (ConnectivityManager) (ConnectivityManager) mContext
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ public DownloadManager getDownloadManager() {
+ return (DownloadManager) (DownloadManager) mContext
+ .getSystemService(Context.DOWNLOAD_SERVICE);
+ }
+
+ public BluetoothAdapter getBluetoothAdapter() {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
+
+ /**
+ * Checks if device connection is active either through wifi or mobile data by sending an HTTP
+ * request, check for HTTP_OK
+ */
+ public boolean isConnected() throws InterruptedException {
+ int counter = 7;
+ long TIMEOUT_MS = TIMEOUT;
+ HttpURLConnection conn = null;
+ while (--counter > 0) {
+ try{
+ URL url = new URL(DEFAULT_PING_SITE);
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("GET");
+ conn.setConnectTimeout(TIMEOUT * 60); // 1 minute
+ conn.setReadTimeout(TIMEOUT * 60); // 1 minute
+ Log.i(TAG, "connection response code is " + conn.getResponseCode());
+ Log.i(TAG, " counter = " + counter);
+ return true;
+ } catch (IOException ex) {
+ // Wifi being flaky in the lab, test retries 10 times to connect to google.com
+ // as IOException is throws connection isn't made and response stream is null
+ // so for retrying purpose, exception hasn't been rethrown
+ Log.i(TAG, ex.getMessage());
+ } finally {
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+ Thread.sleep(TIMEOUT_MS);
+ TIMEOUT_MS = 2 * TIMEOUT_MS;
+ }
+ Log.i(TAG, " counter = " + counter);
+ return false;
+ }
+
+ /**
+ * Disconnects and disables network
+ * @return true/false
+ */
+ public int disconnectWifi() {
+ Assert.assertTrue("Wifi not disconnected", getWifiManager().disconnect());
+ int netId = getWifiManager().getConnectionInfo().getNetworkId();
+ getWifiManager().disableNetwork(netId);
+ getWifiManager().saveConfiguration();
+ return netId;
+ }
+
+ /**
+ * Ensures wifi is enabled in device
+ * @throws InterruptedException
+ */
+ public void ensureWifiEnabled() throws InterruptedException {
+ // Device already connected to wifi as part of tradefed setup
+ if (!getWifiManager().isWifiEnabled()) {
+ getWifiManager().enableNetwork(getWifiManager().getConnectionInfo().getNetworkId(),
+ true);
+ int counter = 5;
+ while (--counter > 0 && !getWifiManager().isWifiEnabled()) {
+ Thread.sleep(TIMEOUT * 5);
+ }
+ }
+ Assert.assertTrue("Wifi should be enabled by now", getWifiManager().isWifiEnabled());
+ }
+
+ /**
+ * Checks whether device has wifi connection for data service
+ * @return true/false
+ */
+ public boolean hasWifiData() {
+ NetworkInfo netInfo = getConnectivityManager().getActiveNetworkInfo();
+ Assert.assertNotNull(netInfo);
+ return (netInfo.getType() == ConnectivityManager.TYPE_WIFI);
+ }
+
+ /**
+ * Checks whether device has mobile connection for data service
+ * @return true/false
+ */
+ public boolean hasMobileData() {
+ NetworkInfo netInfo = getConnectivityManager().getActiveNetworkInfo();
+ Assert.assertNotNull(netInfo);
+ return (netInfo.getType() == ConnectivityManager.TYPE_MOBILE);
+ }
+
+ /**
+ * Checks whether device has sim
+ * @return true/false
+ */
+ public boolean hasDeviceSim() {
+ TelephonyManager telMgr = (TelephonyManager) mContext
+ .getSystemService(mContext.TELEPHONY_SERVICE);
+ return (telMgr.getSimState() == TelephonyManager.SIM_STATE_READY);
+ }
+
+ /**
+ * Get connected wifi SSID.
+ * @return connected wifi SSID
+ */
+ public String getCurrentWifiSSID() {
+ WifiInfo connectionInfo = getWifiManager().getConnectionInfo();
+ if (connectionInfo != null) {
+ return connectionInfo.getSSID();
+ }
+ return null;
+ }
+
+ /**
+ * Ensure bluetooth is enabled on device.
+ * @throws InterruptedException
+ */
+ public void ensureBluetoothEnabled() throws InterruptedException {
+ if (!getBluetoothAdapter().isEnabled()) {
+ getBluetoothAdapter().enable();
+ }
+ int counter = 5;
+ while (--counter > 0 && !getBluetoothAdapter().isEnabled()) {
+ Thread.sleep(TIMEOUT * 2);
+ }
+ }
+
+ /**
+ * Check whether device has mobile data available.
+ * @return true/false
+ */
+ public boolean mobileDataAvailable() {
+ Network[] networkArray = getConnectivityManager().getAllNetworks();
+ for (Network net: networkArray) {
+ if (getConnectivityManager().getNetworkInfo(net).getType()
+ == ConnectivityManager.TYPE_MOBILE) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/device-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/device-helper/Android.mk
index 9e797fb..cd32d11 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/device-helper/Android.mk
@@ -16,13 +16,8 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := device-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/device-helper/src/android/system/helpers/DeviceHelper.java b/libraries/system-helpers/device-helper/src/android/system/helpers/DeviceHelper.java
new file mode 100644
index 0000000..73ea852
--- /dev/null
+++ b/libraries/system-helpers/device-helper/src/android/system/helpers/DeviceHelper.java
@@ -0,0 +1,96 @@
+/*
+ * 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.system.helpers;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.view.WindowManager;
+import android.util.DisplayMetrics;
+
+/**
+ * Implement common helper methods for device.
+ */
+public class DeviceHelper {
+
+ public static final String PIXEL_XL = "Pixel XL";
+ public static final String PIXEL = "Pixel";
+ public static final String RYU = "Pixel C";
+ // 600dp is the threshold value for 7-inch tablets.
+ private static final int TABLET_DP_THRESHOLD = 600;
+ public static final int LONG_TIMEOUT = 2000;
+ private static DeviceHelper sInstance = null;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+
+ public DeviceHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public static DeviceHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new DeviceHelper();
+ }
+ return sInstance;
+ }
+
+ /** Returns true if the device is a tablet */
+ public boolean isTablet() {
+ // Get screen density & screen size from window manager
+ WindowManager wm = (WindowManager) mContext.getSystemService(
+ Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metrics);
+ // Determines the smallest screen width DP which is
+ // calculated as ( pixels * density - independent pixel unit ) / density.
+ // http://developer.android.com/guide/practices/screens_support.html.
+ int screenDensity = metrics.densityDpi;
+ int screenWidth = Math.min(
+ metrics.widthPixels, metrics.heightPixels);
+ int screenHeight = Math.max(
+ metrics.widthPixels, metrics.heightPixels);
+ int smallestScreenWidthDp = (Math.min(screenWidth, screenHeight)
+ * DisplayMetrics.DENSITY_DEFAULT) / screenDensity;
+ return smallestScreenWidthDp >= TABLET_DP_THRESHOLD;
+ }
+
+ public boolean isNexusExperienceDevice() {
+ // Get device model
+ String result = Build.MODEL;
+ if (result.trim().equalsIgnoreCase(PIXEL) || result.trim().equalsIgnoreCase(PIXEL_XL)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isRyuDevice() {
+ return Build.MODEL.trim().equalsIgnoreCase(RYU);
+ }
+
+ /**
+ * Device sleep and wake up
+ * @throws RemoteException, InterruptedException
+ */
+ public void sleepAndWakeUpDevice() throws RemoteException, InterruptedException {
+ mDevice.sleep();
+ Thread.sleep(LONG_TIMEOUT);
+ mDevice.wakeUp();
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/package-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/package-helper/Android.mk
index 9e797fb..3a06fc6 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/package-helper/Android.mk
@@ -16,13 +16,9 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := package-helper
+LOCAL_JAVA_LIBRARIES := commands-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/package-helper/src/android/system/helpers/PackageHelper.java b/libraries/system-helpers/package-helper/src/android/system/helpers/PackageHelper.java
new file mode 100644
index 0000000..5faff90
--- /dev/null
+++ b/libraries/system-helpers/package-helper/src/android/system/helpers/PackageHelper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.system.helpers;
+
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.system.helpers.CommandsHelper;
+
+/**
+ * Implement common helper methods for package management.
+ * eg. Delete package data.
+ */
+public class PackageHelper {
+ public final int TIMEOUT = 500;
+ public static PackageHelper sInstance = null;
+ private Instrumentation mInstrumentation = null;
+ private CommandsHelper cmdHelper = null;
+ private PackageManager mPackageManager = null;
+
+ public PackageHelper(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ cmdHelper = CommandsHelper.getInstance(instrumentation);
+ mPackageManager = instrumentation.getTargetContext().getPackageManager();
+ }
+
+ public static PackageHelper getInstance(Instrumentation instrumentation) {
+ if (sInstance == null) {
+ sInstance = new PackageHelper(instrumentation);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Deletes all data associated with the package.
+ * @param packageName package name
+ */
+ public void cleanPackage(String packageName) {
+ cmdHelper.executeShellCommand(String.format("pm clear %s", packageName));
+ SystemClock.sleep(2 * TIMEOUT);
+ }
+
+ /**
+ * Check if certain package is installed on the device.
+ * @param packageName package name
+ * @return true/false
+ */
+ public Boolean isPackageInstalled(String packageName) {
+ try {
+ mPackageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/permission-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/permission-helper/Android.mk
index 9e797fb..5ac70a6 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/permission-helper/Android.mk
@@ -16,13 +16,11 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := permission-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ launcher-helper-lib \
+ android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/functional/permission/src/com/android/functional/permissiontests/PermissionHelper.java b/libraries/system-helpers/permission-helper/src/android/system/helpers/PermissionHelper.java
similarity index 89%
rename from tests/functional/permission/src/com/android/functional/permissiontests/PermissionHelper.java
rename to libraries/system-helpers/permission-helper/src/android/system/helpers/PermissionHelper.java
index d6edd8a..092dbd8 100644
--- a/tests/functional/permission/src/com/android/functional/permissiontests/PermissionHelper.java
+++ b/libraries/system-helpers/permission-helper/src/android/system/helpers/PermissionHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.functional.permissiontests;
+package android.system.helpers;
import android.app.UiAutomation;
import android.content.Context;
@@ -25,6 +25,7 @@
import android.os.SystemClock;
import android.support.test.launcherhelper.ILauncherStrategy;
import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
@@ -36,42 +37,59 @@
import java.io.BufferedReader;
import java.io.FileInputStream;
-import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.ArrayList;
+import java.io.IOException;
import java.util.Arrays;
-import java.util.Hashtable;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Hashtable;
+/**
+ * Implement common helper methods for permissions.
+ */
public class PermissionHelper {
public static final String TEST_TAG = "PermissionTest";
public static final String SETTINGS_PACKAGE = "com.android.settings";
+ public static final int REQUESTED_PERMISSION_FLAG_GRANTED = 3;
+ public static final int REQUESTED_PERMISSION_FLAG_DENIED = 1;
public final int TIMEOUT = 500;
public static PermissionHelper mInstance = null;
- private UiDevice mDevice;
- private Context mContext;
- private static UiAutomation mUiAutomation;
+ private UiDevice mDevice = null;
+ private Context mContext = null;
+ private static UiAutomation mUiAutomation = null;
public static Hashtable<String, List<String>> mPermissionGroupInfo = null;
- ILauncherStrategy mLauncherStrategy;
+ ILauncherStrategy mLauncherStrategy = null;
- private PermissionHelper(UiDevice device, Context context, UiAutomation uiAutomation) {
- mDevice = device;
- mContext = context;
- mUiAutomation = uiAutomation;
+ /** Supported operations on permission */
+ public enum PermissionOp {
+ GRANT, REVOKE;
+ }
+
+ /** Available permission status */
+ public enum PermissionStatus {
+ ON, OFF;
+ }
+
+ private PermissionHelper() {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mContext = InstrumentationRegistry.getTargetContext();
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
}
- public static PermissionHelper getInstance(UiDevice device, Context context,
- UiAutomation uiAutomation) {
+ public static PermissionHelper getInstance() {
if (mInstance == null) {
- mInstance = new PermissionHelper(device, context, uiAutomation);
+ mInstance = new PermissionHelper();
PermissionHelper.populateDangerousPermissionGroupInfo();
}
return mInstance;
}
/**
- * Returns list of all dangerous permission of the system
+ * Populates a list of all dangerous permission of the system
+ * Dangerous permissions are higher-risk permissoins that grant requesting applications
+ * access to private user data or control over the device that can negatively impact
+ * the user
*/
private static void populateDangerousPermissionGroupInfo() {
ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand("pm list permissions -g -d");
@@ -81,9 +99,7 @@
List<String> permissions = new ArrayList<String>();
String groupName = null;
while ((line = reader.readLine()) != null) {
- if (line.isEmpty()) {
- // Do nothing
- } else if (line.startsWith("group")) {
+ if (line.startsWith("group")) {
if (mPermissionGroupInfo == null) {
mPermissionGroupInfo = new Hashtable<String, List<String>>();
} else {
@@ -102,10 +118,10 @@
}
/**
- * Returns list of permission asked by package
+ * Returns list of granted/denied permission asked by package
* @param packageName : PackageName for which permission list to be returned
* @param permitted : set 'true' for normal and default granted dangerous permissions, 'false'
- * otherwise
+ * for permissions currently denied by package
* @return
*/
public List<String> getPermissionByPackage(String packageName, Boolean permitted) {
@@ -114,7 +130,7 @@
int[] requestedPermissionFlags = null;
PackageInfo packageInfo = null;
try {
- packageInfo = getPackageManager().getPackageInfo(packageName,
+ packageInfo = mContext.getPackageManager().getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS);
} catch (NameNotFoundException e) {
throw new RuntimeException(String.format("%s package isn't found", packageName));
@@ -124,9 +140,11 @@
requestedPermissionFlags = packageInfo.requestedPermissionsFlags;
for (int i = 0; i < requestedPermissions.length; ++i) {
// requestedPermissionFlags 1 = Denied, 3 = Granted
- if (permitted && requestedPermissionFlags[i] == 3) {
+ if (permitted && requestedPermissionFlags[i]
+ == REQUESTED_PERMISSION_FLAG_GRANTED) {
selectedPermissions.add(requestedPermissions[i]);
- } else if (!permitted && requestedPermissionFlags[i] == 1) {
+ } else if (!permitted && requestedPermissionFlags[i]
+ == REQUESTED_PERMISSION_FLAG_DENIED) {
selectedPermissions.add(requestedPermissions[i]);
}
}
@@ -253,13 +271,15 @@
*/
public void openAppPermissionView(String appName) {
mDevice.pressHome();
- launchApp(SETTINGS_PACKAGE, "Settings");
+ if (!mDevice.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0))) {
+ mLauncherStrategy.launch("Settings", SETTINGS_PACKAGE);
+ }
UiObject2 app = null;
UiObject2 view = null;
int maxAttempt = 5;
while ((maxAttempt-- > 0)
&& ((app = mDevice.wait(Until.findObject(By.res("android:id/title").text("Apps")),
- TIMEOUT)) == null)) {
+ TIMEOUT)) == null)) {
view = mDevice.wait(Until.findObject(By.res(SETTINGS_PACKAGE, "main_content")),
TIMEOUT);
// todo scroll may be different for device and build
@@ -274,7 +294,7 @@
maxAttempt = 10;
while ((maxAttempt-- > 0)
&& ((app = mDevice.wait(Until.findObject(By.res("android:id/title").text(appName)),
- TIMEOUT)) == null)) {
+ TIMEOUT)) == null)) {
view = mDevice.wait(Until.findObject(By.res("com.android.settings:id/main_content")),
TIMEOUT);
// todo scroll may be different for device and build
@@ -357,8 +377,8 @@
}
/**
- * To ensure that all default dangerous permissions mentioned in manifest are granted for any
- * privileged app
+ * Set package permissions to ensure that all default dangerous permissions
+ * mentioned in manifest are granted for any privileged app
* @param packageName
* @param granted
* @param denied
@@ -402,29 +422,4 @@
}
return groupNames;
}
-
- public PackageManager getPackageManager() {
- return mContext.getPackageManager();
- }
-
- public void launchApp(String packageName, String appName) {
- if (!mDevice.hasObject(By.pkg(packageName).depth(0))) {
- mLauncherStrategy.launch(appName, packageName);
- }
- }
-
- public void cleanPackage(String packageName) {
- mUiAutomation.executeShellCommand(String.format("pm clear %s", packageName));
- SystemClock.sleep(2 * TIMEOUT);
- }
-
- /** Supported operations on permission */
- public enum PermissionOp {
- GRANT, REVOKE;
- }
-
- /** Available permission status */
- public enum PermissionStatus {
- ON, OFF;
- }
}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/settings-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/settings-helper/Android.mk
index 9e797fb..74e0098 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/settings-helper/Android.mk
@@ -16,13 +16,10 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := settings-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ activity-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/settings-helper/src/android/system/helpers/SettingsHelper.java b/libraries/system-helpers/settings-helper/src/android/system/helpers/SettingsHelper.java
new file mode 100644
index 0000000..f113a85
--- /dev/null
+++ b/libraries/system-helpers/settings-helper/src/android/system/helpers/SettingsHelper.java
@@ -0,0 +1,594 @@
+/*
+ * 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.system.helpers;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import junit.framework.Assert;
+
+import java.util.regex.Pattern;
+
+/**
+ * Implement common helper methods for settings.
+ */
+public class SettingsHelper {
+ private static final String TAG = SettingsHelper.class.getSimpleName();
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+ private static final String SETTINGS_APP = "Settings";
+ private static final String SWITCH_WIDGET = "switch_widget";
+ private static final String WIFI = "Wi-Fi";
+ private static final String BLUETOOTH = "Bluetooth";
+ private static final String AIRPLANE = "Airplane mode";
+ private static final String LOCATION = "Location";
+ private static final String DND = "Do not disturb";
+ private static final String ZEN_MODE = "zen_mode";
+ private static final String FLASHLIGHT = "Flashlight";
+ private static final String AUTO_ROTATE_SCREEN = "Auto-rotate screen";
+ private static final BySelector SETTINGS_DASHBOARD = By.res(SETTINGS_PACKAGE,
+ "dashboard_container");
+ private static final UiSelector LIST_ITEM_VALUE =
+ new UiSelector().className(TextView.class);
+ public static final int TIMEOUT = 2000;
+ private static SettingsHelper sInstance = null;
+ private ActivityHelper mActivityHelper = null;
+ private ContentResolver mResolver = null;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+
+ public SettingsHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResolver = mContext.getContentResolver();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mActivityHelper = ActivityHelper.getInstance();
+ }
+
+ public static SettingsHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new SettingsHelper();
+ }
+ return sInstance;
+ }
+
+ public static enum SettingsType {
+ SYSTEM, SECURE, GLOBAL
+ }
+
+ /**
+ * @return Settings package name
+ */
+ public String getPackage() {
+ return SETTINGS_PACKAGE;
+ }
+
+ /**
+ * @return Settings app name
+ */
+ public String getLauncherName() {
+ return SETTINGS_APP;
+ }
+
+ /**
+ * Scroll through settings page
+ * @param numberOfFlings
+ * @throws Exception
+ */
+ public void scrollThroughSettings(int numberOfFlings) throws Exception {
+ UiObject2 settingsList = loadAllSettings();
+ int count = 0;
+ while (count <= numberOfFlings && settingsList.fling(Direction.DOWN)) {
+ count++;
+ }
+ }
+
+ /**
+ * Move to top of settings page
+ * @throws Exception
+ */
+ public void flingSettingsToStart() throws Exception {
+ UiObject2 settingsList = loadAllSettings();
+ while (settingsList.fling(Direction.UP));
+ }
+
+ /**
+ * Launch specific settings page
+ * @param ctx
+ * @param pageName
+ * @throws Exception
+ */
+ public static void launchSettingsPage(Context ctx, String pageName) throws Exception {
+ Intent intent = new Intent(pageName);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ctx.startActivity(intent);
+ Thread.sleep(TIMEOUT * 2);
+ }
+
+ /**
+ * Scroll vertically up or down
+ * @param isUp
+ */
+ public void scrollVert(boolean isUp) {
+ int w = mDevice.getDisplayWidth();
+ int h = mDevice.getDisplayHeight();
+ mDevice.swipe(w / 2, h / 2, w / 2, isUp ? h : 0, 2);
+ }
+
+ /**
+ * On N, the settingsDashboard is initially collapsed, and the user can see the "See all"
+ * element. On hitting "See all", the same settings dashboard element is now scrollable. For
+ * pre-N, the settings Dashboard is always scrollable, hence the check in the while loop. All
+ * this method does is expand the Settings list if needed, before returning the element.
+ */
+ public UiObject2 loadAllSettings() throws Exception {
+ UiObject2 settingsDashboard = mDevice.wait(Until.findObject(SETTINGS_DASHBOARD),
+ TIMEOUT * 2);
+ Assert.assertNotNull("Could not find the settings dashboard object.", settingsDashboard);
+ int count = 0;
+ while (!settingsDashboard.isScrollable() && count <= 2) {
+ mDevice.wait(Until.findObject(By.text("SEE ALL")), TIMEOUT * 2).click();
+ settingsDashboard = mDevice.wait(Until.findObject(SETTINGS_DASHBOARD),
+ TIMEOUT * 2);
+ count++;
+ }
+ return settingsDashboard;
+ }
+
+ /**
+ * Performs click action on a setting when setting name is provided as exact string
+ * @param settingName
+ * @throws InterruptedException
+ */
+ public void clickSetting(String settingName) throws InterruptedException {
+ int count = 5;
+ while (count > 0 && mDevice.wait(Until.findObject(By.text(settingName)), TIMEOUT) == null) {
+ scrollVert(false);
+ count--;
+ }
+ mDevice.wait(Until.findObject(By.text(settingName)), TIMEOUT).click();
+ Thread.sleep(TIMEOUT);
+ }
+
+ /**
+ * Performs click action on a setting when setting has been found
+ * @param name
+ * @throws InterruptedException,UiObjectNotFoundException
+ */
+ public boolean selectSettingFor(String settingName)
+ throws InterruptedException, UiObjectNotFoundException {
+ UiScrollable settingsList = new UiScrollable(
+ new UiSelector().resourceId("android:id/content"));
+ UiObject appSettings = settingsList.getChildByText(LIST_ITEM_VALUE, settingName);
+ if (appSettings != null) {
+ return appSettings.click();
+ }
+ return false;
+ }
+
+ /**
+ * Performs click action on a setting when setting name is provided as pattern
+ *
+ * @param settingName
+ * @throws InterruptedException
+ */
+ public void clickSetting(Pattern settingName) throws InterruptedException {
+ mDevice.wait(Until.findObject(By.text(settingName)), TIMEOUT).click();
+ Thread.sleep(400);
+ }
+
+ /**
+ * Gets string value of a setting
+ * @param type
+ * @param sName
+ * @return
+ */
+ public String getStringSetting(SettingsType type, String sName) {
+ switch (type) {
+ case SYSTEM:
+ return Settings.System.getString(mResolver, sName);
+ case GLOBAL:
+ return Settings.Global.getString(mResolver, sName);
+ case SECURE:
+ return Settings.Secure.getString(mResolver, sName);
+ }
+ return "";
+ }
+
+ /**
+ * Get int value of a setting
+ * @param type
+ * @param sName
+ * @return
+ * @throws SettingNotFoundException
+ */
+ public int getIntSetting(SettingsType type, String sName) throws SettingNotFoundException {
+ switch (type) {
+ case SYSTEM:
+ return Settings.System.getInt(mResolver, sName);
+ case GLOBAL:
+ return Settings.Global.getInt(mResolver, sName);
+ case SECURE:
+ return Settings.Secure.getInt(mResolver, sName);
+ }
+ return Integer.MIN_VALUE;
+ }
+
+ /**
+ * Set string value of a setting
+ * @param type
+ * @param sName
+ * @param value
+ */
+ public void setStringSetting(SettingsType type, String sName, String value)
+ throws InterruptedException {
+ switch (type) {
+ case SYSTEM:
+ Settings.System.putString(mResolver, sName, value);
+ break;
+ case GLOBAL:
+ Settings.Global.putString(mResolver, sName, value);
+ break;
+ case SECURE:
+ Settings.Secure.putString(mResolver, sName, value);
+ break;
+ }
+ Thread.sleep(TIMEOUT);
+ }
+
+ /**
+ * Sets int value of a setting
+ * @param type
+ * @param sName
+ * @param value
+ */
+ public void setIntSetting(SettingsType type, String sName, int value)
+ throws InterruptedException {
+ switch (type) {
+ case SYSTEM:
+ Settings.System.putInt(mResolver, sName, value);
+ break;
+ case GLOBAL:
+ Settings.Global.putInt(mResolver, sName, value);
+ break;
+ case SECURE:
+ Settings.Secure.putInt(mResolver, sName, value);
+ break;
+ }
+ Thread.sleep(TIMEOUT);
+ }
+
+ /**
+ * Toggles setting and verifies the action, when setting name is passed as string
+ * @param type
+ * @param settingAction
+ * @param settingName
+ * @param internalName
+ * @return
+ * @throws Exception
+ */
+ public boolean verifyToggleSetting(SettingsType type, String settingAction,
+ String settingName, String internalName) throws Exception {
+ return verifyToggleSetting(
+ type, settingAction, Pattern.compile(settingName), internalName, true);
+ }
+
+ /**
+ * Toggles setting and verifies the action, when setting name is passed as pattern
+ * @param type
+ * @param settingAction
+ * @param settingName
+ * @param internalName
+ * @return
+ * @throws Exception
+ */
+ public boolean verifyToggleSetting(SettingsType type, String settingAction,
+ Pattern settingName, String internalName) throws Exception {
+ return verifyToggleSetting(type, settingAction, settingName, internalName, true);
+ }
+
+ /**
+ * Toggles setting and verifies the action, when setting name is passed as string
+ * and settings page needs to be launched or not
+ * @param type
+ * @param settingAction
+ * @param settingName
+ * @param internalName
+ * @param doLaunch
+ * @return
+ * @throws Exception
+ */
+ public boolean verifyToggleSetting(SettingsType type, String settingAction,
+ String settingName, String internalName, boolean doLaunch) throws Exception {
+ return verifyToggleSetting(
+ type, settingAction, Pattern.compile(settingName), internalName, doLaunch);
+ }
+
+ /**
+ * Toggles setting and verifies the action
+ * @param type
+ * @param settingAction
+ * @param settingName
+ * @param internalName
+ * @param doLaunch
+ * @return
+ * @throws Exception
+ */
+ public boolean verifyToggleSetting(SettingsType type, String settingAction,
+ Pattern settingName, String internalName, boolean doLaunch) throws Exception {
+ String onSettingBaseVal = getStringSetting(type, internalName);
+ if (onSettingBaseVal == null) {
+ // Per bug b/35717943 default for charging sounds is ON
+ // So if null, the value should be set to 1.
+ if (settingName.matcher("Charging sounds").matches()) {
+ onSettingBaseVal = "1";
+ }
+ else {
+ onSettingBaseVal = "0";
+ }
+ }
+ int onSetting = Integer.parseInt(onSettingBaseVal);
+ Log.d(TAG, "On Setting value is : " + onSetting);
+ if (doLaunch) {
+ launchSettingsPage(mContext, settingAction);
+ }
+ clickSetting(settingName);
+ Log.d(TAG, "Clicked setting : " + settingName);
+ Thread.sleep(5000);
+ String changedSetting = getStringSetting(type, internalName);
+ Log.d(TAG, "Changed Setting value is : " + changedSetting);
+ if (changedSetting == null) {
+ Log.d(TAG, "Changed Setting value is : NULL");
+ changedSetting = "0";
+ }
+ return (1 - onSetting) == Integer.parseInt(changedSetting);
+ }
+
+ /**
+ * @param type
+ * @param settingAction
+ * @param baseName
+ * @param settingName
+ * @param internalName
+ * @param testVal
+ * @return
+ * @throws Exception
+ */
+ public boolean verifyRadioSetting(SettingsType type, String settingAction,
+ String baseName, String settingName,
+ String internalName, String testVal) throws Exception {
+ if (baseName != null)
+ clickSetting(baseName);
+ clickSetting(settingName);
+ Thread.sleep(500);
+ return getStringSetting(type, internalName).equals(testVal);
+ }
+
+ public void toggleWiFiOnOffAndVerify(boolean verifyOn, boolean isQuickSettings)
+ throws Exception {
+ String switchText = (verifyOn ? "OFF" : "ON");
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ wifiManager.setWifiEnabled(!verifyOn);
+ Thread.sleep(TIMEOUT * 3);
+ if (isQuickSettings) {
+ launchAndClickSettings(isQuickSettings, null, By.descContains(WIFI)
+ .clazz(Switch.class));
+ } else {
+ launchAndClickSettings(isQuickSettings, Settings.ACTION_WIFI_SETTINGS,
+ By.res(SETTINGS_PACKAGE, SWITCH_WIDGET).text(switchText));
+ }
+ Thread.sleep(TIMEOUT * 3);
+ String wifiValue = Settings.Global.getString(mResolver, Settings.Global.WIFI_ON);
+ if (verifyOn) {
+ Assert.assertFalse(wifiValue == "0");
+ } else {
+ Assert.assertEquals("0", wifiValue);
+ }
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ }
+
+ public void toggleBTOnOffAndVerify(boolean verifyOn, boolean isQuickSettings)
+ throws Exception {
+ String switchText = (verifyOn ? "OFF" : "ON");
+ BluetoothAdapter bluetoothAdapter = ((BluetoothManager) mContext
+ .getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
+ boolean isEnabled = bluetoothAdapter.isEnabled();
+ boolean success = (verifyOn ? bluetoothAdapter.disable() : bluetoothAdapter.enable());
+ Thread.sleep(TIMEOUT * 3);
+ if (isQuickSettings) {
+ launchAndClickSettings(isQuickSettings, null,
+ By.descContains(BLUETOOTH).clazz(Switch.class));
+ } else {
+ launchAndClickSettings(isQuickSettings, Settings.ACTION_BLUETOOTH_SETTINGS,
+ By.res(SETTINGS_PACKAGE, SWITCH_WIDGET).text(switchText));
+ }
+ Thread.sleep(TIMEOUT * 3);
+ String bluetoothValue = Settings.Global.getString(
+ mResolver,
+ Settings.Global.BLUETOOTH_ON);
+ Assert.assertEquals((verifyOn ? "1" : "0"), bluetoothValue);
+ if (isEnabled) {
+ bluetoothAdapter.enable();
+ } else {
+ bluetoothAdapter.disable();
+ }
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ }
+
+ public void toggleAirplaneModeOnOrOffAndVerify(boolean verifyOn, boolean isQuickSettings)
+ throws Exception {
+ String settingValToPut = (verifyOn ? "0" : "1");
+ Settings.Global.putString(mResolver, Settings.Global.AIRPLANE_MODE_ON, settingValToPut);
+ if (isQuickSettings) {
+ launchAndClickSettings(isQuickSettings, null, By.descContains(AIRPLANE));
+ } else {
+ launchAndClickSettings(isQuickSettings, Settings.ACTION_WIRELESS_SETTINGS,
+ By.text(AIRPLANE));
+ }
+ Thread.sleep(TIMEOUT * 3);
+ String airplaneModeValue = Settings.Global
+ .getString(mResolver,
+ Settings.Global.AIRPLANE_MODE_ON);
+ Assert.assertEquals((verifyOn ? "1" : "0"), airplaneModeValue);
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ }
+
+ public void toggleLocationSettingsOnOrOffAndVerify(boolean verifyOn, boolean isQuickSettings)
+ throws Exception {
+ // Set location flag
+ int settingValToPut = (verifyOn ? Settings.Secure.LOCATION_MODE_OFF
+ : Settings.Secure.LOCATION_MODE_SENSORS_ONLY);
+ Settings.Secure.putInt(mResolver, Settings.Secure.LOCATION_MODE, settingValToPut);
+ // Load location settings
+ if (isQuickSettings) {
+ launchAndClickSettings(isQuickSettings, null, By.descContains(LOCATION));
+ } else {
+ launchAndClickSettings(isQuickSettings, Settings.ACTION_LOCATION_SOURCE_SETTINGS,
+ By.res(SETTINGS_PACKAGE, SWITCH_WIDGET));
+ }
+ Thread.sleep(TIMEOUT * 3);
+ // Verify change in setting
+ int locationEnabled = Settings.Secure.getInt(mResolver,
+ Settings.Secure.LOCATION_MODE);
+ if (verifyOn) {
+ Assert.assertFalse("Location not enabled correctly", locationEnabled == 0);
+ } else {
+ Assert.assertEquals("Location not disabled correctly", 0, locationEnabled);
+ }
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ }
+
+ public void launchAndClickSettings(boolean isQuickSettings, String settingsPage,
+ BySelector bySelector) throws Exception {
+ if (isQuickSettings) {
+ launchQuickSettingsAndWait();
+ UiObject2 qsTile = mDevice.wait(Until.findObject(bySelector), TIMEOUT * 3);
+ qsTile.findObject(By.clazz("android.widget.FrameLayout")).click();
+ } else {
+ mActivityHelper.launchIntent(settingsPage);
+ mDevice.wait(Until.findObject(bySelector), TIMEOUT * 3).click();
+ }
+ }
+
+ /**
+ * Verify Quick Setting DND can be toggled DND default value is OFF
+ * @throws Exception
+ */
+ public void toggleQuickSettingDNDAndVerify() throws Exception {
+ try {
+ int onSetting = Settings.Global.getInt(mResolver, ZEN_MODE);
+ launchQuickSettingsAndWait();
+ mDevice.wait(Until.findObject(By.descContains(DND)),
+ TIMEOUT * 3).getChildren().get(0).click();
+ Thread.sleep(TIMEOUT * 3);
+ int changedSetting = Settings.Global.getInt(mResolver, ZEN_MODE);
+ Assert.assertFalse(onSetting == changedSetting);
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ } finally {
+ // change to DND default value
+ int setting = Settings.Global.getInt(mResolver, ZEN_MODE);
+ if (setting > 0) {
+ launchQuickSettingsAndWait();
+ mDevice.wait(Until.findObject(By.descContains(DND)),
+ TIMEOUT * 3).getChildren().get(0).click();
+ Thread.sleep(TIMEOUT * 3);
+ }
+ }
+ }
+
+ public void toggleQuickSettingFlashLightAndVerify() throws Exception {
+ String lightOn = "On";
+ String lightOff = "Off";
+ boolean verifyOn = false;
+ launchQuickSettingsAndWait();
+ UiObject2 flashLight = mDevice.wait(
+ Until.findObject(By.desc(FLASHLIGHT)),
+ TIMEOUT * 3);
+ if (flashLight != null && flashLight.getText().equals(lightOn)) {
+ verifyOn = true;
+ }
+ mDevice.wait(Until.findObject(By.desc(FLASHLIGHT)),
+ TIMEOUT * 3).click();
+ Thread.sleep(TIMEOUT * 3);
+ flashLight = mDevice.wait(
+ Until.findObject(By.desc(FLASHLIGHT)),
+ TIMEOUT * 3);
+ if (flashLight != null) {
+ String txt = flashLight.getText();
+ if (verifyOn) {
+ Assert.assertTrue(txt.equals(lightOff));
+ } else {
+ Assert.assertTrue(txt.equals(lightOn));
+ mDevice.wait(Until.findObject(By.textContains(FLASHLIGHT)),
+ TIMEOUT * 3).click();
+ }
+ }
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ }
+
+ public void toggleQuickSettingOrientationAndVerify() throws Exception {
+ launchQuickSettingsAndWait();
+ mDevice.wait(Until.findObject(By.descContains(AUTO_ROTATE_SCREEN)),
+ TIMEOUT * 3).click();
+ Thread.sleep(TIMEOUT * 3);
+ String rotation = Settings.System.getString(mResolver,
+ Settings.System.ACCELEROMETER_ROTATION);
+ Assert.assertEquals("1", rotation);
+ mDevice.setOrientationNatural();
+ mDevice.pressHome();
+ Thread.sleep(TIMEOUT * 3);
+ }
+
+ public void launchQuickSettingsAndWait() throws Exception {
+ mDevice.openQuickSettings();
+ Thread.sleep(TIMEOUT * 2);
+ }
+
+ public void launchSettingsPageByComponentName(Context ctx, String name) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ ComponentName settingComponent = new ComponentName(SETTINGS_PACKAGE,
+ String.format("%s.%s$%s", SETTINGS_PACKAGE, SETTINGS_APP, name));
+ intent.setComponent(settingComponent);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ctx.startActivity(intent);
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/sysui-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/sysui-helper/Android.mk
index 9e797fb..2adcbcd 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/sysui-helper/Android.mk
@@ -16,13 +16,14 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := sysui-helper
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ activity-helper \
+ commands-helper \
+ device-helper \
+ legacy-android-test
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/sysui-helper/src/android/system/helpers/HotseatHelper.java b/libraries/system-helpers/sysui-helper/src/android/system/helpers/HotseatHelper.java
new file mode 100644
index 0000000..59e44da
--- /dev/null
+++ b/libraries/system-helpers/sysui-helper/src/android/system/helpers/HotseatHelper.java
@@ -0,0 +1,66 @@
+/*
+ * 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.system.helpers;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+
+import junit.framework.Assert;
+
+/**
+ * Implement common helper methods for Hotseat.
+ */
+public class HotseatHelper {
+ private static final int TIMEOUT = 3000;
+ private UiDevice mDevice = null;
+ private PackageManager mPkgManger = null;
+ private Context mContext = null;
+ public static HotseatHelper sInstance = null;
+
+ private HotseatHelper() {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ public static HotseatHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new HotseatHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Launch app from hotseat
+ * @param textAppName
+ * @param appPackage
+ */
+ public void launchAppFromHotseat(String textAppName, String appPackage) {
+ mDevice.pressHome();
+ UiObject2 appOnHotseat = mDevice.findObject(By.clazz("android.widget.TextView")
+ .desc(textAppName));
+ Assert.assertNotNull(textAppName + " app couldn't be found on hotseat", appOnHotseat);
+ appOnHotseat.click();
+ UiObject2 appLoaded = mDevice.wait(Until.findObject(By.pkg(appPackage)), TIMEOUT * 2);
+ Assert.assertNotNull(textAppName + "app did not load on tapping from hotseat", appLoaded);
+ }
+}
\ No newline at end of file
diff --git a/libraries/system-helpers/sysui-helper/src/android/system/helpers/LockscreenHelper.java b/libraries/system-helpers/sysui-helper/src/android/system/helpers/LockscreenHelper.java
new file mode 100644
index 0000000..ece416b
--- /dev/null
+++ b/libraries/system-helpers/sysui-helper/src/android/system/helpers/LockscreenHelper.java
@@ -0,0 +1,214 @@
+/*
+ * 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.system.helpers;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.system.helpers.ActivityHelper;
+
+import junit.framework.Assert;
+
+import java.io.IOException;
+/**
+ * Implement common helper methods for Lockscreen.
+ */
+public class LockscreenHelper {
+ private static final String LOG_TAG = LockscreenHelper.class.getSimpleName();
+ public static final int SHORT_TIMEOUT = 200;
+ public static final int LONG_TIMEOUT = 2000;
+ public static final String EDIT_TEXT_CLASS_NAME = "android.widget.EditText";
+ public static final String CAMERA2_PACKAGE = "com.android.camera2";
+ public static final String CAMERA_PACKAGE = "com.google.android.GoogleCamera";
+ public static final String MODE_PIN = "PIN";
+ private static final int SWIPE_MARGIN = 5;
+ private static final int DEFAULT_FLING_STEPS = 5;
+ private static final int DEFAULT_SCROLL_STEPS = 15;
+
+ private static final String SET_PIN_COMMAND = "locksettings set-pin %s";
+ private static final String CLEAR_COMMAND = "locksettings clear --old %s";
+
+ private static LockscreenHelper sInstance = null;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+ private final ActivityHelper mActivityHelper;
+ private final CommandsHelper mCommandsHelper;
+ private final DeviceHelper mDeviceHelper;
+ private boolean mIsRyuDevice = false;
+
+ public LockscreenHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mActivityHelper = ActivityHelper.getInstance();
+ mCommandsHelper = CommandsHelper.getInstance(InstrumentationRegistry.getInstrumentation());
+ mDeviceHelper = DeviceHelper.getInstance();
+ mIsRyuDevice = mDeviceHelper.isRyuDevice();
+
+ }
+
+ public static LockscreenHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new LockscreenHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Launch Camera on LockScreen
+ * @return true/false
+ */
+ public boolean launchCameraOnLockScreen() {
+ String CameraPackage = mIsRyuDevice ? CAMERA2_PACKAGE : CAMERA_PACKAGE;
+ int w = mDevice.getDisplayWidth();
+ int h = mDevice.getDisplayHeight();
+ // Load camera on LockScreen and take a photo
+ mDevice.drag((w - 25), (h - 25), (int) (w * 0.5), (int) (w * 0.5), 40);
+ mDevice.waitForIdle();
+ return mDevice.wait(Until.hasObject(
+ By.res(CameraPackage, "activity_root_view")),
+ LONG_TIMEOUT * 2);
+ }
+
+ /**
+ * Sets the screen lock pin or password
+ * @param pwd text of Password or Pin for lockscreen
+ * @param mode indicate if its password or PIN
+ * @throws InterruptedException
+ */
+ public void setScreenLock(String pwd, String mode, boolean mIsNexusDevice) throws InterruptedException {
+ navigateToScreenLock();
+ mDevice.wait(Until.findObject(By.text(mode)), LONG_TIMEOUT * 2).click();
+ // set up Secure start-up page
+ if (!mIsNexusDevice) {
+ mDevice.wait(Until.findObject(By.text("No thanks")), LONG_TIMEOUT).click();
+ }
+ UiObject2 pinField = mDevice.wait(Until.findObject(By.clazz(EDIT_TEXT_CLASS_NAME)),
+ LONG_TIMEOUT);
+ pinField.setText(pwd);
+ // enter and verify password
+ mDevice.pressEnter();
+ pinField.setText(pwd);
+ mDevice.pressEnter();
+ mDevice.wait(Until.findObject(By.text("DONE")), LONG_TIMEOUT).click();
+ }
+
+ /**
+ * check if Emergency Call page exists
+ * @throws InterruptedException
+ */
+ public void checkEmergencyCallOnLockScreen() throws InterruptedException {
+ mDevice.pressMenu();
+ mDevice.wait(Until.findObject(By.text("EMERGENCY")), LONG_TIMEOUT).click();
+ Thread.sleep(LONG_TIMEOUT);
+ UiObject2 dialButton = mDevice.wait(Until.findObject(By.desc("dial")),
+ LONG_TIMEOUT);
+ Assert.assertNotNull("Can't reach emergency call page", dialButton);
+ mDevice.pressBack();
+ Thread.sleep(LONG_TIMEOUT);
+ }
+
+ /**
+ * remove Screen Lock
+ * @throws InterruptedException
+ */
+ public void removeScreenLock(String pwd)
+ throws InterruptedException {
+ navigateToScreenLock();
+ UiObject2 pinField = mDevice.wait(Until.findObject(By.clazz(EDIT_TEXT_CLASS_NAME)),
+ LONG_TIMEOUT);
+ pinField.setText(pwd);
+ mDevice.pressEnter();
+ mDevice.wait(Until.findObject(By.text("Swipe")), LONG_TIMEOUT).click();
+ mDevice.waitForIdle();
+ mDevice.wait(Until.findObject(By.text("YES, REMOVE")), LONG_TIMEOUT).click();
+ }
+
+ /**
+ * unlock screen
+ * @throws InterruptedException, IOException
+ */
+ public void unlockScreen(String pwd)
+ throws InterruptedException, IOException {
+ String command = String.format(" %s %s %s", "input", "keyevent", "82");
+ mDevice.executeShellCommand(command);
+ Thread.sleep(SHORT_TIMEOUT);
+ Thread.sleep(SHORT_TIMEOUT);
+ // enter password to unlock screen
+ command = String.format(" %s %s %s", "input", "text", pwd);
+ mDevice.executeShellCommand(command);
+ mDevice.waitForIdle();
+ Thread.sleep(SHORT_TIMEOUT);
+ mDevice.pressEnter();
+ }
+
+ /**
+ * navigate to screen lock setting page
+ * @throws InterruptedException
+ */
+ public void navigateToScreenLock()
+ throws InterruptedException {
+ mActivityHelper.launchIntent(Settings.ACTION_SECURITY_SETTINGS);
+ mDevice.wait(Until.findObject(By.text("Screen lock")), LONG_TIMEOUT).click();
+ }
+
+ /**
+ * check if Lock Screen is enabled
+ */
+ public boolean isLockScreenEnabled() {
+ KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ return km.isKeyguardSecure();
+ }
+
+ /**
+ * Sets a screen lock via shell.
+ */
+ public void setScreenLockViaShell(String pwd, String mode) throws Exception {
+ switch (mode) {
+ case MODE_PIN:
+ mCommandsHelper.executeShellCommand(String.format(SET_PIN_COMMAND, pwd));
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported mode: " + mode);
+ }
+ }
+
+ /**
+ * Removes the screen lock via shell.
+ */
+ public void removeScreenLockViaShell(String pwd) throws Exception {
+ mCommandsHelper.executeShellCommand(String.format(CLEAR_COMMAND, pwd));
+ }
+
+ /**
+ * swipe up to unlock the screen
+ */
+ public void unlockScreenSwipeUp() throws Exception {
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ mDevice.swipe(mDevice.getDisplayWidth() / 2,
+ mDevice.getDisplayHeight() - SWIPE_MARGIN,
+ mDevice.getDisplayWidth() / 2,
+ SWIPE_MARGIN,
+ DEFAULT_SCROLL_STEPS);
+ mDevice.waitForIdle();
+ }
+}
\ No newline at end of file
diff --git a/libraries/system-helpers/sysui-helper/src/android/system/helpers/NotificationHelper.java b/libraries/system-helpers/sysui-helper/src/android/system/helpers/NotificationHelper.java
new file mode 100644
index 0000000..7554b60
--- /dev/null
+++ b/libraries/system-helpers/sysui-helper/src/android/system/helpers/NotificationHelper.java
@@ -0,0 +1,171 @@
+/*
+ * 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.system.helpers;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+import android.view.inputmethod.InputMethodManager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Toast;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implement common helper methods for Notification.
+ */
+public class NotificationHelper {
+ private static final String LOG_TAG = NotificationHelper.class.getSimpleName();
+ public static final int SHORT_TIMEOUT = 200;
+ public static final int LONG_TIMEOUT = 2000;
+ private static NotificationHelper sInstance = null;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+
+ public NotificationHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public static NotificationHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new NotificationHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * check if notification exists
+ * @param id notification id
+ * @param mNotificationManager NotificationManager
+ * @return true/false
+ * @throws Exception
+ */
+ public boolean checkNotificationExistence(int id, NotificationManager mNotificationManager)
+ throws Exception {
+ boolean isFound = false;
+ for (int tries = 3; tries-- > 0;) {
+ isFound = false;
+ StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getId() == id) {
+ isFound = true;
+ break;
+ }
+ }
+ if (isFound) {
+ break;
+ }
+ Thread.sleep(SHORT_TIMEOUT);
+ }
+ Log.i(LOG_TAG, "checkNotificationExistence..." + isFound);
+ return isFound;
+ }
+
+ /**
+ * send out a group of notifications
+ * @param lists notification list for a group of notifications which includes two child
+ * notifications and one summary notification
+ * @param groupKey the group key of group notification
+ * @param mNotificationManager NotificationManager
+ * @throws Exception
+ */
+ public void sendBundlingNotifications(List<Integer> lists, String groupKey,
+ NotificationManager mNotificationManager) throws Exception {
+ Notification childNotification = new Notification.Builder(mContext)
+ .setContentTitle(lists.get(1).toString())
+ .setSmallIcon(android.R.drawable.stat_notify_chat)
+ .setContentText("test1")
+ .setWhen(System.currentTimeMillis())
+ .setGroup(groupKey)
+ .build();
+ mNotificationManager.notify(lists.get(1),
+ childNotification);
+ childNotification = new Notification.Builder(mContext)
+ .setContentTitle(lists.get(2).toString())
+ .setContentText("test2")
+ .setSmallIcon(android.R.drawable.stat_notify_chat)
+ .setWhen(System.currentTimeMillis())
+ .setGroup(groupKey)
+ .build();
+ mNotificationManager.notify(lists.get(2),
+ childNotification);
+ Notification notification = new Notification.Builder(mContext)
+ .setContentTitle(lists.get(0).toString())
+ .setSubText(groupKey)
+ .setSmallIcon(android.R.drawable.stat_notify_chat)
+ .setGroup(groupKey)
+ .setGroupSummary(true)
+ .build();
+ mNotificationManager.notify(lists.get(0),
+ notification);
+ }
+
+ /**
+ * send out a notification with inline reply
+ * @param notificationId An identifier for this notification
+ * @param title notification title
+ * @param inLineReply inline reply text
+ * @param mNotificationManager NotificationManager
+ */
+ public void sendNotificationsWithInLineReply(int notificationId, String title,
+ String inLineReply,PendingIntent pendingIntent, NotificationManager mNotificationManager) {
+ Notification.Action action = new Notification.Action.Builder(
+ android.R.drawable.stat_notify_chat, "Reply",
+ pendingIntent).addRemoteInput(new RemoteInput.Builder(inLineReply)
+ .setLabel("Quick reply").build())
+ .build();
+ Notification.Builder n = new Notification.Builder(mContext)
+ .setContentTitle(Integer.toString(notificationId))
+ .setContentText(title)
+ .setWhen(System.currentTimeMillis())
+ .setSmallIcon(android.R.drawable.stat_notify_chat)
+ .addAction(action)
+ .setDefaults(Notification.DEFAULT_VIBRATE);
+ mNotificationManager.notify(notificationId, n.build());
+ }
+
+ /**
+ * dismiss notification
+ * @param mNotificationManager NotificationManager
+ */
+ public void dismissNotifications(NotificationManager mNotificationManager){
+ mNotificationManager.cancelAll();
+ }
+
+ /**
+ * open notification shade
+ */
+ public void openNotification(){
+ mDevice.openNotification();
+ }
+}
\ No newline at end of file
diff --git a/libraries/system-helpers/sysui-helper/src/android/system/helpers/OverviewHelper.java b/libraries/system-helpers/sysui-helper/src/android/system/helpers/OverviewHelper.java
new file mode 100644
index 0000000..39feb94
--- /dev/null
+++ b/libraries/system-helpers/sysui-helper/src/android/system/helpers/OverviewHelper.java
@@ -0,0 +1,231 @@
+/*
+ * 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.system.helpers;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.system.helpers.ActivityHelper;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implement common helper methods for Overview.
+ */
+public class OverviewHelper {
+
+ private static final String TAG = OverviewHelper.class.getSimpleName();
+ private static final int TIMEOUT = 3000;
+ private static final String RECENTS = "com.android.systemui:id/recents_view";
+
+ private UiDevice mDevice = null;
+ private Instrumentation mInstrumentation = null;
+ private ActivityHelper mActHelper = null;
+ private final CommandsHelper mCommandsHelper;
+ public static OverviewHelper sInstance = null;
+
+ public OverviewHelper() {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActHelper = ActivityHelper.getInstance();
+ mCommandsHelper = CommandsHelper.getInstance(mInstrumentation);
+ }
+
+ public static OverviewHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new OverviewHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Navigates to the recents screen
+ * @returns recents object
+ * @throws UiObjectNotFoundException
+ */
+ public UiObject2 navigateToRecents() throws Exception {
+ mDevice.pressRecentApps();
+ mDevice.waitForIdle();
+ return mDevice.wait(Until.findObject(By.res(RECENTS)), TIMEOUT);
+ }
+
+ /**
+ * Populates recents by launching six apps
+ * @throws InterruptedException
+ */
+ public void populateRecents() throws InterruptedException {
+ // We launch six apps, since five is the maximum number
+ // of apps under Recents
+ String[] appPackages = {"com.google.android.gm",
+ "com.google.android.deskclock", "com.android.settings",
+ "com.google.android.youtube", "com.google.android.contacts",
+ "com.google.android.apps.maps"};
+ for (String appPackage : appPackages) {
+ mActHelper.launchPackage(appPackage);
+ }
+ }
+
+ public ArrayList<String> populateManyRecentApps() throws IOException {
+ PackageManager pm = mInstrumentation.getContext().getPackageManager();
+ List<PackageInfo> packages = pm.getInstalledPackages(0);
+ ArrayList<String> launchedPackages = new ArrayList<>();
+ for (PackageInfo pkg : packages) {
+ if (pkg.packageName.equals(mInstrumentation.getTargetContext().getPackageName())) {
+ continue;
+ }
+ Intent intent = pm.getLaunchIntentForPackage(pkg.packageName);
+ if (intent == null) {
+ continue;
+ }
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mInstrumentation.getTargetContext().startActivity(intent);
+ } catch (SecurityException e) {
+ Log.i(TAG, "Failed to start package " + pkg.packageName + ", exception: " + e);
+ }
+
+ // Don't overload the system
+ SystemClock.sleep(500);
+ launchedPackages.add(pkg.packageName);
+ }
+
+ // Give the apps some time to finish starting. Some apps start another activity while
+ // starting, and we don't want to happen when we are testing stuff.
+ SystemClock.sleep(3000);
+
+ // Close any crash dialogs
+ while (mDevice.hasObject(By.textContains("has stopped"))) {
+ UiObject2 crashDialog = mDevice.findObject(By.text("Close"));
+ if (crashDialog != null) {
+ crashDialog.clickAndWait(Until.newWindow(), 2000);
+ }
+ }
+ return launchedPackages;
+ }
+
+ public void forceStopPackages(ArrayList<String> packages) {
+ for (String pkg : packages) {
+ mCommandsHelper.executeShellCommand("am force-stop " + pkg);
+ }
+ }
+
+ /**
+ * Scrolls through given recents object to the top
+ * @param recents Recents object
+ */
+ public void scrollToTopOfRecents(UiObject2 recents) {
+ Rect r = recents.getVisibleBounds();
+ // decide the top & bottom edges for scroll gesture
+ int top = r.top + r.height() / 4; // top edge = top + 25% height
+ int bottom = r.bottom - 200; // bottom edge = bottom & shift up 200px
+ mDevice.swipe(r.width() / 2, top, r.width() / 2, bottom, 5);
+ mDevice.waitForIdle();
+ }
+
+ /**
+ * Docks an app to the top half of the multiwindow screen
+ * @param appPackageName name of app package
+ * @param appName Name of app to verify on screen
+ * @throws UiObjectNotFoundException, InterruptedException
+ */
+ public void dockAppToTopMultiwindowSlot(String appPackageName, String appName)
+ throws Exception {
+ mDevice.pressRecentApps();
+ mDevice.waitForIdle();
+ UiObject2 recentsView = mDevice.wait(Until.findObject
+ (By.res("com.android.systemui:id/recents_view")),TIMEOUT);
+ // Check if recents isn't already empty, if not, clear it.
+ if (!mDevice.wait(Until.hasObject(By.text("No recent items")),TIMEOUT)) {
+ scrollToTopOfRecents(recentsView);
+ // click clear all
+ UiObject2 clearAll = mDevice.wait(Until.findObject(By.text("CLEAR ALL")),TIMEOUT);
+ if (!clearAll.equals(null)) {
+ clearAll.click();
+ }
+ Thread.sleep(TIMEOUT);
+ }
+ // Open app
+ mActHelper.launchPackage(appPackageName);
+ // Go to overview
+ mDevice.pressRecentApps();
+ mDevice.waitForIdle();
+ // Long press on app
+ UiObject2 appObject = mDevice.wait(Until.findObject
+ (By.desc(appName)),TIMEOUT);
+ int yCoordinate = mDevice.getDisplayHeight() / 12;
+ int xCoordinate = mDevice.getDisplayWidth() / 2;
+ // Drag and drop the app object to the multiwindow area
+ appObject.drag(new Point(xCoordinate, yCoordinate), 1000);
+ // Adding a sleep to allow the drag and drop animation to finish.
+ Thread.sleep(TIMEOUT);
+ mDevice.click(mDevice.getDisplayHeight() / 4, mDevice.getDisplayWidth() / 2);
+ Assert.assertTrue("App not correctly docked to top multiwindow slot",
+ mDevice.wait(Until.hasObject(By.pkg(appPackageName)
+ .res("android:id/content")), TIMEOUT));
+ }
+
+ /**
+ * Docks two apps, one to the each half of the multiwindow screen
+ * @param topAppPackageName name of app package for top half
+ * @param topAppName Name of top app to verify on screen
+ * @param bottomAppPackageName name of app package for bottom half
+ * @throws UiObjectNotFoundException, InterruptedException
+ */
+ public void dockAppsToBothMultiwindowAreas(String topAppPackageName,
+ String topAppName, String bottomAppPackageName) throws Exception {
+ dockAppToTopMultiwindowSlot(topAppPackageName, topAppName);
+ mDevice.pressHome();
+ mDevice.waitForIdle();
+ // After docking the top app, simply launching another app
+ // will launch it in the bottom half in docked mode. This
+ // results in two apps being docked to multiwindow.
+ mActHelper.launchPackage(bottomAppPackageName);
+ }
+
+ /**
+ * Undocks apps from multiwindow. Only the package for the upper app is needed.
+ * @param topAppPackageName name of app package for top half
+ * @throws UiObjectNotFoundException, InterruptedException
+ */
+ public void undockAppFromMultiwindow(String topAppPackageName) throws Exception {
+ mDevice.click(mDevice.getDisplayHeight() / 4, mDevice.getDisplayWidth() / 2);
+ UiObject2 appArea = mDevice.wait(Until.findObject(By.pkg(topAppPackageName)
+ .res("android:id/content")), TIMEOUT);
+ Rect appBounds = appArea.getVisibleBounds();
+ int xCoordinate = mDevice.getDisplayWidth() / 2;
+ mDevice.drag(xCoordinate, appBounds.bottom, xCoordinate,
+ mDevice.getDisplayHeight() - 120, 4);
+ // Adding a sleep to allow the drag and drop animation to finish.
+ Thread.sleep(TIMEOUT);
+ }
+}
\ No newline at end of file
diff --git a/libraries/system-helpers/sysui-helper/src/android/system/helpers/QuickSettingsHelper.java b/libraries/system-helpers/sysui-helper/src/android/system/helpers/QuickSettingsHelper.java
new file mode 100644
index 0000000..4955cd3
--- /dev/null
+++ b/libraries/system-helpers/sysui-helper/src/android/system/helpers/QuickSettingsHelper.java
@@ -0,0 +1,150 @@
+/*
+ * 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.system.helpers;
+
+import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.graphics.Point;
+import android.provider.Settings;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import org.junit.Assert;
+
+/**
+ * Implement common helper methods for Quick settings.
+ */
+public class QuickSettingsHelper {
+
+ private UiDevice mDevice = null;
+ private ContentResolver mResolver;
+ private Instrumentation mInstrumentation;
+ private static final int LONG_TIMEOUT = 2000;
+ private static final int SHORT_TIMEOUT = 500;
+
+ public QuickSettingsHelper(UiDevice device, Instrumentation inst, ContentResolver resolver) {
+ this.mDevice = device;
+ mInstrumentation = inst;
+ mResolver = resolver;
+ }
+
+ public enum QuickSettingDefaultTiles {
+ WIFI("Wi-Fi"), SIM("Mobile data"), DND("Do not disturb"), FLASHLIGHT("Flashlight"), SCREEN(
+ "Auto-rotate screen"), BLUETOOTH("Bluetooth"), AIRPLANE("Airplane mode"),
+ BRIGHTNESS("Display brightness");
+
+ private final String name;
+
+ private QuickSettingDefaultTiles(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ };
+
+ public enum QuickSettingEditMenuTiles {
+ LOCATION("Location"), HOTSPOT("Hotspot"), INVERTCOLORS("Invert colors"),
+ DATASAVER("Data Saver"), CAST("Cast"), NEARBY("Nearby");
+
+ private final String name;
+
+ private QuickSettingEditMenuTiles(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ };
+
+ public void addQuickSettingTileFromEditMenu(String quickSettingTile,
+ String quickSettingTileToReplace, String quickSettingTileToCheckForInCSV)
+ throws Exception {
+ // Draw down quick settings
+ launchQuickSetting();
+ // Press Edit button
+ UiObject2 quickSettingEdit = mDevice.wait(Until.findObject
+ (By.descContains("Edit")), LONG_TIMEOUT);
+ quickSettingEdit.click();
+ // Scroll down to bottom to see all QS options on Edit
+ swipeDown();
+ // Drag and drop QS item onto existing QS tile to replace it
+ // This is because we need specific coordinates on which to
+ // drop the quick setting tile.
+ UiObject2 quickSettingTileObject = mDevice.wait(Until.findObject
+ (By.descContains(quickSettingTile)), LONG_TIMEOUT);
+ Point destination = mDevice.wait(Until.findObject
+ (By.descContains(quickSettingTileToReplace)), LONG_TIMEOUT)
+ .getVisibleCenter();
+ Assert.assertNotNull(quickSettingTile + " in Edit menu can't be found",
+ quickSettingTileObject);
+ Assert.assertNotNull(quickSettingTileToReplace + " in QS menu can't be found",
+ destination);
+ // Long press the icon, then drag it to the destination slowly.
+ // Without the long press, it ends up scrolling down quick settings.
+ quickSettingTileObject.click(2000);
+ quickSettingTileObject.drag(destination, 1000);
+ // Hit the back button in the QS menu to go back to quick settings.
+ mDevice.wait(Until.findObject(By.descContains("Navigate up")), LONG_TIMEOUT);
+ // Retrieve the quick settings CSV string and verify that the newly
+ // added item is present.
+ String quickSettingsList = Settings.Secure.getString
+ (mInstrumentation.getContext().getContentResolver(),
+ "sysui_qs_tiles");
+ Assert.assertTrue(quickSettingTile + " not present in qs tiles after addition.",
+ quickSettingsList.contains(quickSettingTileToCheckForInCSV));
+ }
+
+ public void setQuickSettingsDefaultTiles() throws Exception {
+ modifyListOfQuickSettingsTiles
+ ("wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location");
+ }
+
+ public void modifyListOfQuickSettingsTiles(String commaSeparatedList) throws Exception {
+ Settings.Secure.putString(mInstrumentation.getContext().getContentResolver(),
+ "sysui_qs_tiles", commaSeparatedList);
+ Thread.sleep(LONG_TIMEOUT);
+ }
+
+ public void launchQuickSetting() throws Exception {
+ mDevice.pressHome();
+ swipeDown();
+ Thread.sleep(LONG_TIMEOUT);
+ swipeDown();
+ }
+
+ public void swipeUp() throws Exception {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight(),
+ mDevice.getDisplayWidth() / 2, 0, 30);
+ Thread.sleep(SHORT_TIMEOUT);
+ }
+
+ public void swipeDown() throws Exception {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2, 0, mDevice.getDisplayWidth() / 2,
+ mDevice.getDisplayHeight() / 2 + 50, 20);
+ Thread.sleep(SHORT_TIMEOUT);
+ }
+
+ public void swipeLeft() {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2, 0,
+ mDevice.getDisplayHeight() / 2, 5);
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/libraries/system-helpers/user-helper/Android.mk
similarity index 76%
copy from libraries/base-app-helpers/Android.mk
copy to libraries/system-helpers/user-helper/Android.mk
index 9e797fb..b0158db 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/libraries/system-helpers/user-helper/Android.mk
@@ -16,13 +16,10 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+LOCAL_MODULE := user-helper
+LOCAL_JAVA_LIBRARIES := ub-uiautomator \
+ android-support-test \
+ commands-helper
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libraries/system-helpers/user-helper/src/android/system/helpers/UserHelper.java b/libraries/system-helpers/user-helper/src/android/system/helpers/UserHelper.java
new file mode 100644
index 0000000..55f2904
--- /dev/null
+++ b/libraries/system-helpers/user-helper/src/android/system/helpers/UserHelper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.system.helpers;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.system.helpers.CommandsHelper;
+import android.util.Log;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import junit.framework.Assert;
+
+/**
+ * Implement common helper methods for user.
+ */
+public class UserHelper {
+ private static final String TAG = UserHelper.class.getSimpleName();
+ private static UserHelper sInstance = null;
+ private Context mContext = null;
+
+ public static final int INVALID_USER_ID = -1;
+
+
+ public UserHelper() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ public static UserHelper getInstance() {
+ if (sInstance == null) {
+ sInstance = new UserHelper();
+ }
+ return sInstance;
+ }
+
+ public UserManager getUserManager() {
+ return (UserManager)mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ /**
+ * Creates a test user
+ * @return id for created secondary user
+ */
+ public int createSecondaryUser(String userName) {
+ // Create user
+ String cmdOut = CommandsHelper.execute("pm create-user " + userName);
+ // Find user id from user-create output
+ // output format : "Success: created user id 10"
+ final Pattern pattern = Pattern.compile("Success: created user id (\\d+)");
+ Matcher matcher = pattern.matcher(cmdOut);
+ int userId = INVALID_USER_ID;
+ if (matcher.find()) {
+ userId = Integer.parseInt(matcher.group(1));
+ Log.i(TAG, String.format("User Name:%s User ID:%d", userName, userId));
+ }
+ return userId;
+ }
+
+ /**
+ * Returns id for first secondary user
+ * @return userid
+ */
+ public int getSecondaryUserId() {
+ String cmdOut = CommandsHelper.execute("pm list users");
+ // Assume that the a user with ID 0 is a primary user. Otherwise secondary users
+ final Pattern USERS_REGEX = Pattern.compile("UserInfo\\{([1-9]\\d*):[\\w\\s]+:(\\d+)\\}");
+ Matcher matcher = USERS_REGEX.matcher(cmdOut);
+ int userId = INVALID_USER_ID;
+ if (matcher.find()) {
+ userId = Integer.parseInt(matcher.group(1)); // 1 = id 2 = flag
+ Log.i(TAG, String.format("The userId is %d", userId));
+ }
+ return userId;
+ }
+
+ public void removeSecondaryUser(int userId) {
+ int prevUserCount = getUserCount();
+ CommandsHelper.execute("pm remove-user " + userId);
+ Assert.assertTrue("User hasn't been removed", getUserCount() == (prevUserCount - 1));
+ }
+
+ public int getUserCount() {
+ return getUserManager().getUserCount();
+ }
+}
\ No newline at end of file
diff --git a/scripts/perf-setup/angler-setup.sh b/scripts/perf-setup/angler-setup.sh
index 7080df7..cd01184 100755
--- a/scripts/perf-setup/angler-setup.sh
+++ b/scripts/perf-setup/angler-setup.sh
@@ -5,24 +5,35 @@
stop thermal-engine
stop perfd
-echo -n 0 > /sys/devices/system/cpu/cpu0/online
-echo -n 0 > /sys/devices/system/cpu/cpu1/online
-echo -n 0 > /sys/devices/system/cpu/cpu2/online
-echo -n 0 > /sys/devices/system/cpu/cpu3/online
+cpubase=/sys/devices/system/cpu
+gov=cpufreq/scaling_governor
-echo -n 1 > /sys/devices/system/cpu/cpu4/online
-echo -n performance > /sys/devices/system/cpu/cpu4/cpufreq/scaling_governor
+cpu=0
+S=960000
+while [ $((cpu < 4)) -eq 1 ]; do
+ echo 1 > $cpubase/cpu${cpu}/online
+ echo userspace > $cpubase/cpu${cpu}/$gov
+ echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_max_freq
+ echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_min_freq
+ echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_setspeed
+ cpu=$(($cpu + 1))
+done
-echo -n 1 > /sys/devices/system/cpu/cpu5/online
-echo -n performance > /sys/devices/system/cpu/cpu5/cpufreq/scaling_governor
-
+echo -n 0 > /sys/devices/system/cpu/cpu4/online
+echo -n 0 > /sys/devices/system/cpu/cpu5/online
echo -n 0 > /sys/devices/system/cpu/cpu6/online
echo -n 0 > /sys/devices/system/cpu/cpu7/online
-echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor
-
echo 0 > /sys/class/kgsl/kgsl-3d0/bus_split
echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on
-
echo 10000 > /sys/class/kgsl/kgsl-3d0/idle_timer
+echo 11863 > /sys/class/devfreq/qcom,gpubw.70/min_freq
+
+echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor
+echo 305000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq
+echo 305000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq
+
+echo 4 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel
+echo 4 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel
+
diff --git a/scripts/perf-setup/bullhead-setup.sh b/scripts/perf-setup/bullhead-setup.sh
new file mode 100644
index 0000000..dfecb7d
--- /dev/null
+++ b/scripts/perf-setup/bullhead-setup.sh
@@ -0,0 +1,36 @@
+if [[ "`id -u`" -ne "0" ]]; then
+ echo "WARNING: running as non-root, proceeding anyways..."
+fi
+
+stop thermal-engine
+stop perfd
+
+cpubase=/sys/devices/system/cpu
+gov=cpufreq/scaling_governor
+
+cpu=0
+S=960000
+while [ $((cpu < 4)) -eq 1 ]; do
+ echo 1 > $cpubase/cpu${cpu}/online
+ echo userspace > $cpubase/cpu${cpu}/$gov
+ echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_max_freq
+ echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_min_freq
+ echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_setspeed
+ cpu=$(($cpu + 1))
+done
+
+echo -n 0 > /sys/devices/system/cpu/cpu4/online
+echo -n 0 > /sys/devices/system/cpu/cpu5/online
+
+echo 0 > /sys/class/kgsl/kgsl-3d0/bus_split
+echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on
+echo 10000 > /sys/class/kgsl/kgsl-3d0/idle_timer
+
+echo 7102 > /sys/class/devfreq/qcom,gpubw.19/min_freq
+
+echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor
+echo 300000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq
+echo 300000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq
+
+echo 4 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel
+echo 4 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel
diff --git a/scripts/perf-setup/dragon-setup.sh b/scripts/perf-setup/dragon-setup.sh
new file mode 100755
index 0000000..7a50a16
--- /dev/null
+++ b/scripts/perf-setup/dragon-setup.sh
@@ -0,0 +1,22 @@
+# performance testing setup script for dragon device
+
+if [[ "`id -u`" -ne "0" ]]; then
+ echo "WARNING: running as non-root, proceeding anyways..."
+fi
+
+# locking CPU frequency
+
+# note: locking cpu0 is sufficent to cover other cores as well
+echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
+echo 1530000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
+echo 1530000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
+echo 1530000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
+
+# locking GPU frequency
+
+# note: frequency choices can be found in:
+# cat /sys/class/drm/card0/device/pstate
+
+# select 768 MHz
+# 0a: core 768 MHz emc 1600 MHz
+echo 0a > /sys/class/drm/card0/device/pstate
diff --git a/scripts/perf-setup/sailin-setup.sh b/scripts/perf-setup/sailin-setup.sh
new file mode 100644
index 0000000..c538dc3
--- /dev/null
+++ b/scripts/perf-setup/sailin-setup.sh
@@ -0,0 +1,18 @@
+#Setup for newer devices
+
+if [[ "`id -u`" -ne "0" ]]; then
+ echo "WARNING: running as non-root, proceeding anyways..."
+fi
+
+stop thermal-engine
+stop perfd
+
+echo 0 > /sys/devices/system/cpu/cpu0/online
+echo 0 > /sys/devices/system/cpu/cpu1/online
+
+echo performance > /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
+echo 2150400 > /sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq
+
+echo 13763 > /sys/class/devfreq/soc:qcom,gpubw/max_freq
+echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor
+echo -n 624000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq
diff --git a/libraries/base-app-helpers/Android.mk b/tests/example/instrumentation/Android.mk
similarity index 67%
copy from libraries/base-app-helpers/Android.mk
copy to tests/example/instrumentation/Android.mk
index 9e797fb..77ad706 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/tests/example/instrumentation/Android.mk
@@ -1,5 +1,4 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
+#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.
@@ -12,17 +11,18 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := HelloWorldTests
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_CERTIFICATE := platform
-######################################
+LOCAL_COMPATIBILITY_SUITE := device-tests
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_PACKAGE)
diff --git a/tests/example/instrumentation/AndroidManifest.xml b/tests/example/instrumentation/AndroidManifest.xml
new file mode 100644
index 0000000..7b5111a
--- /dev/null
+++ b/tests/example/instrumentation/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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"
+ package="android.test.example.helloworld"
+ android:sharedUserId="android.uid.system" >
+
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.example.helloworld"
+ android:label="Hello World Test"/>
+
+</manifest>
diff --git a/tests/example/instrumentation/AndroidTest.xml b/tests/example/instrumentation/AndroidTest.xml
new file mode 100644
index 0000000..244d794
--- /dev/null
+++ b/tests/example/instrumentation/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs sample instrumentation test.">
+ <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup" />
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="HelloWorldTests.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer" />
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer" />
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="SampleInstrumentationTest" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="android.test.example.helloworld" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java b/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java
new file mode 100644
index 0000000..911769e
--- /dev/null
+++ b/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.test.example.helloworld;
+
+import android.support.test.filters.SmallTest;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class HelloWorldTest {
+
+ private static final String TAG = HelloWorldTest.class.getSimpleName();
+
+ @BeforeClass
+ public static void beforeClass() {
+ Log.d(TAG, "beforeClass()");
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Log.d(TAG, "afterClass()");
+ }
+
+ @Before
+ public void before() {
+ Log.d(TAG, "before()");
+ }
+
+ @After
+ public void after() {
+ Log.d(TAG, "after()");
+ }
+
+ @Test
+ @SmallTest
+ public void testHelloWorld() {
+ Log.d(TAG, "testHelloWorld()");
+ Assert.assertNotEquals("Hello", "world");
+ }
+
+ @Test
+ @SmallTest
+ public void testHalloWelt() {
+ Log.d(TAG, "testHalloWelt()");
+ Assert.assertNotEquals("Hallo", "Welt");
+ }
+}
diff --git a/libraries/base-app-helpers/Android.mk b/tests/example/native/Android.mk
similarity index 67%
copy from libraries/base-app-helpers/Android.mk
copy to tests/example/native/Android.mk
index 9e797fb..b68d14c 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/tests/example/native/Android.mk
@@ -1,4 +1,3 @@
-#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,17 +11,18 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_SRC_FILES := \
+ HelloWorldTest.cpp
-######################################
+LOCAL_MODULE := hello_world_test
+LOCAL_MODULE_TAGS := tests
-include $(call all-makefiles-under, $(LOCAL_PATH))
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_NATIVE_TEST)
+
diff --git a/tests/example/native/AndroidTest.xml b/tests/example/native/AndroidTest.xml
new file mode 100644
index 0000000..aea2225
--- /dev/null
+++ b/tests/example/native/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for APCT native hello world test cases">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="hello_world_test->/data/local/tmp/hello_world_test" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="hello_world_test" />
+ <option name="runtime-hint" value="8m" />
+ </test>
+</configuration>
diff --git a/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateTest.java b/tests/example/native/HelloWorldTest.cpp
similarity index 61%
copy from tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateTest.java
copy to tests/example/native/HelloWorldTest.cpp
index 4d5acec..25408d8 100644
--- a/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateTest.java
+++ b/tests/example/native/HelloWorldTest.cpp
@@ -14,21 +14,11 @@
* limitations under the License.
*/
-package com.android.functional.otatests;
+#include <gtest/gtest.h>
-import org.junit.Test;
+#include <stdio.h>
-/**
- * A basic test case to assert that the system was updated to the expected version.
- */
-public class SystemUpdateTest extends VersionCheckingTest {
-
- public SystemUpdateTest(String testPath) {
- super(testPath);
- }
-
- @Test
- public void testIsUpdated() throws Exception {
- assertUpdated();
- }
+TEST(HelloWorldTest, PrintHelloWorld) {
+ printf("Hello, World!");
}
+
diff --git a/tests/functional/applinktests/Android.mk b/tests/functional/applinktests/Android.mk
index 6644ac9..347494f 100644
--- a/tests/functional/applinktests/Android.mk
+++ b/tests/functional/applinktests/Android.mk
@@ -26,4 +26,6 @@
LOCAL_PACKAGE_NAME := AppLinkFunctionalTests
LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/applinktests/AndroidTest.xml b/tests/functional/applinktests/AndroidTest.xml
new file mode 100644
index 0000000..709e6b8
--- /dev/null
+++ b/tests/functional/applinktests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs AppLink Functional Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="AppLinkFunctionalTests.apk" />
+ <option name="test-file-name" value="AppLinkTestApp.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="ApplinkTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.functional.applinktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/appsmoke/Android.mk b/tests/functional/appsmoke/Android.mk
index 17d41c9..0fb3c72 100644
--- a/tests/functional/appsmoke/Android.mk
+++ b/tests/functional/appsmoke/Android.mk
@@ -23,4 +23,6 @@
LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib android-support-test
LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/appsmoke/AndroidTest.xml b/tests/functional/appsmoke/AndroidTest.xml
new file mode 100644
index 0000000..cd0c9a1
--- /dev/null
+++ b/tests/functional/appsmoke/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Prebuilt App Smoke Test.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="AppSmoke.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="AppSmoke" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="android.test.appsmoke" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/appsmoke/src/android/test/appsmoke/AppSmokeTest.java b/tests/functional/appsmoke/src/android/test/appsmoke/AppSmokeTest.java
index d061304..dcf6197 100644
--- a/tests/functional/appsmoke/src/android/test/appsmoke/AppSmokeTest.java
+++ b/tests/functional/appsmoke/src/android/test/appsmoke/AppSmokeTest.java
@@ -16,13 +16,14 @@
package android.test.appsmoke;
-import android.app.ActivityManagerNative;
+import android.app.ActivityManager;
import android.app.IActivityController;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -66,6 +67,7 @@
private boolean mAppHasError = false;
private boolean mLaunchIntentDetected = false;
+ private boolean mHasLeanback = false;
private ILauncherStrategy mLauncherStrategy = null;
private static UiDevice sDevice = null;
@@ -202,8 +204,16 @@
@Before
public void before() throws RemoteException {
- ActivityManagerNative.getDefault().setActivityController(mActivityController, false);
- mLauncherStrategy = LauncherStrategyFactory.getInstance(sDevice).getLauncherStrategy();
+ ActivityManager.getService().setActivityController(mActivityController, false);
+ LauncherStrategyFactory factory = LauncherStrategyFactory.getInstance(sDevice);
+ mLauncherStrategy = factory.getLauncherStrategy();
+ // Inject an instance of instrumentation only if leanback. This enables to launch any app
+ // in the Apps and Games row on leanback launcher.
+ Instrumentation instr = InstrumentationRegistry.getInstrumentation();
+ mHasLeanback = hasLeanback(instr.getTargetContext());
+ if (mHasLeanback) {
+ factory.getLeanbackLauncherStrategy().setInstrumentation(instr);
+ }
mAppHasError = false;
mLaunchIntentDetected = false;
}
@@ -217,9 +227,9 @@
@After
public void after() throws RemoteException {
sDevice.pressHome();
- ActivityManagerNative.getDefault().forceStopPackage(
+ ActivityManager.getService().forceStopPackage(
mAppInfo.packageName, UserHandle.USER_ALL);
- ActivityManagerNative.getDefault().setActivityController(null, false);
+ ActivityManager.getService().setActivityController(null, false);
}
@AfterClass
@@ -248,12 +258,23 @@
}
private void pokeApp() {
- int w = sDevice.getDisplayWidth();
- int h = sDevice.getDisplayHeight();
- int dY = h / 4;
- boolean ret = sDevice.swipe(w / 2, h / 2 + dY, w / 2, h / 2 - dY, 40);
- if (!ret) {
- Log.w(TAG, "Failed while attempting to poke front end window with swipe");
+ // The swipe action on leanback launcher that doesn't support swipe gesture may
+ // cause unnecessary focus change and test to fail.
+ // Use the dpad key to poke the app instead.
+ if (!mHasLeanback) {
+ int w = sDevice.getDisplayWidth();
+ int h = sDevice.getDisplayHeight();
+ int dY = h / 4;
+ boolean ret = sDevice.swipe(w / 2, h / 2 + dY, w / 2, h / 2 - dY, 40);
+ if (!ret) {
+ Log.w(TAG, "Failed while attempting to poke front end window with swipe");
+ }
+ } else {
+ sDevice.pressDPadUp();
}
}
+
+ private boolean hasLeanback(Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
}
diff --git a/libraries/base-app-helpers/Android.mk b/tests/functional/calculator/Android.mk
similarity index 64%
copy from libraries/base-app-helpers/Android.mk
copy to tests/functional/calculator/Android.mk
index 9e797fb..20827c7 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/tests/functional/calculator/Android.mk
@@ -1,5 +1,4 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,17 +11,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+
LOCAL_PATH := $(call my-dir)
-
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+
+LOCAL_PACKAGE_NAME := CalculatorFunctionalTests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ launcher-helper-lib \
+ metrics-helper-lib \
+ ub-uiautomator \
+ services.core \
+ legacy-android-test
-include $(BUILD_STATIC_JAVA_LIBRARY)
+#LOCAL_SDK_VERSION := current
-######################################
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_PACKAGE)
diff --git a/tests/functional/calculator/AndroidManifest.xml b/tests/functional/calculator/AndroidManifest.xml
new file mode 100644
index 0000000..0b68441
--- /dev/null
+++ b/tests/functional/calculator/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.calculator.functional" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="24" />
+
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="Android Calculator Functional Tests"
+ android:targetPackage="com.android.calculator.functional" />
+
+</manifest>
diff --git a/tests/functional/calculator/src/com/android/calculator/functional/CalculatorHelper.java b/tests/functional/calculator/src/com/android/calculator/functional/CalculatorHelper.java
new file mode 100644
index 0000000..4b45193
--- /dev/null
+++ b/tests/functional/calculator/src/com/android/calculator/functional/CalculatorHelper.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calculator.functional;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.launcherhelper.ILauncherStrategy;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.Until;
+import android.support.test.uiautomator.UiSelector;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+import junit.framework.Assert;
+
+public class CalculatorHelper {
+ private static CalculatorHelper mInstance = null;
+ private static final int SHORT_TIMEOUT = 1000;
+ private static final int LONG_TIMEOUT = 2000;
+ public static final String PACKAGE_NAME = "com.google.android.calculator";
+ public static final String APP_NAME = "Calculator";
+ public static final String TEST_TAG = "CalculatorTests";
+ public final int TIMEOUT = 500;
+ private Context mContext = null;
+ private UiDevice mDevice = null;
+ public ILauncherStrategy mLauncherStrategy;
+
+ private CalculatorHelper(UiDevice device, Context context) {
+ mDevice = device;
+ mContext = context;
+ mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
+ }
+
+ public static CalculatorHelper getInstance(UiDevice device, Context context) {
+ if (mInstance == null) {
+ mInstance = new CalculatorHelper(device, context);
+ }
+ return mInstance;
+ }
+
+ public void launchApp(String packageName, String appName) {
+ if (!mDevice.hasObject(By.pkg(packageName).depth(0))) {
+ mLauncherStrategy.launch(appName, packageName);
+ }
+ }
+
+ public void clickButton(String resource_id) {
+ UiObject2 button = mDevice.wait(
+ Until.findObject(By.res(PACKAGE_NAME, resource_id)),
+ SHORT_TIMEOUT);
+ Assert.assertNotNull("Element not found or pressed", button);
+ button.click();
+ }
+
+ public void performCalculation(String input1, String operator, String input2) {
+ clickButton(input1);
+ clickButton(operator);
+ clickButton(input2);
+ clickButton("eq");
+ }
+
+ public void pressLongDigits() {
+ for (int i=1; i<10; i++) clickButton("digit_"+i);
+ }
+
+ public void pressNumber100000() {
+ clickButton("digit_1");
+ for (int i=0; i<5; i++) clickButton("digit_0");
+ }
+
+ public String getResultText(String result) {
+ UiObject2 resultText = mDevice.wait(
+ Until.findObject(By.res(PACKAGE_NAME, result)),
+ SHORT_TIMEOUT);
+ Assert.assertNotNull("Result text box not found", resultText);
+ return resultText.getText();
+ }
+
+ public void clearResults(String result) {
+ UiObject2 resultText = mDevice.wait(
+ Until.findObject(By.res(PACKAGE_NAME, result)),
+ SHORT_TIMEOUT);
+ Assert.assertNotNull("Result box not found", resultText);
+ resultText.clear();
+ }
+
+ public void scrollResults(String result) {
+ UiObject2 resultText = mDevice.wait(
+ Until.findObject(By.res(PACKAGE_NAME, result)),
+ SHORT_TIMEOUT);
+ Assert.assertNotNull("Result text box not found", resultText);
+ resultText.swipe(Direction.LEFT, 1.0f, 5000);
+ Assert.assertEquals("Scroll failed","…41578750190521", getResultText("result"));
+ mDevice.waitForIdle();
+ resultText.swipe(Direction.RIGHT, 1.0f, 5000);
+ }
+
+ public void showAdvancedPad(){
+ UiObject2 padAdvanced = mDevice.wait(
+ Until.findObject(By.res(PACKAGE_NAME, "pad_advanced")),
+ SHORT_TIMEOUT);
+ if (padAdvanced.isClickable()) {//don't click if already pad opened
+ padAdvanced.click();
+ Assert.assertNotNull("Advanced pad not found", padAdvanced);
+ }
+ mDevice.waitForIdle();
+ }
+
+ public void dismissAdvancedPad() {
+ UiObject2 padAdvanced = mDevice.wait(
+ Until.findObject(By.res(PACKAGE_NAME, "pad_advanced")),
+ SHORT_TIMEOUT);
+ padAdvanced.swipe(Direction.RIGHT, 1.0f);
+ mDevice.waitForIdle();
+ }
+}
diff --git a/tests/functional/calculator/src/com/android/calculator/functional/CalculatorTests.java b/tests/functional/calculator/src/com/android/calculator/functional/CalculatorTests.java
new file mode 100644
index 0000000..183c07a
--- /dev/null
+++ b/tests/functional/calculator/src/com/android/calculator/functional/CalculatorTests.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calculator.functional;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.inputmethod.InputMethodManager;
+
+public class CalculatorTests extends InstrumentationTestCase {
+ private CalculatorHelper mCalculatorHelper = null;
+ private static final int SHORT_TIMEOUT = 1000;
+ private static final int LONG_TIMEOUT = 2000;
+ private UiDevice mDevice = null;
+ private Context mContext;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mContext = getInstrumentation().getContext();
+ mDevice.setOrientationNatural();
+ mDevice.pressHome();
+ mCalculatorHelper = CalculatorHelper.getInstance(mDevice, mContext);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mDevice.pressHome();
+ mDevice.unfreezeRotation();
+ super.tearDown();
+ }
+
+ // Launch the app
+ public void launchCalculator() {
+ mCalculatorHelper.launchApp(CalculatorHelper.PACKAGE_NAME, CalculatorHelper.APP_NAME);
+ mDevice.waitForIdle();
+ }
+
+ @SmallTest
+ //Test to verify basic addition functionality
+ public void testAdd() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.performCalculation("digit_9","op_add","digit_9");
+ assertEquals("Results are wrong", "18", mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @SmallTest
+ //Test to verify basic subraction functionality
+ public void testSubtract() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.performCalculation("digit_6","op_sub","digit_4");
+ assertEquals("Results are wrong","2", mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @SmallTest
+ //Test to verify basic multiplication functionality
+ public void testMultiply() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.performCalculation("digit_7","op_mul","digit_5");
+ assertEquals("Results are wrong","35", mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @SmallTest
+ //Test to verify basic divition functionality
+ public void testDivide() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.performCalculation("digit_8","op_div","digit_2");
+ assertEquals("Results are wrong","4", mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @SmallTest
+ //Test to verify to clear the results
+ public void testClearButton() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.performCalculation("digit_9","op_mul","digit_9");
+ mCalculatorHelper.clickButton("clr");
+ UiObject2 deleteButton = mDevice.wait(
+ Until.findObject(By.res(mCalculatorHelper.PACKAGE_NAME, "del")),
+ SHORT_TIMEOUT);
+ if (deleteButton !=null) { //Verify the button is changed to delete after clear
+ assertNull(mCalculatorHelper.getResultText("result"));
+ }
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @SmallTest
+ // Test divide by zero error
+ public void testDivideByZero() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.performCalculation("digit_1", "op_div","digit_0");
+ assertEquals("Error", "Can't divide by 0", mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @MediumTest
+ // Test Scroll funtion in long results
+ public void testScrollLongResult() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.pressLongDigits();
+ mCalculatorHelper.clickButton("op_mul");
+ mCalculatorHelper.pressLongDigits();
+ mCalculatorHelper.clickButton("eq");
+ mCalculatorHelper.scrollResults("result");
+ assertEquals("Scroll failed","1.52415787501E16", mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @MediumTest
+ // Test to verify the advanced panel and basic operation works
+ public void testAdvancedOperation() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.showAdvancedPad();
+ mCalculatorHelper.clickButton("fun_cos");
+ mCalculatorHelper.dismissAdvancedPad();
+ mCalculatorHelper.clickButton("digit_0");
+ mCalculatorHelper.clickButton("eq");
+ assertEquals("Results are wrong", "1",mCalculatorHelper.getResultText("result"));
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+
+ @MediumTest
+ //Test timeouts on complex calculations
+ public void testComplexCalculationTimeout() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.pressNumber100000();
+ mCalculatorHelper.showAdvancedPad();
+ mCalculatorHelper.clickButton("op_fact");
+ mCalculatorHelper.dismissAdvancedPad();
+ mCalculatorHelper.clickButton("eq");
+ mDevice.waitForIdle();
+ UiObject2 alertTitle = mDevice.wait(
+ Until.findObject(By.res("android:id/alertTitle")), SHORT_TIMEOUT);
+ assertNotNull("Alert pop up not found", alertTitle);
+ UiObject2 msgText = mDevice.wait(
+ Until.findObject(By.res(mCalculatorHelper.PACKAGE_NAME, "message")),
+ SHORT_TIMEOUT);
+ mDevice.waitForIdle();
+ assertEquals("Message not found", "Value may be infinite or undefined.",msgText.getText());
+ UiObject2 dismissButton = mDevice.wait(
+ Until.findObject(By.res("android:id/button2")), SHORT_TIMEOUT);
+ assertNotNull("Dismiss button not found", dismissButton);
+ dismissButton.click();
+ mCalculatorHelper.clearResults("formula");
+ mDevice.pressBack();
+ }
+
+ @MediumTest
+ // Test DEG/RAD switch happens and display changes
+ public void testDegRadSwitch() throws Exception {
+ launchCalculator();
+ mCalculatorHelper.showAdvancedPad();
+
+ UiObject2 toggleButton = mDevice.wait(
+ Until.findObject(By.res(mCalculatorHelper.PACKAGE_NAME, "toggle_mode")),
+ SHORT_TIMEOUT);
+ assertNotNull("Toggle Button not found", toggleButton);
+
+ UiObject2 modeBox = mDevice.wait(
+ Until.findObject(By.res(mCalculatorHelper.PACKAGE_NAME, "mode")),
+ SHORT_TIMEOUT);
+ assertNotNull("Mode Box not found", modeBox);
+
+ for (int i=0; i<3; i++) { //Test the toggle button 3 times
+ mCalculatorHelper.clickButton("toggle_mode");
+ mDevice.waitForIdle();
+ assertNotSame("Switch Failed",toggleButton.getText(),modeBox.getText());
+ }
+ mCalculatorHelper.dismissAdvancedPad();
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ }
+}
diff --git a/tests/functional/devicehealthtests/Android.mk b/tests/functional/devicehealthtests/Android.mk
index ebf312c..625ac44 100644
--- a/tests/functional/devicehealthtests/Android.mk
+++ b/tests/functional/devicehealthtests/Android.mk
@@ -23,5 +23,8 @@
LOCAL_PACKAGE_NAME := DeviceHealthTests
LOCAL_CERTIFICATE := platform
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/devicehealthtests/AndroidTest.xml b/tests/functional/devicehealthtests/AndroidTest.xml
new file mode 100644
index 0000000..4febb7a
--- /dev/null
+++ b/tests/functional/devicehealthtests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs DeviceHealthTests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="DeviceHealthTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="DeviceHealthTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.devicehealth.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheck.java b/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheck.java
index 6901370..81d67c8 100644
--- a/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheck.java
+++ b/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheck.java
@@ -15,32 +15,24 @@
*/
package com.android.devicehealth.tests;
-import android.content.Context;
-import android.os.DropBoxManager;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.Assert;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+import android.platform.test.annotations.GlobalPresubmit;
+
/**
- * Tests used for basic device health validation (ex: system app crash or system app
- * native crash) after the device boot is completed. This test class can be used to
- * add more tests in the future for additional basic device health validation
- * after the device boot is completed.
+ * Tests used for basic device health validation after the device boot is completed. This test class
+ * can be used to add more tests in the future for additional basic device health validation after
+ * the device boot is completed. This test is used for global presubmit, any dropbox label checked
+ * showing failures must be resolved immediately, or have flaky ones moved into
+ * {@link BasicHealthCheckPostSubmit} instead.
*/
+@GlobalPresubmit
@RunWith(Parameterized.class)
-public class BasicHealthCheck {
-
- private static final String SYSTEM_APP_CRASH_TAG = "system_app_crash";
- private static final String SYSTEM_APP_NATIVE_CRASH_TAG = "system_app_native_crash";
- private static final String SYSTEM_SERVER_ANR_TAG = "system_server_anr";
-
- private Context mContext;
+public class BasicHealthCheck extends HealthCheckBase {
@Parameter
public String mDropboxLabel;
@@ -48,39 +40,21 @@
@Parameters(name = "{0}")
public static String[] dropboxLabels() {
return new String[] {
- SYSTEM_APP_CRASH_TAG, SYSTEM_APP_NATIVE_CRASH_TAG, SYSTEM_SERVER_ANR_TAG};
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ "system_server_crash",
+ "system_server_native_crash",
+ "system_server_anr",
+ "system_app_crash",
+ "system_app_native_crash",
+ };
}
/**
- * Test if there are app crashes in the device by checking system_app_crash or
- * system_app_native_crash using DropBoxManager service.
+ * Test if there are app crashes in the device by checking system_app_crash,
+ * system_app_native_crash or system_server_anr using DropBoxManager service.
*/
@Test
public void checkCrash() {
- DropBoxManager dropbox = (DropBoxManager) mContext
- .getSystemService(Context.DROPBOX_SERVICE);
- Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
-
- long timestamp = 0;
- DropBoxManager.Entry entry = null;
- int crashCount = 0;
- // TODO: Fail the test if system_server_anr is observed or not.
- while (null != (entry = dropbox.getNextEntry(null, timestamp))) {
- try {
- if (mDropboxLabel.equals(entry.getTag())) {
- crashCount++;
- }
- } finally {
- entry.close();
- }
- timestamp = entry.getTimeMillis();
- }
- Assert.assertEquals(0, crashCount);
+ checkCrash(mDropboxLabel);
}
}
diff --git a/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheckPostSubmit.java b/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheckPostSubmit.java
new file mode 100644
index 0000000..25fc5a2
--- /dev/null
+++ b/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/BasicHealthCheckPostSubmit.java
@@ -0,0 +1,53 @@
+/*
+ * 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.devicehealth.tests;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests used for basic device health validation (ex: system app crash or system app
+ * native crash) after the device boot is completed. This test class can be used to
+ * add more tests in the future for additional basic device health validation
+ * after the device boot is completed.
+ */
+@RunWith(Parameterized.class)
+public class BasicHealthCheckPostSubmit extends HealthCheckBase {
+
+ @Parameter
+ public String mDropboxLabel;
+
+ @Parameters(name = "{0}")
+ public static String[] dropboxLabels() {
+ return new String[] {
+ "system_app_anr", // b/35626956
+ "SYSTEM_TOMBSTONE", // b/36066697
+ };
+ }
+
+ /**
+ * Test if there are app crashes in the device by checking system_app_crash,
+ * system_app_native_crash or system_server_anr using DropBoxManager service.
+ */
+ @Test
+ public void checkCrash() {
+ checkCrash(mDropboxLabel);
+ }
+
+}
diff --git a/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/HealthCheckBase.java b/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/HealthCheckBase.java
new file mode 100644
index 0000000..6341b74
--- /dev/null
+++ b/tests/functional/devicehealthtests/src/com/android/devicehealth/tests/HealthCheckBase.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.devicehealth.tests;
+
+import org.junit.Assert;
+import org.junit.Before;
+
+import android.content.Context;
+import android.os.DropBoxManager;
+import android.support.test.InstrumentationRegistry;
+
+abstract class HealthCheckBase {
+
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ /**
+ * Check dropbox service for a particular label and assert if found
+ */
+ protected void checkCrash(String label) {
+ DropBoxManager dropbox = (DropBoxManager) mContext
+ .getSystemService(Context.DROPBOX_SERVICE);
+ Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
+
+ long timestamp = 0;
+ DropBoxManager.Entry entry = null;
+ int crashCount = 0;
+ StringBuilder errorDetails = new StringBuilder("Error details:\n");
+ while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
+ try {
+ crashCount++;
+ errorDetails.append(label);
+ errorDetails.append(": ");
+ errorDetails.append(entry.getText(70));
+ errorDetails.append(" ...\n");
+ } finally {
+ entry.close();
+ }
+ timestamp = entry.getTimeMillis();
+ }
+ Assert.assertEquals(errorDetails.toString(), 0, crashCount);
+ }
+}
diff --git a/tests/functional/downloadapp/Android.mk b/tests/functional/downloadapp/Android.mk
index 814537b..1a58bb1 100644
--- a/tests/functional/downloadapp/Android.mk
+++ b/tests/functional/downloadapp/Android.mk
@@ -12,4 +12,6 @@
LOCAL_PACKAGE_NAME := DownloadAppFunctionalTests
LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/downloadapp/AndroidTest.xml b/tests/functional/downloadapp/AndroidTest.xml
new file mode 100644
index 0000000..e478177
--- /dev/null
+++ b/tests/functional/downloadapp/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs DownloadApp Functional Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="DownloadAppFunctionalTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="DownloadAppFunctionalTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.functional.downloadapp" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/notificationtests/Android.mk b/tests/functional/notificationtests/Android.mk
index de2ea8b..5c255a0 100644
--- a/tests/functional/notificationtests/Android.mk
+++ b/tests/functional/notificationtests/Android.mk
@@ -21,9 +21,13 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
launcher-helper-lib \
+ metrics-helper-lib \
ub-uiautomator \
- services.core
+ services.core \
+ legacy-android-test
#LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/notificationtests/AndroidManifest.xml b/tests/functional/notificationtests/AndroidManifest.xml
index 970ac39..2fc9b2a 100644
--- a/tests/functional/notificationtests/AndroidManifest.xml
+++ b/tests/functional/notificationtests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
<instrumentation
android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/functional/notificationtests/AndroidTest.xml b/tests/functional/notificationtests/AndroidTest.xml
new file mode 100644
index 0000000..9e85023
--- /dev/null
+++ b/tests/functional/notificationtests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Android Notifications Functional Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="NotificationFunctionalTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="android_systemui" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.notification.functional" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationBundlingTests.java b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationBundlingTests.java
index 84e3f57..1524646 100644
--- a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationBundlingTests.java
+++ b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationBundlingTests.java
@@ -34,8 +34,8 @@
private static final int SHORT_TIMEOUT = 200;
private static final int LONG_TIMEOUT = 2000;
private static final int GROUP_NOTIFICATION_ID = 1;
- private static final int CHILD_NOTIFICATION_ID = 100;
- private static final int SECOND_CHILD_NOTIFICATION_ID = 101;
+ private static final int CHILD_NOTIFICATION_ID = 500;
+ private static final int SECOND_CHILD_NOTIFICATION_ID = 501;
private static final String BUNDLE_GROUP_KEY = "group_key ";
private NotificationManager mNotificationManager;
private UiDevice mDevice = null;
diff --git a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationDNDTests.java b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationDNDTests.java
index 16fd608..994fce4 100644
--- a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationDNDTests.java
+++ b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationDNDTests.java
@@ -2,6 +2,7 @@
package com.android.notification.functional;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -99,7 +100,7 @@
mHelper.sendNotificationsWithInLineReply(NOTIFICATION_ID, true);
Thread.sleep(LONG_TIMEOUT);
NotificationRecord nr = new NotificationRecord(mContext,
- mHelper.getStatusBarNotification(NOTIFICATION_ID));
+ mHelper.getStatusBarNotification(NOTIFICATION_ID), mHelper.getDefaultChannel());
ZenModeConfig mConfig = mZenHelper.getConfig();
ZenModeFiltering zF = new ZenModeFiltering(mContext);
assertTrue(zF.shouldIntercept(mNotificationManager.getZenMode(), mConfig, nr));
@@ -120,19 +121,23 @@
mNotificationManager
.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
try {
- mHelper.showInstalledAppDetails(mContext, "com.android.notification.functional");
+ mHelper.showAppNotificationSettings(mContext);
+ mDevice.wait(Until.findObject(By.textContains("Miscellaneous")), LONG_TIMEOUT)
+ .click();
mDevice.wait(Until.findObject(By.textContains("Override Do Not Disturb")), LONG_TIMEOUT)
.click();
Thread.sleep(LONG_TIMEOUT);
mHelper.sendNotificationsWithInLineReply(NOTIFICATION_ID, true);
Thread.sleep(LONG_TIMEOUT);
NotificationRecord nr = new NotificationRecord(mContext,
- mHelper.getStatusBarNotification(NOTIFICATION_ID));
+ mHelper.getStatusBarNotification(NOTIFICATION_ID), mHelper.getDefaultChannel());
ZenModeConfig mConfig = mZenHelper.getConfig();
ZenModeFiltering zF = new ZenModeFiltering(mContext);
assertFalse(zF.shouldIntercept(mZenHelper.getZenMode(), mConfig, nr));
} finally {
- mHelper.showInstalledAppDetails(mContext, "com.android.notification.functional");
+ mHelper.showAppNotificationSettings(mContext);
+ mDevice.wait(Until.findObject(By.textContains("Miscellaneous")), LONG_TIMEOUT)
+ .click();
mDevice.wait(Until.findObject(By.textContains("Override Do Not Disturb")), LONG_TIMEOUT)
.click();
mNotificationManager.setInterruptionFilter(setting);
@@ -145,16 +150,16 @@
@LargeTest
public void testBlockNotification() throws Exception {
try {
- mHelper.showInstalledAppDetails(mContext, "com.android.notification.functional");
+ mHelper.showAppNotificationSettings(mContext);
mDevice.wait(Until.findObject(By.textContains("Block all")), LONG_TIMEOUT).click();
Thread.sleep(LONG_TIMEOUT);
mHelper.sendNotificationsWithInLineReply(NOTIFICATION_ID, true);
Thread.sleep(LONG_TIMEOUT);
if (mHelper.checkNotificationExistence(NOTIFICATION_ID, true)) {
- fail(String.format("Notification %s has not benn blocked", NOTIFICATION_ID));
+ fail(String.format("Notification %s has not been blocked", NOTIFICATION_ID));
}
} finally {
- mHelper.showInstalledAppDetails(mContext, "com.android.notification.functional");
+ mHelper.showAppNotificationSettings(mContext);
mDevice.wait(Until.findObject(By.textContains("Block all")), LONG_TIMEOUT).click();
}
}
diff --git a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationHelper.java b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationHelper.java
index 7e36fa8..dc9aeb4 100644
--- a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationHelper.java
+++ b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationHelper.java
@@ -16,27 +16,23 @@
package com.android.notification.functional;
-import android.app.AlarmManager;
import android.app.Instrumentation;
import android.app.IntentService;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.Notification.Builder;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
@@ -57,14 +53,18 @@
import java.util.List;
import java.util.Map;
+
public class NotificationHelper {
private static final String LOG_TAG = NotificationHelper.class.getSimpleName();
- private static final int LONG_TIMEOUT = 2000;
+ private static final int LONG_TIMEOUT = 2500;
private static final int SHORT_TIMEOUT = 200;
private static final String KEY_QUICK_REPLY_TEXT = "quick_reply";
private static final UiSelector LIST_VIEW = new UiSelector().className(ListView.class);
private static final UiSelector LIST_ITEM_VALUE = new UiSelector().className(TextView.class);
+ public static final String FIRST_ACTION = "FIRST ACTION";
+ public static final String SECOND_ACTION = "SECOND ACTION";
+ public static final String CONTENT_TITLE = "THIS IS A NOTIFICATION";
private UiDevice mDevice;
private Instrumentation mInst;
@@ -107,7 +107,11 @@
mDevice.pressEnter();
}
new UiObject(new UiSelector().text("PIN")).click();
- clickText("No thanks");
+ // If there's an option to set 'require PIN to start device'
+ // choose 'No thanks', otherwise just skip ahead.
+ if (new UiObject(new UiSelector().text("No thanks")).exists()) {
+ clickText("No thanks");
+ }
UiObject pinField = new UiObject(new UiSelector().className(EditText.class.getName()));
pinField.setText(String.format("%04d", pin));
mDevice.pressEnter();
@@ -164,32 +168,52 @@
}
public void sendNotification(int id, int visibility, String title) throws Exception {
+ sendNotification(id, visibility, title, false);
+ }
+
+ public void sendNotification(int id, int visibility, String title, boolean buzz)
+ throws Exception {
Log.v(LOG_TAG, "Sending out notification...");
+ PendingIntent emptyIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent("an.action.that.nobody.will.be.listening.for"), 0);
Intent intent = new Intent(Intent.ACTION_VIEW);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
CharSequence subtitle = String.valueOf(System.currentTimeMillis());
- Notification notification = new Notification.Builder(mContext)
+ Notification.Builder notification = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.stat_notify_email)
- .setWhen(System.currentTimeMillis()).setContentTitle(title).setContentText(subtitle)
- .setContentIntent(pendingIntent).setVisibility(visibility)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle(title)
+ .setContentText(subtitle)
+ .setContentIntent(pendingIntent)
+ .setVisibility(visibility)
.setPriority(Notification.PRIORITY_HIGH)
- .build();
- mNotificationManager.notify(id, notification);
+ .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email,
+ FIRST_ACTION, emptyIntent)
+ .build())
+ .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email,
+ SECOND_ACTION, emptyIntent)
+ .build())
+ .setAutoCancel(false);
+ if (buzz) {
+ notification.setDefaults(Notification.DEFAULT_VIBRATE);
+ }
+ mNotificationManager.notify(id, notification.build());
Thread.sleep(LONG_TIMEOUT);
}
- public void sendNotifications(Map<Integer, String> lists) throws Exception {
+ public void sendNotifications(Map<Integer, String> lists, boolean withDelay) throws Exception {
Log.v(LOG_TAG, "Sending out notification...");
- Intent intent = new Intent(Intent.ACTION_VIEW);
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
CharSequence subtitle = String.valueOf(System.currentTimeMillis());
for (Map.Entry<Integer, String> l : lists.entrySet()) {
- Notification notification = new Notification.Builder(mContext)
+ Notification.Builder notification = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.stat_notify_email)
.setWhen(System.currentTimeMillis()).setContentTitle(l.getValue())
- .setContentText(subtitle)
- .build();
- mNotificationManager.notify(l.getKey(), notification);
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(subtitle);
+ mNotificationManager.notify(l.getKey(), notification.build());
+ if (withDelay) {
+ Thread.sleep(SHORT_TIMEOUT);
+ }
}
Thread.sleep(LONG_TIMEOUT);
}
@@ -259,7 +283,7 @@
}
public void swipeUp() throws Exception {
- mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight(),
+ mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight()*3/4,
mDevice.getDisplayWidth() / 2, 0, 30);
Thread.sleep(SHORT_TIMEOUT);
}
@@ -279,15 +303,11 @@
}
}
- public void showInstalledAppDetails(Context context, String packageName) throws Exception {
- Intent intent = new Intent();
+ public void showAppNotificationSettings(Context context) throws Exception {
+ Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Uri uri = Uri.fromParts("package", packageName, null);
- intent.setData(uri);
- intent.setClassName("com.android.settings",
- "com.android.settings.Settings$AppNotificationSettingsActivity");
- intent.putExtra("app_package", mContext.getPackageName());
- intent.putExtra("app_uid", mContext.getApplicationInfo().uid);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName());
+ intent.putExtra(Settings.EXTRA_APP_UID, mContext.getApplicationInfo().uid);
context.startActivity(intent);
Thread.sleep(LONG_TIMEOUT * 2);
}
@@ -337,6 +357,10 @@
mNotificationManager.notify(notificationId, n.build());
}
+ public NotificationChannel getDefaultChannel() {
+ return mNotificationManager.getNotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID);
+ }
+
public static class ToastService extends IntentService {
private static final String TAG = "ToastService";
private static final String ACTION_TOAST = "toast";
diff --git a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationInteractionTests.java b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationInteractionTests.java
index 83aae52..53b9c69 100644
--- a/tests/functional/notificationtests/src/com/android/notification/functional/NotificationInteractionTests.java
+++ b/tests/functional/notificationtests/src/com/android/notification/functional/NotificationInteractionTests.java
@@ -16,9 +16,12 @@
package com.android.notification.functional;
+import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
+import android.metrics.LogMaker;
import android.service.notification.StatusBarNotification;
+import android.support.test.metricshelper.MetricsAsserts;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
@@ -28,12 +31,17 @@
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import android.metrics.MetricsReader;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import java.util.HashMap;
import java.util.Map;
+import java.util.Queue;
public class NotificationInteractionTests extends InstrumentationTestCase {
private static final String LOG_TAG = NotificationInteractionTests.class.getSimpleName();
- private static final int LONG_TIMEOUT = 2000;
+ private static final int LONG_TIMEOUT = 3000;
+ private static final int SHORT_TIMEOUT = 200;
private final boolean DEBUG = false;
private NotificationManager mNotificationManager;
private UiDevice mDevice = null;
@@ -41,6 +49,7 @@
private NotificationHelper mHelper;
private static final int CUSTOM_NOTIFICATION_ID = 1;
private static final int NOTIFICATIONS_COUNT = 3;
+ private MetricsReader mMetricsReader;
@Override
public void setUp() throws Exception {
@@ -52,6 +61,8 @@
mHelper = new NotificationHelper(mDevice, getInstrumentation(), mNotificationManager);
mDevice.setOrientationNatural();
mNotificationManager.cancelAll();
+ mMetricsReader = new MetricsReader();
+ mMetricsReader.checkpoint(); // clear out old logs
}
@Override
@@ -86,7 +97,8 @@
for (int i = 0; i < NOTIFICATIONS_COUNT; i++) {
lists.put(CUSTOM_NOTIFICATION_ID + i, Integer.toString(CUSTOM_NOTIFICATION_ID + i));
}
- mHelper.sendNotifications(lists);
+ mHelper.sendNotifications(lists, false);
+
if (DEBUG) {
Log.d(LOG_TAG,
String.format("posted %s notifications, here they are: ", NOTIFICATIONS_COUNT));
@@ -98,12 +110,166 @@
if (mDevice.openNotification()) {
Thread.sleep(LONG_TIMEOUT);
UiObject2 clearAll = findByText(text);
+ assertNotNull("could not find clear all target", clearAll);
clearAll.click();
}
Thread.sleep(LONG_TIMEOUT);
sbns = mNotificationManager.getActiveNotifications();
assertTrue(String.format("%s notifications have not been cleared", sbns.length),
sbns.length == currentSbns);
+
+ MetricsAsserts.assertHasVisibilityLog("missing panel revealed log", mMetricsReader,
+ MetricsEvent.NOTIFICATION_PANEL, true);
+ MetricsAsserts.assertHasLog("missing notification visibility log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, CUSTOM_NOTIFICATION_ID)
+ .setPackageName(mContext.getPackageName()));
+ MetricsAsserts.assertHasLog("missing notification cancel log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_DISMISS)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, CUSTOM_NOTIFICATION_ID)
+ .setPackageName(mContext.getPackageName()));
+ MetricsAsserts.assertHasActionLog("missing dismiss-all log", mMetricsReader,
+ MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+ MetricsAsserts.assertHasVisibilityLog("missing panel hidden log", mMetricsReader,
+ MetricsEvent.NOTIFICATION_PANEL, false);
+ }
+
+ /** send notifications, then open and close the shade to test visibility metrics. */
+ @MediumTest
+ public void testNotificationShadeMetricsl() throws Exception {
+ Map<Integer, String> lists = new HashMap<Integer, String>();
+ int firstId = CUSTOM_NOTIFICATION_ID;
+ int secondId = CUSTOM_NOTIFICATION_ID + 1;
+ lists.put(firstId, Integer.toString(firstId));
+ lists.put(secondId, Integer.toString(secondId));
+ // post
+ mHelper.sendNotifications(lists, true);
+ Thread.sleep(LONG_TIMEOUT);
+ // update
+ mHelper.sendNotifications(lists, true);
+
+ if (mDevice.openNotification()) {
+ Thread.sleep(LONG_TIMEOUT);
+ }
+ MetricsAsserts.assertHasVisibilityLog("missing panel revealed log", mMetricsReader,
+ MetricsEvent.NOTIFICATION_PANEL, true);
+ Queue<LogMaker> firstLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, firstId)
+ .setPackageName(mContext.getPackageName()));
+ assertTrue("missing first note visibility log", !firstLog.isEmpty());
+ Queue<LogMaker> secondLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, secondId));
+ assertTrue("missing second note visibility log", !secondLog.isEmpty());
+ int firstRank = (Integer) firstLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX);
+ int secondRank = (Integer) secondLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX);
+ assertTrue("note must have distinct ranks", firstRank != secondRank);
+ int lifespan = (Integer) firstLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS);
+ int freshness = (Integer) firstLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS);
+ int exposure = (Integer) firstLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+ assertTrue("first note updated before it was created", lifespan > freshness);
+ assertTrue("first note visible before it was updated", freshness > exposure);
+ assertTrue("first note visibility log should have zero exposure time", exposure == 0);
+ int secondLifespan = (Integer) secondLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS);
+ assertTrue("first note created after second note", lifespan > secondLifespan);
+
+ mMetricsReader.checkpoint(); // clear out old logs again
+ firstLog.clear();
+ secondLog.clear();
+ // close the shade
+ if (mDevice.pressHome()) {
+ Thread.sleep(LONG_TIMEOUT);
+ }
+
+ MetricsAsserts.assertHasVisibilityLog("missing panel hidden log", mMetricsReader,
+ MetricsEvent.NOTIFICATION_PANEL, false);
+ firstLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_CLOSE)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, firstId)
+ .setPackageName(mContext.getPackageName()));
+ assertTrue("missing first note hidden log", !firstLog.isEmpty());
+ exposure = (Integer) firstLog.peek()
+ .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+ assertTrue("first note visibility log should have nonzero exposure time", exposure > 0);
+ secondLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_CLOSE)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, secondId)
+ .setPackageName(mContext.getPackageName()));
+ assertTrue("missing second note hidden log", !secondLog.isEmpty());
+ }
+
+ /** send a notification, click on first it. */
+ @MediumTest
+ public void testNotificationClicks() throws Exception {
+ int id = CUSTOM_NOTIFICATION_ID;
+ mHelper.sendNotification(id, Notification.VISIBILITY_PUBLIC,
+ NotificationHelper.CONTENT_TITLE, true);
+
+ UiObject2 target = null;
+ if (mDevice.openNotification()) {
+ target = mDevice.wait(
+ Until.findObject(By.text(NotificationHelper.FIRST_ACTION)),
+ LONG_TIMEOUT);
+ assertNotNull("could not find first action button", target);
+ target.click();
+ }
+ Thread.sleep(SHORT_TIMEOUT);
+ // top item is always expanded
+ MetricsAsserts.assertHasLog("missing notification expansion log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_DETAIL)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
+ .setPackageName(mContext.getPackageName()));
+ MetricsAsserts.assertHasLog("missing notification alert log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ALERT)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
+ .setSubtype(1) // 1: BUZZ, nop BEEP, nop BLINK
+ .setPackageName(mContext.getPackageName()));
+ MetricsAsserts.assertHasLog("missing notification action 0 click log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM_ACTION)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
+ .setSubtype(0) // first action button, zero indexed
+ .setPackageName(mContext.getPackageName()));
+
+ mMetricsReader.checkpoint(); // clear out old logs again
+ target = mDevice.wait(Until.findObject(By.text(NotificationHelper.SECOND_ACTION)),
+ LONG_TIMEOUT);
+ assertNotNull("could not find second action button", target);
+ target.click();
+ Thread.sleep(SHORT_TIMEOUT);
+ MetricsAsserts.assertHasLog("missing notification action 1 click log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM_ACTION)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
+ .setSubtype(1) // second action button, zero indexed
+ .setPackageName(mContext.getPackageName()));
+
+ mMetricsReader.checkpoint(); // clear out old logs again\
+ target = mDevice.wait(Until.findObject(By.text(NotificationHelper.CONTENT_TITLE)),
+ LONG_TIMEOUT);
+ assertNotNull("could not find content click target", target);
+ target.click();
+ Thread.sleep(SHORT_TIMEOUT);
+ MetricsAsserts.assertHasLog("missing notification content click log", mMetricsReader,
+ new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
+ .setPackageName(mContext.getPackageName()));
}
private UiObject2 findByText(String text) throws Exception {
diff --git a/tests/functional/otatests/Android.mk b/tests/functional/otatests/Android.mk
deleted file mode 100644
index 2d6a980..0000000
--- a/tests/functional/otatests/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := OtaFunctionalTests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- easymocklib \
- launcher-helper-lib \
- objenesis-target \
- ub-uiautomator
-
-include $(BUILD_PACKAGE)
diff --git a/tests/functional/otatests/AndroidManifest.xml b/tests/functional/otatests/AndroidManifest.xml
deleted file mode 100644
index b4037ea..0000000
--- a/tests/functional/otatests/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.functional.otatests">
-
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-
- <uses-sdk android:minSdkVersion="19"
- android:targetSdkVersion="24" />
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.functional.otatests"
- android:label="Android System Update Functional Tests" />
-</manifest>
diff --git a/tests/functional/otatests/src/com/android/functional/otatests/PackageProcessTest.java b/tests/functional/otatests/src/com/android/functional/otatests/PackageProcessTest.java
deleted file mode 100644
index f3a873a..0000000
--- a/tests/functional/otatests/src/com/android/functional/otatests/PackageProcessTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.functional.otatests;
-
-import static org.easymock.EasyMock.createNiceMock;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.os.IPowerManager;
-import android.os.PowerManager;
-import android.os.RecoverySystem;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import java.io.File;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class PackageProcessTest {
-
- private static final String PACKAGE_DATA_PATH =
- "/data/data/com.google.android.gms/app_download/update.zip";
- private static final String BLOCK_MAP = "/cache/recovery/block.map";
- private static final String UNCRYPT_FILE = "/cache/recovery/uncrypt_file";
-
- private Context mMockContext;
- private Context mContext;
- private PowerManager mMockPowerManager;
- private Instrumentation mInstrumentation;
-
- private class PackageProcessMockContext extends ContextWrapper {
-
- private Context mInternal;
-
- public PackageProcessMockContext(Context base) {
- super(base);
- mInternal = base;
- }
-
- @Override
- public Object getSystemService(String name) {
- if (name.equals(Context.POWER_SERVICE)) {
- return mMockPowerManager;
- }
- return mInternal.getSystemService(name);
- }
- }
-
- @Before
- public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mContext = mInstrumentation.getContext();
- mMockContext = new PackageProcessMockContext(mContext);
- // Set a mocked out power manager into the mocked context, so the device
- // won't reboot at the end of installPackage
- IPowerManager mockIPowerManager = createNiceMock(IPowerManager.class);
- mMockPowerManager = new PowerManager(mContext, mockIPowerManager, null);
- }
-
- @Test
- public void testPackageProcessOnly() throws Exception {
- File pkg = new File(PACKAGE_DATA_PATH);
- RecoverySystem.verifyPackage(pkg, null, null);
- RecoverySystem.processPackage(mMockContext, pkg, null);
- // uncrypt will push block.map to this location if and only if it finishes successfully
- assertTrue(new File(BLOCK_MAP).exists());
- }
-
- @Test
- public void testPackageProcessViaInstall() throws Exception {
- File pkg = new File(PACKAGE_DATA_PATH);
- RecoverySystem.verifyPackage(pkg, null, null);
- RecoverySystem.installPackage(mMockContext, pkg);
- // uncrypt will push block.map to this location if and only if it finishes successfully
- assertTrue(new File(UNCRYPT_FILE).exists());
- }
-}
diff --git a/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateAppTest.java b/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateAppTest.java
deleted file mode 100644
index 12e0158..0000000
--- a/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateAppTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.functional.otatests;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
-import org.junit.After;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests functionality related to the System Update app.
- *
- * Precondition: an OTA must be available to the device.
- */
-@RunWith(AndroidJUnit4.class)
-public class SystemUpdateAppTest {
-
- private static final String SYSTEM_UPDATE_INTENT = "android.settings.SYSTEM_UPDATE_SETTINGS";
- private static final String POLICY_AUTH_PACKAGE = "com.google.android.gms.policy_auth";
- private static final long TIMEOUT_MS = 2000;
-
- private UiDevice mDevice;
- private Instrumentation mInstrumentation;
-
- @BeforeClass
- public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mDevice = UiDevice.getInstance(mInstrumentation);
- }
-
- @After
- public void tearDown() throws Exception {
- mDevice.pressHome();
- }
-
- @Test
- public void testUpdateIsAvailable() {
- mInstrumentation.getContext().startActivity(new Intent(SYSTEM_UPDATE_INTENT));
- String mainText = mDevice.wait(Until.findObject(
- By.res(POLICY_AUTH_PACKAGE, "suw_layout_title")), TIMEOUT_MS).getText();
- assertTrue(mainText.contains("available"));
- assertNotNull(mDevice.findObject(By.res(POLICY_AUTH_PACKAGE, "suw_items_title")));
- }
-}
diff --git a/tests/functional/otatests/src/com/android/functional/otatests/VersionCheckingTest.java b/tests/functional/otatests/src/com/android/functional/otatests/VersionCheckingTest.java
deleted file mode 100644
index df3df9c..0000000
--- a/tests/functional/otatests/src/com/android/functional/otatests/VersionCheckingTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.functional.otatests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Bundle;
-import android.support.test.InstrumentationRegistry;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.Arrays;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class VersionCheckingTest {
-
- protected static final String OLD_VERSION = "/sdcard/otatest/version.old";
- protected static final String NEW_VERSION = "/sdcard/otatest/version.new";
- protected static final String KEY_BUILD_ID = "ro.build.version.incremental";
- protected static final String KEY_BOOTLOADER = "ro.bootloader";
- protected static final String KEY_BASEBAND = "ro.build.expect.baseband";
- protected static final String KEY_BASEBAND_GSM = "gsm.version.baseband";
- protected static final String PATH_NAME = "path_name";
-
- protected VersionInfo mOldVersion;
- protected VersionInfo mNewVersion;
-
- protected String mTestPath;
-
- public VersionCheckingTest(String testPath) {
- mTestPath = testPath;
- }
-
- @Before
- public void setUp() throws Exception {
- try {
- mOldVersion = VersionInfo.parseFromFile(OLD_VERSION);
- mNewVersion = VersionInfo.parseFromFile(NEW_VERSION);
- } catch (IOException e) {
- throw new RuntimeException(
- "Couldn't find version file; was this test run with VersionCachePreparer?", e);
- }
- }
-
- @Parameters(name = "{0}")
- public static Iterable<? extends Object> getOtaPathName() {
- Bundle args = InstrumentationRegistry.getArguments();
- if (args.containsKey(PATH_NAME)) {
- return Arrays.asList(args.getString(PATH_NAME));
- }
- return Arrays.asList("unnamed path");
- }
-
- protected void assertNotUpdated() throws IOException {
- assertEquals(mOldVersion.getBuildId(), getProp(KEY_BUILD_ID));
- assertEquals(mOldVersion.getBootloaderVersion(), getProp(KEY_BOOTLOADER));
- assertTrue(mOldVersion.getBasebandVersion().equals(getProp(KEY_BASEBAND))
- || mOldVersion.getBasebandVersion().equals(getProp(KEY_BASEBAND_GSM)));
- }
-
- protected void assertUpdated() throws IOException {
- assertEquals(mNewVersion.getBuildId(), getProp(KEY_BUILD_ID));
- assertEquals(mNewVersion.getBootloaderVersion(), getProp(KEY_BOOTLOADER));
- // Due to legacy property names (an old meaning to gsm.version.baseband),
- // the KEY_BASEBAND and KEY_BASEBAND_GSM properties may not match each other.
- // At least one of them will always match the baseband version recorded by
- // NEW_VERSION.
- assertTrue(mNewVersion.getBasebandVersion().equals(getProp(KEY_BASEBAND))
- || mNewVersion.getBasebandVersion().equals(getProp(KEY_BASEBAND_GSM)));
- }
-
- private String getProp(String key) throws IOException {
- Process p = Runtime.getRuntime().exec("getprop " + key);
- BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
- String ret = r.readLine().trim();
- r.close();
- return ret;
- }
-}
diff --git a/tests/functional/otatests/src/com/android/functional/otatests/VersionInfo.java b/tests/functional/otatests/src/com/android/functional/otatests/VersionInfo.java
deleted file mode 100644
index 624c698..0000000
--- a/tests/functional/otatests/src/com/android/functional/otatests/VersionInfo.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.functional.otatests;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-public class VersionInfo {
- private final String mBuildId;
- private final String mBootloaderVersion;
- private final String mBasebandVersion;
-
- private VersionInfo(String buildId, String bootVersion, String radioVersion) {
- mBuildId = buildId;
- mBootloaderVersion = bootVersion;
- mBasebandVersion = radioVersion;
- }
-
- public String getBuildId() {
- return mBuildId;
- }
-
- public String getBootloaderVersion() {
- return mBootloaderVersion;
- }
-
- public String getBasebandVersion() {
- return mBasebandVersion;
- }
-
- public static VersionInfo parseFromFile(String fileName) throws IOException {
- BufferedReader r = new BufferedReader(
- new InputStreamReader(new FileInputStream(new File(fileName))));
- try {
- return new VersionInfo(
- denull(r.readLine()),
- denull(r.readLine()),
- denull(r.readLine()));
- } finally {
- r.close();
- }
- }
-
- private static String denull(String s) {
- return s == null || s.equals("null") ? "" : s;
- }
-}
diff --git a/tests/functional/overviewtests/Android.mk b/tests/functional/overviewtests/Android.mk
index 6d6cec7..828b1f5 100644
--- a/tests/functional/overviewtests/Android.mk
+++ b/tests/functional/overviewtests/Android.mk
@@ -23,4 +23,6 @@
LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/overviewtests/AndroidTest.xml b/tests/functional/overviewtests/AndroidTest.xml
new file mode 100644
index 0000000..8a992a7
--- /dev/null
+++ b/tests/functional/overviewtests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Platform Android Overview Functional Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="OverviewFunctionalTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="android_systemui" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="android.overview.functional" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/overviewtests/src/com/android/overview/functional/MultiWindowTests.java b/tests/functional/overviewtests/src/com/android/overview/functional/MultiWindowTests.java
index 4cbf7e7..db9fcd7 100644
--- a/tests/functional/overviewtests/src/com/android/overview/functional/MultiWindowTests.java
+++ b/tests/functional/overviewtests/src/com/android/overview/functional/MultiWindowTests.java
@@ -97,6 +97,9 @@
public void testResizeHandleOnMultiwindow() throws Exception {
mOverviewHelper.dockAppsToBothMultiwindowAreas(CALCULATOR_PACKAGE, "Calculator",
GMAIL_PACKAGE);
+ // Adding a sleep here to make sure the test fetches the bounds of the
+ // elements on the multiwindow screen instead of the home screen.
+ Thread.sleep(TIMEOUT);
// verify initial bounds for top and bottom
mDevice.click(mDevice.getDisplayHeight() / 4, mDevice.getDisplayWidth() / 2);
UiObject2 calcArea = mDevice.wait(Until.findObject
diff --git a/tests/functional/permission/Android.mk b/tests/functional/permission/Android.mk
index 0ecdeb2..e71d648 100644
--- a/tests/functional/permission/Android.mk
+++ b/tests/functional/permission/Android.mk
@@ -7,9 +7,13 @@
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
-
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator \
+ launcher-helper-lib \
+ permission-helper \
+ package-helper
LOCAL_PACKAGE_NAME := PermissionFunctionalTests
LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/permission/AndroidTest.xml b/tests/functional/permission/AndroidTest.xml
new file mode 100644
index 0000000..2640372
--- /dev/null
+++ b/tests/functional/permission/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Permission Functional Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="PermissionFunctionalTests.apk" />
+ <option name="test-file-name" value="PermissionTestAppMV1.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="PermissionTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.functional.permissiontests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/permission/src/com/android/functional/permissiontests/GenericAppPermissionTests.java b/tests/functional/permission/src/com/android/functional/permissiontests/GenericAppPermissionTests.java
index 1923e3c..3379dca 100644
--- a/tests/functional/permission/src/com/android/functional/permissiontests/GenericAppPermissionTests.java
+++ b/tests/functional/permission/src/com/android/functional/permissiontests/GenericAppPermissionTests.java
@@ -18,19 +18,19 @@
import android.app.UiAutomation;
import android.content.Context;
-import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.launcherhelper.ILauncherStrategy;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
+import android.system.helpers.PackageHelper;
+import android.system.helpers.PermissionHelper;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
-import com.android.functional.permissiontests.PermissionHelper.PermissionOp;
-import com.android.functional.permissiontests.PermissionHelper.PermissionStatus;
-
import java.util.Arrays;
import java.util.List;
@@ -42,7 +42,9 @@
private UiDevice mDevice = null;
private Context mContext = null;
private UiAutomation mUiAutomation = null;
- private PermissionHelper pHelper;
+ private PermissionHelper pHelper = null;
+ private PackageHelper pkgHelper = null;
+ private ILauncherStrategy mILauncherStrategy = null;
private final String[] mDefaultPermittedGroups = new String[] {
"CONTACTS", "SMS", "STORAGE"
};
@@ -61,7 +63,8 @@
mContext = getInstrumentation().getContext();
mUiAutomation = getInstrumentation().getUiAutomation();
mDevice.setOrientationNatural();
- pHelper = PermissionHelper.getInstance(mDevice, mContext, mUiAutomation);
+ pHelper = PermissionHelper.getInstance();
+ pkgHelper = PackageHelper.getInstance(getInstrumentation());
mDefaultGrantedPermissions = pHelper.getPermissionByPackage(TARGET_APP_PKG, Boolean.TRUE);
}
@@ -84,12 +87,13 @@
public void testToggleAppPermisssionOFF() {
pHelper.togglePermissionSetting(PERMISSION_TEST_APP, "Contacts", Boolean.FALSE);
pHelper.verifyPermissionSettingStatus(
- PERMISSION_TEST_APP, "Contacts", PermissionStatus.OFF);
+ PERMISSION_TEST_APP, "Contacts", PermissionHelper.PermissionStatus.OFF);
}
public void testToggleAppPermisssionON() {
pHelper.togglePermissionSetting(PERMISSION_TEST_APP, "Contacts", Boolean.TRUE);
- pHelper.verifyPermissionSettingStatus(PERMISSION_TEST_APP, "Contacts", PermissionStatus.ON);
+ pHelper.verifyPermissionSettingStatus(PERMISSION_TEST_APP, "Contacts",
+ PermissionHelper.PermissionStatus.ON);
}
@MediumTest
@@ -101,8 +105,10 @@
}
public void testPermissionDialogAllow() {
- pHelper.cleanPackage(PERMISSION_TEST_APP_PKG);
- pHelper.launchApp(PERMISSION_TEST_APP_PKG, PERMISSION_TEST_APP);
+ pkgHelper.cleanPackage(PERMISSION_TEST_APP_PKG);
+ if (!mDevice.hasObject(By.pkg(PERMISSION_TEST_APP_PKG).depth(0))) {
+ mILauncherStrategy.launch(PERMISSION_TEST_APP, PERMISSION_TEST_APP_PKG);
+ }
mDevice.wait(Until.findObject(By.text("GET CONTACT PERMISSION")), pHelper.TIMEOUT).click();
mDevice.wait(Until.findObject(
By.res(PACKAGE_INSTALLER, "permission_allow_button")), pHelper.TIMEOUT).click();
@@ -112,10 +118,13 @@
}
public void testPermissionDialogDenyFlow() {
- pHelper.cleanPackage(PERMISSION_TEST_APP_PKG);
- pHelper.launchApp(PERMISSION_TEST_APP_PKG, PERMISSION_TEST_APP);
+ pkgHelper.cleanPackage(PERMISSION_TEST_APP_PKG);
+ if (!mDevice.hasObject(By.pkg(PERMISSION_TEST_APP_PKG).depth(0))) {
+ mILauncherStrategy.launch(PERMISSION_TEST_APP, PERMISSION_TEST_APP_PKG);
+ }
pHelper.grantOrRevokePermissionViaAdb(
- PERMISSION_TEST_APP_PKG, "android.permission.READ_CONTACTS", PermissionOp.REVOKE);
+ PERMISSION_TEST_APP_PKG, "android.permission.READ_CONTACTS",
+ PermissionHelper.PermissionOp.REVOKE);
BySelector getContactSelector = By.text("GET CONTACT PERMISSION");
BySelector dontAskChkSelector = By.res(PACKAGE_INSTALLER, "do_not_ask_checkbox");
BySelector denySelctor = By.res(PACKAGE_INSTALLER, "permission_deny_button");
diff --git a/libraries/base-app-helpers/Android.mk b/tests/functional/systemmetrics/Android.mk
similarity index 68%
copy from libraries/base-app-helpers/Android.mk
copy to tests/functional/systemmetrics/Android.mk
index 9e797fb..9d7e51a 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/tests/functional/systemmetrics/Android.mk
@@ -1,4 +1,3 @@
-#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,17 +11,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+
LOCAL_PATH := $(call my-dir)
-
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+
+LOCAL_PACKAGE_NAME := SystemMetricsFunctionalTests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ metrics-helper-lib \
+ ub-uiautomator \
+ services.core \
+ legacy-android-test
-include $(BUILD_STATIC_JAVA_LIBRARY)
+#LOCAL_SDK_VERSION := current
-######################################
+LOCAL_COMPATIBILITY_SUITE := device-tests
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_PACKAGE)
diff --git a/tests/functional/systemmetrics/AndroidManifest.xml b/tests/functional/systemmetrics/AndroidManifest.xml
new file mode 100644
index 0000000..aacbb80
--- /dev/null
+++ b/tests/functional/systemmetrics/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemmetrics.functional" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="24" />
+
+ <uses-permission android:name="android.permission.READ_LOGS" />
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="Android System Metrics Functional Tests"
+ android:targetPackage="com.android.systemmetrics.functional" />
+
+</manifest>
diff --git a/tests/functional/systemmetrics/AndroidTest.xml b/tests/functional/systemmetrics/AndroidTest.xml
new file mode 100644
index 0000000..86d342f
--- /dev/null
+++ b/tests/functional/systemmetrics/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Android System Metrics Functional Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SystemMetricsFunctionalTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="SystemMetricsFunctionalTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.systemmetrics.functional" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/systemmetrics/src/com/android/systemmetrics/functional/AppStartTests.java b/tests/functional/systemmetrics/src/com/android/systemmetrics/functional/AppStartTests.java
new file mode 100644
index 0000000..43c6681
--- /dev/null
+++ b/tests/functional/systemmetrics/src/com/android/systemmetrics/functional/AppStartTests.java
@@ -0,0 +1,119 @@
+/*
+ * 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.systemmetrics.functional;
+
+import android.content.Context;
+import android.content.Intent;
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
+import android.os.SystemClock;
+import android.support.test.metricshelper.MetricsAsserts;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.telecom.Log;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.Queue;
+
+/**
+ * runtest --path platform_testing/tests/functional/systemmetrics/
+ */
+public class AppStartTests extends InstrumentationTestCase {
+ private static final String LOG_TAG = AppStartTests.class.getSimpleName();
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+ private static final int LONG_TIMEOUT_MS = 2000;
+ private UiDevice mDevice = null;
+ private Context mContext;
+ private MetricsReader mMetricsReader;
+ private int mPreUptime;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mContext = getInstrumentation().getContext();
+ mDevice.setOrientationNatural();
+ mMetricsReader = new MetricsReader();
+ mMetricsReader.checkpoint(); // clear out old logs
+ mPreUptime = (int) (SystemClock.uptimeMillis() / 1000);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mDevice.unfreezeRotation();
+ mDevice.pressHome();
+ }
+
+ @MediumTest
+ public void testStartApp() throws Exception {
+ Context context = getInstrumentation().getContext();
+ Intent intent = context.getPackageManager().getLaunchIntentForPackage(SETTINGS_PACKAGE);
+
+ // Clear out any previous instances
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ assertNotNull("component name is null", intent.getComponent());
+ String className = intent.getComponent().getClassName();
+ String packageName = intent.getComponent().getPackageName();
+ assertTrue("className is empty", !TextUtils.isEmpty(className));
+ assertTrue("packageName is empty", !TextUtils.isEmpty(packageName));
+
+
+ context.startActivity(intent);
+ mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), LONG_TIMEOUT_MS);
+
+ int postUptime = (int) (SystemClock.uptimeMillis() / 1000);
+
+ Queue<LogMaker> startLogs = MetricsAsserts.findMatchingLogs(mMetricsReader,
+ new LogMaker(MetricsEvent.APP_TRANSITION));
+ boolean found = false;
+ for (LogMaker log : startLogs) {
+ String actualClassName = (String) log.getTaggedData(
+ MetricsEvent.FIELD_CLASS_NAME);
+ String actualPackageName = log.getPackageName();
+ if (className.equals(actualClassName) && packageName.equals(actualPackageName)) {
+ found = true;
+ int startUptime = ((Number)
+ log.getTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS))
+ .intValue();
+ assertTrue("must be either cold or warm launch",
+ MetricsEvent.TYPE_TRANSITION_COLD_LAUNCH == log.getType()
+ || MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH == log.getType());
+ assertTrue("reported uptime should be after the app was started",
+ mPreUptime <= startUptime);
+ assertTrue("reported uptime should be before assertion time",
+ startUptime <= postUptime);
+ assertNotNull("log should have delay",
+ log.getTaggedData(MetricsEvent.APP_TRANSITION_DELAY_MS));
+ assertEquals("transition should be started because of starting window",
+ 1 /* APP_TRANSITION_STARTING_WINDOW */, log.getSubtype());
+ assertNotNull("log should have starting window delay",
+ log.getTaggedData(MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS));
+ assertNotNull("log should have windows drawn delay",
+ log.getTaggedData(MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS));
+ }
+ }
+ assertTrue("did not find the app start start log for: "
+ + intent.getComponent().flattenToShortString(), found);
+ }
+}
diff --git a/tests/functional/testapks/applinktestapp/Android.mk b/tests/functional/testapks/applinktestapp/Android.mk
index 08c75f8..cc3831e 100644
--- a/tests/functional/testapks/applinktestapp/Android.mk
+++ b/tests/functional/testapks/applinktestapp/Android.mk
@@ -23,4 +23,7 @@
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := AppLinkTestApp
LOCAL_CERTIFICATE := platform
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/functional/testapks/permissiontestappmv1/Android.mk b/tests/functional/testapks/permissiontestappmv1/Android.mk
index 0bf4196..f9586be 100644
--- a/tests/functional/testapks/permissiontestappmv1/Android.mk
+++ b/tests/functional/testapks/permissiontestappmv1/Android.mk
@@ -11,4 +11,7 @@
$(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := PermissionTestAppMV1
LOCAL_CERTIFICATE := platform
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/UbSystemUiJankTests/Android.mk b/tests/jank/UbSystemUiJankTests/Android.mk
index eb5e92c..5741713 100644
--- a/tests/jank/UbSystemUiJankTests/Android.mk
+++ b/tests/jank/UbSystemUiJankTests/Android.mk
@@ -19,8 +19,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := ub-janktesthelper ub-uiautomator launcher-helper-lib timeresult-helper-lib
+LOCAL_STATIC_JAVA_LIBRARIES := ub-janktesthelper ub-uiautomator launcher-helper-lib \
+ timeresult-helper-lib sysui-helper
-LOCAL_SDK_VERSION := 21
+LOCAL_COMPATIBILITY_SUITE := device-tests
include $(BUILD_PACKAGE)
diff --git a/tests/jank/UbSystemUiJankTests/AndroidTest.xml b/tests/jank/UbSystemUiJankTests/AndroidTest.xml
new file mode 100644
index 0000000..cefe1c8
--- /dev/null
+++ b/tests/jank/UbSystemUiJankTests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs System UI Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push" value="/google/data/ro/teams/tradefed/testdata/browser/chrome-command-line->/data/local/chrome-command-line" />
+ <option name="push" value="/google/data/ro/teams/tradefed/testdata/browser/chrome-command-line->/data/local/tmp/chrome-command-line" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="UbSystemUiJankTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="UbSystemUiJankTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="android.platform.systemui.tests.jank" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/UbSystemUiJankTests/src/android/platform/systemui/tests/jank/SystemUiJankTests.java b/tests/jank/UbSystemUiJankTests/src/android/platform/systemui/tests/jank/SystemUiJankTests.java
index 62c4ebd..0193b80 100644
--- a/tests/jank/UbSystemUiJankTests/src/android/platform/systemui/tests/jank/SystemUiJankTests.java
+++ b/tests/jank/UbSystemUiJankTests/src/android/platform/systemui/tests/jank/SystemUiJankTests.java
@@ -16,17 +16,20 @@
package android.platform.systemui.tests.jank;
+import android.app.Notification.Action;
import android.app.Notification.Builder;
import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.graphics.Rect;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
import android.support.test.jank.GfxMonitor;
import android.support.test.jank.JankTest;
import android.support.test.jank.JankTestBase;
@@ -38,7 +41,8 @@
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
-import android.util.Log;
+import android.system.helpers.LockscreenHelper;
+import android.system.helpers.OverviewHelper;
import android.widget.Button;
import android.widget.ImageView;
@@ -55,6 +59,8 @@
private static final int SWIPE_MARGIN = 5;
private static final int DEFAULT_FLING_STEPS = 5;
private static final int DEFAULT_SCROLL_STEPS = 15;
+ private static final int BRIGHTNESS_SCROLL_STEPS = 30;
+
// short transitions should be repeated within the test function, otherwise frame stats
// captured are not really meaningful in a statistical sense
private static final int INNER_LOOP = 3;
@@ -72,6 +78,7 @@
android.R.drawable.stat_notify_voicemail,
};
private static final String NOTIFICATION_TEXT = "Lorem ipsum dolor sit amet";
+ private static final String REPLY_TEXT = "REPLY";
private static final File TIMESTAMP_FILE = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath(), "autotester.log");
private static final File RESULTS_FILE = new File(Environment.getExternalStorageDirectory()
@@ -79,17 +86,40 @@
private static final String GMAIL_PACKAGE_NAME = "com.google.android.gm";
private static final String DISABLE_COMMAND = "pm disable-user ";
private static final String ENABLE_COMMAND = "pm enable ";
+ private static final String PULSE_COMMAND = "am broadcast -a com.android.systemui.doze.pulse";
+ private static final String PIN = "1234";
+
+ /**
+ * Group mode: Let the system auto-group our notifications. This is required so we don't screw
+ * up jank numbers for our existing notification list pull test.
+ */
+ private static final int GROUP_MODE_LEGACY = 0;
+
+ /**
+ * Group mode: Group the notifications.
+ */
+ private static final int GROUP_MODE_GROUPED = 1;
+
+ /**
+ * Group mode: All notifications should be separate
+ */
+ private static final int GROUP_MODE_UNGROUPED = 2;
private UiDevice mDevice;
- private List<String> mLaunchedPackages = new ArrayList<>();
+ private ArrayList<String> mLaunchedPackages;
+ private NotificationManager mNotificationManager;
- public void setUp() {
+ public void setUp() throws Exception {
mDevice = UiDevice.getInstance(getInstrumentation());
try {
mDevice.setOrientationNatural();
} catch (RemoteException e) {
throw new RuntimeException("failed to freeze device orientaion", e);
}
+ mNotificationManager = getInstrumentation().getContext().getSystemService(
+ NotificationManager.class);
+ InstrumentationRegistry.registerInstance(getInstrumentation(), getArguments());
+ blockNotifications();
}
public void goHome() {
@@ -100,32 +130,12 @@
@Override
protected void tearDown() throws Exception {
mDevice.unfreezeRotation();
+ unblockNotifications();
super.tearDown();
}
public void populateRecentApps() throws IOException {
- PackageManager pm = getInstrumentation().getContext().getPackageManager();
- List<PackageInfo> packages = pm.getInstalledPackages(0);
- mLaunchedPackages.clear();
- for (PackageInfo pkg : packages) {
- if (pkg.packageName.equals(getInstrumentation().getTargetContext().getPackageName())) {
- continue;
- }
- Intent intent = pm.getLaunchIntentForPackage(pkg.packageName);
- if (intent == null) {
- continue;
- }
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getInstrumentation().getTargetContext().startActivity(intent);
- SystemClock.sleep(5000);
- mLaunchedPackages.add(pkg.packageName);
- }
-
- // Close any crash dialogs
- while (mDevice.hasObject(By.textContains("has stopped"))) {
- mDevice.findObject(By.text("Close")).clickAndWait(Until.newWindow(), 2000);
- }
+ mLaunchedPackages = OverviewHelper.getInstance().populateManyRecentApps();
TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
}
@@ -133,13 +143,7 @@
public void forceStopPackages(Bundle metrics) throws IOException {
TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
- for (String pkg : mLaunchedPackages) {
- try {
- mDevice.executeShellCommand("am force-stop " + pkg);
- } catch (IOException e) {
- Log.w(LOG_TAG, "exeception while force stopping package " + pkg, e);
- }
- }
+ OverviewHelper.getInstance().forceStopPackages(mLaunchedPackages);
goHome();
TimeResultLogger.writeResultToFile(String.format("%s-%s",
getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
@@ -160,8 +164,7 @@
mDevice.waitForIdle();
}
- public void prepareNotifications() throws Exception {
- blockNotifications();
+ public void prepareNotifications(int groupMode) throws Exception {
goHome();
mDevice.openNotification();
SystemClock.sleep(100);
@@ -175,19 +178,66 @@
}
mDevice.pressHome();
mDevice.waitForIdle();
+ postNotifications(groupMode);
+ mDevice.waitForIdle();
+ }
+
+ private void postNotifications(int groupMode) {
+ postNotifications(groupMode, 100, -1);
+ }
+
+ private void postNotifications(int groupMode, int sleepBetweenDuration, int maxCount) {
Builder builder = new Builder(getInstrumentation().getTargetContext())
.setContentTitle(NOTIFICATION_TEXT);
- NotificationManager nm = (NotificationManager) getInstrumentation().getTargetContext()
- .getSystemService(Context.NOTIFICATION_SERVICE);
- for (int icon : ICONS) {
+ if (groupMode == GROUP_MODE_GROUPED) {
+ builder.setGroup("key");
+ }
+ boolean first = true;
+ for (int i = 0; i < ICONS.length; i++) {
+ if (maxCount != -1 && i >= maxCount) {
+ break;
+ }
+ int icon = ICONS[i];
+ if (first && groupMode == GROUP_MODE_GROUPED) {
+ builder.setGroupSummary(true);
+ } else {
+ builder.setGroupSummary(false);
+ }
+ if (groupMode == GROUP_MODE_UNGROUPED) {
+ builder.setGroup(Integer.toString(icon));
+ }
builder.setContentText(Integer.toHexString(icon))
.setSmallIcon(icon);
- nm.notify(icon, builder.build());
- SystemClock.sleep(100);
+ mNotificationManager.notify(icon, builder.build());
+ SystemClock.sleep(sleepBetweenDuration);
+ first = false;
}
- mDevice.waitForIdle();
- TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
- getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ private void postInlineReplyNotification() {
+ RemoteInput remoteInput = new RemoteInput.Builder("reply")
+ .setLabel(NOTIFICATION_TEXT)
+ .build();
+ Context context = getInstrumentation().getTargetContext();
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0 , new Intent(),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ Icon icon = Icon.createWithResource(context, ICONS[0]);
+ Action action = new Action.Builder(icon, REPLY_TEXT, pendingIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+ Builder builder = new Builder(getInstrumentation().getTargetContext())
+ .setContentTitle(NOTIFICATION_TEXT)
+ .setContentText(NOTIFICATION_TEXT)
+ .setSmallIcon(ICONS[0])
+ .addAction(action);
+ mNotificationManager.notify(0, builder.build());
+ }
+
+ private void cancelNotifications(int sleepBetweenDuration) {
+ for (int icon : ICONS) {
+ mNotificationManager.cancel(icon);
+ SystemClock.sleep(sleepBetweenDuration);
+ }
}
public void blockNotifications() throws Exception {
@@ -198,21 +248,13 @@
mDevice.executeShellCommand(ENABLE_COMMAND + GMAIL_PACKAGE_NAME);
}
- public void cancelNotifications(Bundle metrics) throws Exception {
- unblockNotifications();
- TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
- getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
- NotificationManager nm = (NotificationManager) getInstrumentation().getTargetContext()
- .getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancelAll();
- TimeResultLogger.writeResultToFile(String.format("%s-%s",
- getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
- super.afterTest(metrics);
+ public void cancelNotifications() throws Exception {
+ mNotificationManager.cancelAll();
}
/** Starts from the bottom of the recent apps list and measures jank while flinging up. */
@JankTest(beforeTest = "populateRecentApps", beforeLoop = "resetRecentsToBottom",
- afterTest = "forceStopPackages", expectedFrames = 100)
+ afterTest = "forceStopPackages", expectedFrames = 100, defaultIterationCount = 5)
@GfxMonitor(processName = SYSTEMUI_PACKAGE)
public void testRecentAppsFling() {
UiObject2 recents = mDevice.findObject(RECENTS);
@@ -228,6 +270,28 @@
}
}
+ /**
+ * Measures jank when dismissing a task in recents.
+ */
+ @JankTest(beforeTest = "populateRecentApps", beforeLoop = "resetRecentsToBottom",
+ afterTest = "forceStopPackages", expectedFrames = 10, defaultIterationCount = 5)
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testRecentAppsDismiss() {
+ // Wait until dismiss views are fully faded in.
+ mDevice.findObject(new UiSelector().resourceId("com.android.systemui:id/dismiss_task"))
+ .waitForExists(5000);
+ for (int i = 0; i < INNER_LOOP; i++) {
+ List<UiObject2> dismissViews = mDevice.findObjects(
+ By.res(SYSTEMUI_PACKAGE, "dismiss_task"));
+ if (dismissViews.size() == 0) {
+ fail("Unable to find dismiss view");
+ }
+ dismissViews.get(dismissViews.size() - 1).click();
+ mDevice.waitForIdle();
+ SystemClock.sleep(500);
+ }
+ }
+
private void swipeDown() {
mDevice.swipe(mDevice.getDisplayWidth() / 2,
SWIPE_MARGIN, mDevice.getDisplayWidth() / 2,
@@ -243,9 +307,25 @@
DEFAULT_SCROLL_STEPS);
}
+ public void beforeNotificationListPull() throws Exception {
+ prepareNotifications(GROUP_MODE_LEGACY);
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterNotificationListPull(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ cancelNotifications();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
/** Measures jank while pulling down the notification list */
@JankTest(expectedFrames = 100,
- beforeTest = "prepareNotifications", afterTest = "cancelNotifications")
+ defaultIterationCount = 5,
+ beforeTest = "beforeNotificationListPull", afterTest = "afterNotificationListPull")
@GfxMonitor(processName = SYSTEMUI_PACKAGE)
public void testNotificationListPull() {
for (int i = 0; i < INNER_LOOP; i++) {
@@ -256,10 +336,31 @@
}
}
+ public void beforeNotificationListPull_manyNotifications() throws Exception {
+ prepareNotifications(GROUP_MODE_UNGROUPED);
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ /** Measures jank while pulling down the notification list with many notifications */
+ @JankTest(expectedFrames = 100,
+ defaultIterationCount = 5,
+ beforeTest = "beforeNotificationListPull_manyNotifications",
+ afterTest = "afterNotificationListPull")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testNotificationListPull_manyNotifications() {
+ for (int i = 0; i < INNER_LOOP; i++) {
+ swipeDown();
+ mDevice.waitForIdle();
+ swipeUp();
+ mDevice.waitForIdle();
+ }
+ }
+
public void beforeQuickSettings() throws Exception {
// Make sure we have some notifications.
- prepareNotifications();
+ prepareNotifications(GROUP_MODE_UNGROUPED);
mDevice.openNotification();
SystemClock.sleep(100);
mDevice.waitForIdle();
@@ -270,7 +371,7 @@
public void afterQuickSettings(Bundle metrics) throws Exception {
TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
- cancelNotifications(metrics);
+ cancelNotifications();
mDevice.pressHome();
TimeResultLogger.writeResultToFile(String.format("%s-%s",
getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
@@ -279,6 +380,7 @@
/** Measures jank while pulling down the quick settings */
@JankTest(expectedFrames = 100,
+ defaultIterationCount = 5,
beforeTest = "beforeQuickSettings", afterTest = "afterQuickSettings")
@GfxMonitor(processName = SYSTEMUI_PACKAGE)
public void testQuickSettingsPull() throws Exception {
@@ -292,5 +394,411 @@
mDevice.waitForIdle();
}
}
-}
+ public void beforeUnlock() throws Exception {
+
+ // Make sure we have some notifications.
+ prepareNotifications(GROUP_MODE_UNGROUPED);
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterUnlock(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ cancelNotifications();
+ mDevice.pressHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measure jank while unlocking the phone.
+ */
+ @JankTest(expectedFrames = 100,
+ defaultIterationCount = 5,
+ beforeTest = "beforeUnlock", afterTest = "afterUnlock")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testUnlock() throws Exception {
+ for (int i = 0; i < INNER_LOOP; i++) {
+ mDevice.sleep();
+ // Make sure we don't trigger the camera launch double-tap shortcut
+ SystemClock.sleep(300);
+ mDevice.wakeUp();
+ swipeUp();
+ mDevice.waitForIdle();
+ }
+ }
+
+ public void beforeExpand() throws Exception {
+ prepareNotifications(GROUP_MODE_GROUPED);
+ mDevice.openNotification();
+ SystemClock.sleep(100);
+ mDevice.waitForIdle();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterExpand(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ cancelNotifications();
+ mDevice.pressHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank while expending a group notification.
+ */
+ @JankTest(expectedFrames = 100,
+ defaultIterationCount = 5,
+ beforeTest = "beforeExpand", afterTest = "afterExpand")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testExpandGroup() throws Exception {
+ UiObject expandButton = mDevice.findObject(
+ new UiSelector().description("Expand button"));
+ for (int i = 0; i < INNER_LOOP; i++) {
+ expandButton.click();
+ mDevice.waitForIdle();
+ expandButton.click();
+ mDevice.waitForIdle();
+ }
+ }
+
+ private void scrollDown() {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2,
+ mDevice.getDisplayHeight() / 2,
+ mDevice.getDisplayWidth() / 2,
+ SWIPE_MARGIN,
+ DEFAULT_SCROLL_STEPS);
+ }
+
+ public void beforeClearAll() throws Exception {
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void beforeClearAllLoop() throws Exception {
+ postNotifications(GROUP_MODE_UNGROUPED);
+ mDevice.openNotification();
+ SystemClock.sleep(100);
+ mDevice.waitForIdle();
+ }
+
+ public void afterClearAll(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when clicking the "clear all" button in the notification shade.
+ */
+ @JankTest(expectedFrames = 10,
+ defaultIterationCount = 5,
+ beforeTest = "beforeClearAll",
+ beforeLoop = "beforeClearAllLoop",
+ afterTest = "afterClearAll")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testClearAll() throws Exception {
+ UiObject clearAll =
+ mDevice.findObject(new UiSelector().className(Button.class).text("CLEAR ALL"));
+ while (!clearAll.exists()) {
+ scrollDown();
+ }
+ clearAll.click();
+ mDevice.waitForIdle();
+ }
+
+ public void beforeChangeBrightness() throws Exception {
+ mDevice.openQuickSettings();
+
+ // Wait until animation is starting.
+ SystemClock.sleep(200);
+ mDevice.waitForIdle();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterChangeBrightness(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ mDevice.pressHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when changing screen brightness
+ */
+ @JankTest(expectedFrames = 10,
+ defaultIterationCount = 5,
+ beforeTest = "beforeChangeBrightness",
+ afterTest = "afterChangeBrightness")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testChangeBrightness() throws Exception {
+ UiObject2 brightness = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, "slider"));
+ Rect bounds = brightness.getVisibleBounds();
+ for (int i = 0; i < INNER_LOOP; i++) {
+ mDevice.swipe(bounds.left, bounds.centerY(),
+ bounds.right, bounds.centerY(), BRIGHTNESS_SCROLL_STEPS);
+
+ // Make sure animation is completing.
+ SystemClock.sleep(500);
+ mDevice.waitForIdle();
+ }
+ }
+
+ public void beforeNotificationAppear() throws Exception {
+ mDevice.openNotification();
+
+ // Wait until animation is starting.
+ SystemClock.sleep(200);
+ mDevice.waitForIdle();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterNotificationAppear(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ mDevice.pressHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when a notification is appearing.
+ */
+ @JankTest(expectedFrames = 10,
+ defaultIterationCount = 5,
+ beforeTest = "beforeNotificationAppear",
+ afterTest = "afterNotificationAppear")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testNotificationAppear() throws Exception {
+ for (int i = 0; i < INNER_LOOP; i++) {
+ postNotifications(GROUP_MODE_UNGROUPED, 250, 5);
+ mDevice.waitForIdle();
+ cancelNotifications(250);
+ mDevice.waitForIdle();
+ }
+ }
+
+ public void beforeCameraFromLockscreen() throws Exception {
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void beforeCameraFromLockscreenLoop() throws Exception {
+ mDevice.pressHome();
+ mDevice.sleep();
+ // Make sure we don't trigger the camera launch double-tap shortcut
+ SystemClock.sleep(300);
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ }
+
+ public void afterCameraFromLockscreen(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ mDevice.pressHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when launching the camera from lockscreen.
+ */
+ @JankTest(expectedFrames = 10,
+ defaultIterationCount = 5,
+ beforeTest = "beforeCameraFromLockscreen",
+ afterTest = "afterCameraFromLockscreen",
+ beforeLoop = "beforeCameraFromLockscreenLoop")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testCameraFromLockscreen() throws Exception {
+ mDevice.swipe(mDevice.getDisplayWidth() - SWIPE_MARGIN,
+ mDevice.getDisplayHeight() - SWIPE_MARGIN, SWIPE_MARGIN, SWIPE_MARGIN,
+ DEFAULT_SCROLL_STEPS);
+ mDevice.waitForIdle();
+ }
+
+ public void beforeAmbientWakeUp() throws Exception {
+ postNotifications(GROUP_MODE_UNGROUPED);
+ mDevice.sleep();
+ SystemClock.sleep(1000);
+ mDevice.waitForIdle();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterAmbientWakeUp(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ cancelNotifications();
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ mDevice.pressMenu();
+ mDevice.waitForIdle();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when waking up from ambient (doze) display.
+ */
+ @JankTest(expectedFrames = 30,
+ defaultIterationCount = 5,
+ beforeTest = "beforeAmbientWakeUp",
+ afterTest = "afterAmbientWakeUp")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testAmbientWakeUp() throws Exception {
+ for (int i = 0; i < INNER_LOOP; i++) {
+ mDevice.executeShellCommand(PULSE_COMMAND);
+ SystemClock.sleep(100);
+ mDevice.waitForIdle();
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ mDevice.sleep();
+ SystemClock.sleep(1000);
+ mDevice.waitForIdle();
+ }
+ }
+
+ public void beforeGoToFullShade() throws Exception {
+ postNotifications(GROUP_MODE_UNGROUPED);
+ mDevice.sleep();
+
+ // Don't trigger camera launch gesture
+ SystemClock.sleep(300);
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterGoToFullShade(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ mDevice.pressMenu();
+ mDevice.waitForIdle();
+ cancelNotifications();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when tragging down on a notification on the lockscreen to go to the full shade.
+ */
+ @JankTest(expectedFrames = 100,
+ defaultIterationCount = 5,
+ beforeTest = "beforeGoToFullShade",
+ afterTest = "afterGoToFullShade")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testGoToFullShade() throws Exception {
+ for (int i = 0; i < INNER_LOOP; i++) {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2,
+ mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() - SWIPE_MARGIN,
+ DEFAULT_SCROLL_STEPS);
+ mDevice.waitForIdle();
+ mDevice.click(mDevice.getDisplayWidth() / 4, mDevice.getDisplayHeight() - SWIPE_MARGIN);
+ mDevice.waitForIdle();
+ }
+ }
+
+ public void beforeInlineReply() throws Exception {
+ postInlineReplyNotification();
+ mDevice.openNotification();
+
+ // Wait until animation kicks in
+ SystemClock.sleep(100);
+ mDevice.waitForIdle();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterInlineReply(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ cancelNotifications();
+ goHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when clicking "reply" on a notification that supports inline reply.
+ */
+ @JankTest(expectedFrames = 50,
+ defaultIterationCount = 5,
+ beforeTest = "beforeInlineReply",
+ afterTest = "afterInlineReply")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testInlineReply() throws Exception {
+ UiObject2 replyButton = mDevice.findObject(By.clazz(Button.class).text(REPLY_TEXT));
+ for (int i = 0; i < INNER_LOOP; i++) {
+ replyButton.click();
+ mDevice.waitForIdle();
+ Thread.sleep(1000);
+ mDevice.pressBack();
+ mDevice.waitForIdle();
+ mDevice.pressBack();
+ mDevice.waitForIdle();
+ }
+ }
+
+ public void beforePinAppearance() throws Exception {
+ LockscreenHelper.getInstance().setScreenLockViaShell(PIN, LockscreenHelper.MODE_PIN);
+ goHome();
+ mDevice.sleep();
+ SystemClock.sleep(300);
+ mDevice.wakeUp();
+ TimeResultLogger.writeTimeStampLogStart(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ }
+
+ public void afterPinAppearanceLoop() throws Exception {
+ mDevice.pressBack();
+ mDevice.waitForIdle();
+ }
+
+ public void afterPinAppearance(Bundle metrics) throws Exception {
+ TimeResultLogger.writeTimeStampLogEnd(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), TIMESTAMP_FILE);
+ LockscreenHelper.getInstance().unlockScreen(PIN);
+ LockscreenHelper.getInstance().removeScreenLockViaShell(PIN);
+ mDevice.pressHome();
+ TimeResultLogger.writeResultToFile(String.format("%s-%s",
+ getClass().getSimpleName(), getName()), RESULTS_FILE, metrics);
+ super.afterTest(metrics);
+ }
+
+ /**
+ * Measures jank when launching the camera from lockscreen.
+ */
+ @JankTest(expectedFrames = 30,
+ defaultIterationCount = 5,
+ beforeTest = "beforePinAppearance",
+ afterTest = "afterPinAppearance",
+ afterLoop = "afterPinAppearanceLoop")
+ @GfxMonitor(processName = SYSTEMUI_PACKAGE)
+ public void testPinAppearance() throws Exception {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() - SWIPE_MARGIN,
+ mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2,
+ DEFAULT_SCROLL_STEPS);
+ mDevice.waitForIdle();
+ String command = String.format("%s %s %s", "input", "text", PIN);
+ mDevice.executeShellCommand(command);
+ mDevice.waitForIdle();
+ }
+}
diff --git a/tests/jank/androidtvjanktests/Android.mk b/tests/jank/androidtvjanktests/Android.mk
index f01074f..70ebaf3 100644
--- a/tests/jank/androidtvjanktests/Android.mk
+++ b/tests/jank/androidtvjanktests/Android.mk
@@ -19,8 +19,11 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := ub-janktesthelper ub-uiautomator timeresult-helper-lib
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ub-janktesthelper ub-uiautomator timeresult-helper-lib dpad-util
LOCAL_SDK_VERSION := 21
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/jank/androidtvjanktests/AndroidTest.xml b/tests/jank/androidtvjanktests/AndroidTest.xml
new file mode 100644
index 0000000..db45c38
--- /dev/null
+++ b/tests/jank/androidtvjanktests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Platform Android TV Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="AndroidTVJankTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="AndroidTVJankTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.androidtv.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemAppJankTests.java b/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemAppJankTests.java
index ccd913c..f6747d0 100644
--- a/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemAppJankTests.java
+++ b/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemAppJankTests.java
@@ -24,12 +24,11 @@
import android.support.test.jank.JankTest;
import android.support.test.jank.JankTestBase;
import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.Until;
-import android.util.Log;
+
import java.io.IOException;
/*
diff --git a/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemUiJankTests.java b/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemUiJankTests.java
index 444c19b..3f6bf0a 100644
--- a/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemUiJankTests.java
+++ b/tests/jank/androidtvjanktests/src/com/android/androidtv/janktests/SystemUiJankTests.java
@@ -17,17 +17,21 @@
package com.android.androidtv.janktests;
import android.os.Bundle;
-import android.os.SystemClock;
+import android.platform.test.utils.DPadUtil;
import android.support.test.jank.GfxMonitor;
import android.support.test.jank.JankTest;
import android.support.test.jank.JankTestBase;
import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Direction;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.Until;
import android.util.Log;
+
+import junit.framework.Assert;
+
import java.io.IOException;
/*
@@ -35,17 +39,19 @@
*/
public class SystemUiJankTests extends JankTestBase {
+ private static final String TAG = SystemUiJankTests.class.getSimpleName();
private static final int SHORT_TIMEOUT = 1000;
- private static final int LONG_TIMEOUT = 3000;
private static final int INNER_LOOP = 4;
- private static final int FLING_SPEED = 12000;
- private static final String LEANBACK_LAUNCHER = "com.google.android.leanbacklauncher";
+ private static final String TVLAUNCHER_PACKAGE = "com.google.android.tvlauncher";
private static final String SETTINGS_PACKAGE = "com.android.tv.settings";
+ private static final BySelector SELECTOR_TOP_ROW = By.res(TVLAUNCHER_PACKAGE, "top_row");
private UiDevice mDevice;
+ private DPadUtil mDPadUtil;
@Override
public void setUp() {
mDevice = UiDevice.getInstance(getInstrumentation());
+ mDPadUtil = new DPadUtil(getInstrumentation());
}
@Override
@@ -56,11 +62,16 @@
public void goHome() {
mDevice.pressHome();
// Ensure that Home screen is being displayed
- UiObject2 homeScreen = mDevice.wait(
- Until.findObject(By.scrollable(true).res(LEANBACK_LAUNCHER, "main_list_view")),
+ UiObject2 homeScreen = mDevice.wait(Until.findObject(
+ By.scrollable(true).res(TVLAUNCHER_PACKAGE, "home_view_container")),
SHORT_TIMEOUT);
}
+ public void goTopRow() {
+ Assert.assertNotNull(select(SELECTOR_TOP_ROW.hasDescendant(By.focused(true)), Direction.UP,
+ SHORT_TIMEOUT));
+ }
+
public void afterTestHomeScreenNavigation(Bundle metrics) throws IOException {
super.afterTest(metrics);
}
@@ -68,30 +79,20 @@
// Measures jank while scrolling down the Home screen
@JankTest(expectedFrames=100, beforeTest = "goHome",
afterTest="afterTestHomeScreenNavigation")
- @GfxMonitor(processName=LEANBACK_LAUNCHER)
+ @GfxMonitor(processName=TVLAUNCHER_PACKAGE)
public void testHomeScreenNavigation() throws UiObjectNotFoundException {
// We've already verified that Home screen is being displayed.
// Scroll up and down the home screen.
navigateDownAndUpCurrentScreen();
}
- // Navigates to the Settings row on the Home screen
- public void goToSettingsRow() {
+ // Navigates to the Settings button on the Top row
+ public void goToSettingsButton() {
// Navigate to Home screen and verify that it is being displayed.
goHome();
- mDevice.wait(Until.findObject(By.scrollable(true).res(LEANBACK_LAUNCHER, "main_list_view")),
- SHORT_TIMEOUT);
- // Look for the row with 'Settings' text.
- // This will ensure that the DPad focus is on the Settings icon.
- int count = 0;
- while (count <= 5 && !(mDevice.hasObject(By.res(LEANBACK_LAUNCHER, "label")
- .text("Settings")))) {
- mDevice.pressDPadDown();
- count++;
- }
- if (!mDevice.hasObject(By.res(LEANBACK_LAUNCHER, "label").text("Settings"))) {
- Log.d(LEANBACK_LAUNCHER, "Couldn't navigate to settings");
- }
+ goTopRow();
+ Assert.assertNotNull(selectBidirect(By.res(TVLAUNCHER_PACKAGE, "settings").focused(true),
+ Direction.RIGHT));
}
public void afterTestSettings(Bundle metrics) throws IOException {
@@ -101,22 +102,26 @@
}
// Measures jank while navigating to Settings from Home and back
- @JankTest(expectedFrames=100, beforeTest="goToSettingsRow",
+ @JankTest(expectedFrames=100, beforeTest="goToSettingsButton",
afterTest="afterTestSettings")
@GfxMonitor(processName=SETTINGS_PACKAGE)
public void testNavigateToSettings() throws UiObjectNotFoundException {
for (int i = 0; i < INNER_LOOP * 10; i++) {
// Press DPad center button to navigate to settings.
- mDevice.pressDPadCenter();
+ mDPadUtil.pressDPadCenter();
+ mDevice.wait(Until.hasObject(
+ By.res(SETTINGS_PACKAGE, "settings_preference_fragment_container")),
+ SHORT_TIMEOUT);
// Press Back button to go back to the Home screen with focus on Settings
- mDevice.pressBack();
+ mDPadUtil.pressBack();
}
}
// Navigates to the Settings Screen
public void goToSettings() {
- goToSettingsRow();
- mDevice.pressDPadCenter();
+ goHome();
+ goTopRow();
+ mDPadUtil.pressDPadCenter();
}
// Measures jank while scrolling on the Settings screen
@@ -125,7 +130,8 @@
@GfxMonitor(processName=SETTINGS_PACKAGE)
public void testSettingsScreenNavigation() throws UiObjectNotFoundException {
// Ensure that Settings screen is being displayed
- mDevice.wait(Until.findObject(By.scrollable(true).res(SETTINGS_PACKAGE, "container_list")),
+ mDevice.wait(Until.hasObject(
+ By.res(SETTINGS_PACKAGE, "settings_preference_fragment_container")),
SHORT_TIMEOUT);
navigateDownAndUpCurrentScreen();
}
@@ -133,11 +139,53 @@
public void navigateDownAndUpCurrentScreen() {
for (int i = 0; i < INNER_LOOP; i++) {
// Press DPad button down eight times in succession
- mDevice.pressDPadDown();
+ mDPadUtil.pressDPadDown();
}
for (int i = 0; i < INNER_LOOP; i++) {
// Press DPad button up eight times in succession.
- mDevice.pressDPadUp();
+ mDPadUtil.pressDPadUp();
}
}
+
+ /**
+ * Select an UI element with given {@link BySelector}. This action keeps moving a focus
+ * in a given {@link Direction} until it finds a matched element.
+ * @param selector the search criteria to match an element
+ * @param direction the direction to find
+ * @param timeoutMs timeout in milliseconds to select
+ * @return a UiObject2 which represents the matched element
+ */
+ public UiObject2 select(BySelector selector, Direction direction, long timeoutMs) {
+ UiObject2 focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_TIMEOUT);
+ while (!mDevice.wait(Until.hasObject(selector), timeoutMs)) {
+ Log.d(TAG, String.format("select: moving a focus from %s to %s", focus, direction));
+ UiObject2 focused = focus;
+ mDPadUtil.pressDPad(direction);
+ focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_TIMEOUT);
+ // Hack: A focus might be lost in some UI. Take one more step forward.
+ if (focus == null) {
+ mDPadUtil.pressDPad(direction);
+ focus = mDevice.wait(Until.findObject(By.focused(true)), SHORT_TIMEOUT);
+ }
+ // Check if it reaches to an end where it no longer moves a focus to next element
+ if (focused.equals(focus)) {
+ Log.d(TAG, "select: not found until it reaches to an end.");
+ return null;
+ }
+ }
+ Log.i(TAG, String.format("select: %s is selected", focus));
+ return focus;
+ }
+
+ /**
+ * Select an element with a given {@link BySelector} in both given direction and reverse.
+ */
+ public UiObject2 selectBidirect(BySelector selector, Direction direction) {
+ Log.d(TAG, String.format("selectBidirect [direction]%s", direction));
+ UiObject2 object = select(selector, direction, SHORT_TIMEOUT);
+ if (object == null) {
+ object = select(selector, Direction.reverse(direction), SHORT_TIMEOUT);
+ }
+ return object;
+ }
}
diff --git a/tests/jank/dialer/Android.mk b/tests/jank/dialer/Android.mk
index 4d16092..3d662c4 100644
--- a/tests/jank/dialer/Android.mk
+++ b/tests/jank/dialer/Android.mk
@@ -19,7 +19,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ub-uiautomator \
+ ub-janktesthelper \
+ legacy-android-test
LOCAK_SDK_VERSION := 22
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/dialer/AndroidTest.xml b/tests/jank/dialer/AndroidTest.xml
new file mode 100644
index 0000000..18b340b
--- /dev/null
+++ b/tests/jank/dialer/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Dialer Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="DialerJankTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="DialerJankTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.dialer.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/jankmicrobenchmark/Android.mk b/tests/jank/jankmicrobenchmark/Android.mk
index d0be009..fa0a5e8 100644
--- a/tests/jank/jankmicrobenchmark/Android.mk
+++ b/tests/jank/jankmicrobenchmark/Android.mk
@@ -23,5 +23,7 @@
LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/jankmicrobenchmark/AndroidTest.xml b/tests/jank/jankmicrobenchmark/AndroidTest.xml
new file mode 100644
index 0000000..1ecbd35
--- /dev/null
+++ b/tests/jank/jankmicrobenchmark/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Platform Benchmark Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="JankMicroBenchmarkTests.apk" />
+ <option name="test-file-name" value="ApiDemos.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="JankMicroBenchmarkTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.jankmicrobenchmark.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/notificationsgenerator_wear/Android.mk b/tests/jank/notificationsgenerator_wear/Android.mk
index 5352278..8b2ca7e 100644
--- a/tests/jank/notificationsgenerator_wear/Android.mk
+++ b/tests/jank/notificationsgenerator_wear/Android.mk
@@ -17,4 +17,6 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/jank/sysapp_wear/Android.mk b/tests/jank/sysapp_wear/Android.mk
index a8c27cd..743970f 100644
--- a/tests/jank/sysapp_wear/Android.mk
+++ b/tests/jank/sysapp_wear/Android.mk
@@ -23,4 +23,6 @@
LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/sysapp_wear/AndroidTest.xml b/tests/jank/sysapp_wear/AndroidTest.xml
new file mode 100644
index 0000000..2ca3ab4
--- /dev/null
+++ b/tests/jank/sysapp_wear/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Wearable Platform System Apps Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SysAppJankTestsWear.apk" />
+ <option name="test-file-name" value="NotificationsGeneratorWear.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
+ <option name="post-install-cmd" value="am start -n com.google.android.wearable.support/.CustomNotificationStubBroadcastActivity" />
+ </target_preparer>
+
+ <option name="post-boot-command" value="am broadcast -a com.google.android.clockwork.action.TEST_MODE" />
+ <option name="post-boot-command" value="setprop debug.hwui.filter_test_overhead true" />
+ <option name="post-boot-command" value="settings put secure accessibility_disable_animations 0" />
+
+ <option name="test-tag" value="SysAppJankTestsWear" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest">
+ <option name="package" value="com.android.wearable.sysapp.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/touch_latency_wear/Android.mk b/tests/jank/touch_latency_wear/Android.mk
index 71d53fc..13fd101 100644
--- a/tests/jank/touch_latency_wear/Android.mk
+++ b/tests/jank/touch_latency_wear/Android.mk
@@ -23,4 +23,6 @@
LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/touch_latency_wear/AndroidTest.xml b/tests/jank/touch_latency_wear/AndroidTest.xml
new file mode 100644
index 0000000..761c0d5
--- /dev/null
+++ b/tests/jank/touch_latency_wear/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Wearable Platform System Apps Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TouchLatencyJankTestWear.apk" />
+ <option name="test-file-name" value="TouchLatency.apk" />
+ </target_preparer>
+
+ <option name="post-boot-command" value="am broadcast -a com.google.android.clockwork.action.TEST_MODE" />
+ <option name="post-boot-command" value="setprop debug.hwui.filter_test_overhead true" />
+ <option name="post-boot-command" value="settings put secure accessibility_disable_animations 0" />
+
+ <option name="test-tag" value="BouncingBallJankTestsWear" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest">
+ <option name="package" value="com.android.wearable.touch.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/uibench/Android.mk b/tests/jank/uibench/Android.mk
index c46630b..be3d756 100644
--- a/tests/jank/uibench/Android.mk
+++ b/tests/jank/uibench/Android.mk
@@ -23,4 +23,6 @@
LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/uibench/AndroidTest.xml b/tests/jank/uibench/AndroidTest.xml
new file mode 100644
index 0000000..df06786
--- /dev/null
+++ b/tests/jank/uibench/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Platform UiBench Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="UiBenchJankTests.apk" />
+ <option name="test-file-name" value="UiBench.apk" />
+ </target_preparer>
+
+ <option name="post-boot-command" value="am broadcast -a com.google.android.clockwork.action.TEST_MODE" />
+ <option name="post-boot-command" value="setprop debug.hwui.filter_test_overhead true" />
+ <option name="post-boot-command" value="settings put secure accessibility_disable_animations 0" />
+
+ <option name="test-tag" value="UiBenchJankTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest">
+ <option name="package" value="com.android.uibench.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTests.java b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTests.java
index e258c13..989023d 100644
--- a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTests.java
+++ b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTests.java
@@ -18,6 +18,7 @@
import static com.android.uibench.janktests.UiBenchJankTestsHelper.EXPECTED_FRAMES;
import static com.android.uibench.janktests.UiBenchJankTestsHelper.PACKAGE_NAME;
+import static com.android.uibench.janktests.UiBenchJankTestsHelper.SHORT_EXPECTED_FRAMES;
import android.os.SystemClock;
import android.support.test.jank.GfxMonitor;
@@ -27,6 +28,7 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
import android.widget.ListView;
+
import junit.framework.Assert;
/**
@@ -53,121 +55,193 @@
super.tearDown();
}
- // Open dialog list from General
public void openDialogList() {
mHelper.launchActivity("DialogListActivity", "Dialog");
mHelper.mContents = mDevice.wait(Until.findObject(
- By.clazz(ListView.class)), mHelper.TIMEOUT);
+ By.clazz(ListView.class)), UiBenchJankTestsHelper.TIMEOUT);
Assert.assertNotNull("Dialog List View isn't found", mHelper.mContents);
}
- // Test dialoglist fling
@JankTest(beforeTest = "openDialogList", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testDialogListFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 1);
+ mHelper.flingUpDown(mHelper.mContents, 1);
}
- // Open Fullscreen Overdraw from General
public void openFullscreenOverdraw() {
mHelper.launchActivity("FullscreenOverdrawActivity",
"General/Fullscreen Overdraw");
}
- // Measure fullscreen overdraw jank
@JankTest(beforeTest = "openFullscreenOverdraw", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testFullscreenOverdraw() {
- SystemClock.sleep(mHelper.LONG_TIMEOUT * 5);
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
}
- // Open GL TextureView from General
public void openGLTextureView() {
mHelper.launchActivity("GlTextureViewActivity",
"General/GL TextureView");
}
- // Measure GL TextureView jank metrics
@JankTest(beforeTest = "openGLTextureView", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testGLTextureView() {
- SystemClock.sleep(mHelper.LONG_TIMEOUT * 5);
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
}
- // Open Invalidate from General
public void openInvalidate() {
mHelper.launchActivity("InvalidateActivity",
"General/Invalidate");
}
- // Measure Invalidate jank metrics
@JankTest(beforeTest = "openInvalidate", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testInvalidate() {
- SystemClock.sleep(mHelper.LONG_TIMEOUT * 5);
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
}
- // Open Trivial Animation from General
+ public void openInvalidateTree() {
+ mHelper.launchActivity("InvalidateTreeActivity",
+ "General/Invalidate Tree");
+ }
+
+ @JankTest(beforeTest = "openInvalidateTree", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testInvalidateTree() {
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
+ }
+
public void openTrivialAnimation() {
mHelper.launchActivity("TrivialAnimationActivity",
"General/Trivial Animation");
}
- // Measure TrivialAnimation jank metrics
@JankTest(beforeTest = "openTrivialAnimation", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testTrivialAnimation() {
- SystemClock.sleep(mHelper.LONG_TIMEOUT * 5);
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
}
- // Open Trivial listview from General
public void openTrivialListView() {
mHelper.launchActivityAndAssert("TrivialListActivity", "General/Trivial ListView");
}
- // Test trivialListView fling
@JankTest(beforeTest = "openTrivialListView", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testTrivialListViewFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 2);
+ mHelper.flingUpDown(mHelper.mContents, 2);
}
- // Open Trivial RecyclerView from General
+ public void openFadingEdgeListView() {
+ mHelper.launchActivityAndAssert("FadingEdgeListActivity", "General/Fading Edge ListView");
+ }
+
+ @JankTest(beforeTest = "openFadingEdgeListView", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testFadingEdgeListViewFling() {
+ mHelper.flingUpDown(mHelper.mContents, 2);
+ }
+
+ public void openSaveLayerInterleaveActivity() {
+ mHelper.launchActivityAndAssert("SaveLayerInterleaveActivity", "General/SaveLayer Animation");
+ }
+
+ @JankTest(beforeTest = "openSaveLayerInterleaveActivity", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testSaveLayerAnimation() {
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
+ }
+
public void openTrivialRecyclerView() {
mHelper.launchActivityAndAssert("TrivialRecyclerViewActivity",
"General/Trivial RecyclerView");
}
- // Test trivialRecyclerView fling
@JankTest(beforeTest = "openTrivialRecyclerView", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testTrivialRecyclerListViewFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 2);
+ mHelper.flingUpDown(mHelper.mContents, 2);
}
- // Open Slow Bind RecyclerView from General
public void openSlowBindRecyclerView() {
mHelper.launchActivityAndAssert("SlowBindRecyclerViewActivity",
"General/Slow Bind RecyclerView");
}
- // Test trivialRecyclerView fling
@JankTest(beforeTest = "openSlowBindRecyclerView", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testSlowBindRecyclerViewFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 2);
+ mHelper.flingUpDown(mHelper.mContents, 2);
}
- // Open Inflation Listview contents
+ public void openSlowNestedRecyclerView() {
+ mHelper.launchActivityAndAssert("SlowNestedRecyclerViewActivity",
+ "General/Slow Nested RecyclerView");
+ }
+
+ @JankTest(beforeTest = "openSlowNestedRecyclerView", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testSlowNestedRecyclerViewFling() {
+ mHelper.flingUpDown(mHelper.mContents, 2);
+ }
+
+ @JankTest(/* NOTE: relaunch between loops */ beforeLoop = "openSlowNestedRecyclerView",
+ expectedFrames = SHORT_EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testSlowNestedRecyclerViewInitialFling() {
+ mHelper.slowSingleFlingDown(mHelper.mContents);
+ }
+
public void openInflatingListView() {
mHelper.launchActivityAndAssert("InflatingListActivity",
"Inflation/Inflating ListView");
}
- // Test Inflating List View fling
@JankTest(beforeTest = "openInflatingListView", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testInflatingListViewFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 2);
+ mHelper.flingUpDown(mHelper.mContents, 2);
+ }
+
+ public void openNavigationDrawerActivity() {
+ mHelper.launchActivityAndAssert("NavigationDrawerActivity", "Navigation Drawer Activity");
+ mHelper.mContents.setGestureMargins(0, 0, 10, 0);
+ }
+
+ @JankTest(beforeTest = "openNavigationDrawerActivity", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testOpenNavigationDrawer() {
+ mHelper.swipeRightLeft(mHelper.mContents, 4);
+ }
+
+ public void openNotificationShade() {
+ mHelper.launchActivityAndAssert("NotificationShadeActivity", "Notification Shade");
+ }
+
+ @JankTest(beforeTest = "openNotificationShade", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testOpenNotificationShade() {
+ mHelper.flingUpDown(mHelper.mContents, 2, true);
+ }
+
+ public void openResizeHWLayer() {
+ mHelper.launchActivity("ResizeHWLayerActivity", "General/Resize HW Layer");
+ }
+
+ @JankTest(beforeTest = "openResizeHWLayer", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testResizeHWLayer() {
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
+ }
+
+ public void openClippedListView() {
+ mHelper.launchActivityAndAssert("ClippedListActivity", "General/Clipped ListView");
+ }
+
+ @JankTest(beforeTest = "openClippedListView", expectedFrames = EXPECTED_FRAMES)
+ @GfxMonitor(processName = PACKAGE_NAME)
+ public void testClippedListView() {
+ mHelper.swipeRightLeft(mHelper.mContents, 4);
}
}
diff --git a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTestsHelper.java b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTestsHelper.java
index a5e2af4..c2e17bd 100644
--- a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTestsHelper.java
+++ b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchJankTestsHelper.java
@@ -25,40 +25,51 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
+import android.util.DisplayMetrics;
+
import junit.framework.Assert;
+
/**
* Jank benchmark tests helper for UiBench app
*/
-
public class UiBenchJankTestsHelper {
public static final int LONG_TIMEOUT = 5000;
+ public static final int FULL_TEST_DURATION = 25000;
public static final int TIMEOUT = 250;
public static final int SHORT_TIMEOUT = 2000;
public static final int EXPECTED_FRAMES = 100;
+ /**
+ * Only to be used for initial-fling tests, or similar cases
+ * where perf during brief experience is important.
+ */
+ public static final int SHORT_EXPECTED_FRAMES = 30;
+
public static final String PACKAGE_NAME = "com.android.test.uibench";
- private static UiBenchJankTestsHelper mInstance;
- private static UiDevice mDevice;
+ private static final int SLOW_FLING_SPEED = 3000; // compare to UiObject2#DEFAULT_FLING_SPEED
+
+ private static UiBenchJankTestsHelper sInstance;
+ private UiDevice mDevice;
private Context mContext;
+ private DisplayMetrics mDisplayMetrics;
protected UiObject2 mContents;
private UiBenchJankTestsHelper(Context context, UiDevice device) {
mContext = context;
mDevice = device;
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
}
public static UiBenchJankTestsHelper getInstance(Context context, UiDevice device) {
- if (mInstance == null) {
- mInstance = new UiBenchJankTestsHelper(context, device);
+ if (sInstance == null) {
+ sInstance = new UiBenchJankTestsHelper(context, device);
}
- return mInstance;
+ return sInstance;
}
/**
* Launch activity using intent
- * @param activityName
- * @param verifyText
*/
public void launchActivity(String activityName, String verifyText) {
ComponentName cn = new ComponentName(PACKAGE_NAME,
@@ -84,21 +95,35 @@
/**
* To perform the fling down and up on given content for flingCount number
* of times
- * @param content
- * @param timeout
- * @param flingCount
*/
- public void flingUpDown(UiObject2 content, long timeout, int flingCount) {
- flingUpDown(content, timeout, flingCount, false);
+ public void flingUpDown(UiObject2 content, int flingCount) {
+ flingUpDown(content, flingCount, false);
}
- public void flingUpDown(UiObject2 content, long timeout, int flingCount, boolean reverse) {
+ public void flingUpDown(UiObject2 content, int flingCount, boolean reverse) {
for (int count = 0; count < flingCount; count++) {
- SystemClock.sleep(timeout);
+ SystemClock.sleep(SHORT_TIMEOUT);
content.fling(reverse ? Direction.UP : Direction.DOWN);
- SystemClock.sleep(timeout);
+ SystemClock.sleep(SHORT_TIMEOUT);
content.fling(reverse ? Direction.DOWN : Direction.UP);
}
}
+ /**
+ * To perform the swipe right and left on given content for swipeCount number
+ * of times
+ */
+ public void swipeRightLeft(UiObject2 content, int swipeCount) {
+ for (int count = 0; count < swipeCount; count++) {
+ SystemClock.sleep(SHORT_TIMEOUT);
+ content.swipe(Direction.RIGHT, 1);
+ SystemClock.sleep(SHORT_TIMEOUT);
+ content.swipe(Direction.LEFT, 1);
+ }
+ }
+
+ public void slowSingleFlingDown(UiObject2 content) {
+ SystemClock.sleep(SHORT_TIMEOUT);
+ content.fling(Direction.DOWN, (int)(SLOW_FLING_SPEED * mDisplayMetrics.density));
+ }
}
diff --git a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchRenderingJankTests.java b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchRenderingJankTests.java
index c825116..7a088f0 100644
--- a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchRenderingJankTests.java
+++ b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchRenderingJankTests.java
@@ -63,7 +63,7 @@
@JankTest(beforeTest = "openBitmapUpload", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testBitmapUploadJank() {
- SystemClock.sleep(mHelper.LONG_TIMEOUT * 5);
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
}
// Open Shadow Grid
@@ -79,7 +79,7 @@
@JankTest(beforeTest = "openRenderingList", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testShadowGridListFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 1);
+ mHelper.flingUpDown(mHelper.mContents, 1);
}
}
diff --git a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchTextJankTests.java b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchTextJankTests.java
index b730aec..64b786e 100644
--- a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchTextJankTests.java
+++ b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchTextJankTests.java
@@ -63,7 +63,7 @@
@JankTest(beforeTest = "openEditTextTyping", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testEditTextTyping() {
- SystemClock.sleep(mHelper.LONG_TIMEOUT * 2);
+ SystemClock.sleep(UiBenchJankTestsHelper.FULL_TEST_DURATION);
}
// Open Layout Cache High Hitrate
@@ -80,7 +80,7 @@
@JankTest(beforeTest = "openLayoutCacheHighHitrate", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testLayoutCacheHighHitrateFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 3);
+ mHelper.flingUpDown(mHelper.mContents, 3);
}
// Open Layout Cache Low Hitrate
@@ -97,7 +97,7 @@
@JankTest(beforeTest = "openLayoutCacheLowHitrate", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testLayoutCacheLowHitrateFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 3);
+ mHelper.flingUpDown(mHelper.mContents, 3);
}
}
diff --git a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchWebView.java b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchWebView.java
index 659577c..28c2ed3 100644
--- a/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchWebView.java
+++ b/tests/jank/uibench/src/com/android/uibench/janktests/UiBenchWebView.java
@@ -62,7 +62,7 @@
@JankTest(beforeTest = "openScrollableWebView", expectedFrames = EXPECTED_FRAMES)
@GfxMonitor(processName = PACKAGE_NAME)
public void testWebViewFling() {
- mHelper.flingUpDown(mHelper.mContents, mHelper.SHORT_TIMEOUT, 1);
+ mHelper.flingUpDown(mHelper.mContents, 1);
}
}
diff --git a/tests/jank/uibench_wear/Android.mk b/tests/jank/uibench_wear/Android.mk
index b21a640..dff9f23 100644
--- a/tests/jank/uibench_wear/Android.mk
+++ b/tests/jank/uibench_wear/Android.mk
@@ -23,4 +23,6 @@
LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/uibench_wear/AndroidTest.xml b/tests/jank/uibench_wear/AndroidTest.xml
new file mode 100644
index 0000000..3d1af6a
--- /dev/null
+++ b/tests/jank/uibench_wear/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Wearable Platform UiBench Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="UiBenchJankTestsWear.apk" />
+ <option name="test-file-name" value="UiBench.apk" />
+ </target_preparer>
+
+ <option name="post-boot-command" value="am broadcast -a com.google.android.clockwork.action.TEST_MODE" />
+ <option name="post-boot-command" value="setprop debug.hwui.filter_test_overhead true" />
+ <option name="post-boot-command" value="settings put secure accessibility_disable_animations 0" />
+
+ <option name="test-tag" value="UiBenchJankTestsWear" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest">
+ <option name="package" value="com.android.wearable.uibench.janktests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/jank/webview/Android.mk b/tests/jank/webview/Android.mk
index 0c2d128..ccae53b 100644
--- a/tests/jank/webview/Android.mk
+++ b/tests/jank/webview/Android.mk
@@ -19,8 +19,13 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ub-uiautomator \
+ ub-janktesthelper \
+ legacy-android-test
LOCAK_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/jank/webview/AndroidTest.xml b/tests/jank/webview/AndroidTest.xml
new file mode 100644
index 0000000..81fd1cd
--- /dev/null
+++ b/tests/jank/webview/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Chromium Jank Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="UbWebViewJankTests.apk" />
+ </target_preparer>
+
+ <option name="post-boot-command" value="am broadcast -a com.google.android.clockwork.action.TEST_MODE" />
+ <option name="post-boot-command" value="setprop debug.hwui.filter_test_overhead true" />
+ <option name="post-boot-command" value="settings put secure accessibility_disable_animations 0" />
+
+ <option name="test-tag" value="UbWebViewJankTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest">
+ <option name="package" value="com.android.webview.chromium.tests.jank" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/libraries/base-app-helpers/Android.mk b/tests/perf/BootHelperApp/Android.mk
similarity index 60%
copy from libraries/base-app-helpers/Android.mk
copy to tests/perf/BootHelperApp/Android.mk
index 9e797fb..221df3d 100644
--- a/libraries/base-app-helpers/Android.mk
+++ b/tests/perf/BootHelperApp/Android.mk
@@ -1,28 +1,30 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright 2016 Google Inc. All Rights Reserved.
#
# 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
+# 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)
+LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := base-app-helpers
-LOCAL_STATIC_JAVA_LIBRARIES := dpad-util
-LOCAL_JAVA_LIBRARIES := ub-uiautomator launcher-helper-lib
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_PACKAGE_NAME := BootHelperApp
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
-######################################
+LOCAL_COMPATIBILITY_SUITE := device-tests
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_PACKAGE)
+
diff --git a/tests/perf/BootHelperApp/AndroidManifest.xml b/tests/perf/BootHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..4539d32
--- /dev/null
+++ b/tests/perf/BootHelperApp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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"
+ package="com.android.boothelper"
+ android:sharedUserId="com.android.boothelper"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.boothelper" />
+
+ <application>
+ <activity android:name=".AwareActivity"
+ android:directBootAware="true"
+ android:exported="true">
+ </activity>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/tests/perf/BootHelperApp/AndroidTest.xml b/tests/perf/BootHelperApp/AndroidTest.xml
new file mode 100644
index 0000000..71f1288
--- /dev/null
+++ b/tests/perf/BootHelperApp/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs BootHelperApp Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="BootHelperApp.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="BootHelperApp" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.boothelper" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateTest.java b/tests/perf/BootHelperApp/src/com/android/boothelper/AwareActivity.java
similarity index 61%
rename from tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateTest.java
rename to tests/perf/BootHelperApp/src/com/android/boothelper/AwareActivity.java
index 4d5acec..467f203 100644
--- a/tests/functional/otatests/src/com/android/functional/otatests/SystemUpdateTest.java
+++ b/tests/perf/BootHelperApp/src/com/android/boothelper/AwareActivity.java
@@ -14,21 +14,18 @@
* limitations under the License.
*/
-package com.android.functional.otatests;
+package com.android.boothelper;
-import org.junit.Test;
+import android.app.Activity;
+import android.os.Bundle;
/**
- * A basic test case to assert that the system was updated to the expected version.
+ * Activity that enables the boot aware property which is needed for
+ * unlocking the device through automation when the device is FBE encrypted
*/
-public class SystemUpdateTest extends VersionCheckingTest {
-
- public SystemUpdateTest(String testPath) {
- super(testPath);
- }
-
- @Test
- public void testIsUpdated() throws Exception {
- assertUpdated();
+public class AwareActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
}
}
diff --git a/tests/perf/BootHelperApp/src/com/android/boothelper/BootHelperTest.java b/tests/perf/BootHelperApp/src/com/android/boothelper/BootHelperTest.java
new file mode 100644
index 0000000..cec70ba
--- /dev/null
+++ b/tests/perf/BootHelperApp/src/com/android/boothelper/BootHelperTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.boothelper;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+public class BootHelperTest {
+
+ private static final long TIMEOUT = 10000;
+ private static final String TAG = "BootHelperTest";
+ private static final String SETTINGS_PKG = "com.android.settings";
+ private static final String LOCK_PIN_ID = "lock_pin";
+ private static final String REQUIRE_PWD_ID = "encrypt_dont_require_password";
+ private static final String PWD_ENTRY = "password_entry";
+ private UiDevice mDevice;
+ private Context mProtectedContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mProtectedContext = getInstrumentation().getContext()
+ .createDeviceProtectedStorageContext();
+ }
+
+ @Test
+ public void setupLockScreenPin() throws Exception {
+ Activity activity = launchActivity(getInstrumentation().getTargetContext()
+ .getPackageName(), AwareActivity.class, new Intent(Intent.ACTION_MAIN));
+ mDevice.waitForIdle();
+
+ // Set a PIN for this user
+ final Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ activity.startActivity(intent);
+ mDevice.waitForIdle();
+
+ // Pick PIN from the option list
+ selectOption(LOCK_PIN_ID);
+
+ // Ignore any interstitial options
+ selectOption(REQUIRE_PWD_ID);
+
+ // Set our PIN
+ selectOption(PWD_ENTRY);
+
+ // Enter it twice to confirm
+ enterTestPin();
+ enterTestPin();
+ mDevice.pressBack();
+
+ }
+
+ @Test
+ public void unlockScreenWithPin() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ };
+ mProtectedContext.registerReceiver(receiver, new IntentFilter(
+ Intent.ACTION_USER_UNLOCKED));
+ dismissKeyguard();
+ }
+
+ private void dismissKeyguard() throws Exception {
+ mDevice.wakeUp();
+ mDevice.waitForIdle();
+ mDevice.pressMenu();
+ mDevice.waitForIdle();
+ enterTestPin();
+ }
+
+ private void enterTestPin() throws Exception {
+ mDevice.waitForIdle();
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
+ mDevice.waitForIdle();
+ mDevice.pressEnter();
+ Log.i(TAG, "Screen Unlocked");
+ mDevice.waitForIdle();
+ }
+
+ /**
+ * Return the instrumentation from the registry.
+ *
+ * @return
+ */
+ private Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+
+ /**
+ * Click on the option based on the resource id in the settings package.
+ *
+ * @param optionId
+ */
+ public void selectOption(String optionId) {
+ UiObject2 tos = mDevice.wait(Until.findObject(By.res(SETTINGS_PKG, optionId)),
+ TIMEOUT);
+ if (tos != null) {
+ tos.click();
+ }
+ }
+
+ /**
+ * To launch an activity
+ * @param pkg
+ * @param activityCls
+ * @param intent
+ * @return
+ */
+ public Activity launchActivity(String pkg, Class activityCls, Intent intent) {
+ intent.setClassName(pkg, activityCls.getName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return getInstrumentation().startActivitySync(intent);
+ }
+
+
+}
diff --git a/tests/perf/PerfTransitionTest/Android.mk b/tests/perf/PerfTransitionTest/Android.mk
index dd0d909..7e4b20a 100644
--- a/tests/perf/PerfTransitionTest/Android.mk
+++ b/tests/perf/PerfTransitionTest/Android.mk
@@ -23,6 +23,13 @@
LOCAL_PACKAGE_NAME := AppTransitionTests
LOCAL_CERTIFICATE := platform
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator launcher-helper-lib
-include $(BUILD_PACKAGE)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ legacy-android-test \
+ ub-uiautomator \
+ launcher-helper-lib \
+ sysui-helper
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/perf/PerfTransitionTest/AndroidTest.xml b/tests/perf/PerfTransitionTest/AndroidTest.xml
new file mode 100644
index 0000000..b4f361b
--- /dev/null
+++ b/tests/perf/PerfTransitionTest/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs AppTransitionTests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="AppTransitionTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="AppTransitionTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.apptransition.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/AppTransitionTests.java b/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/AppTransitionTests.java
index eafb8d8..c4cfe35 100644
--- a/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/AppTransitionTests.java
+++ b/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/AppTransitionTests.java
@@ -16,18 +16,14 @@
package com.android.apptransition.tests;
-import android.app.ActivityManagerNative;
+import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.Instrumentation;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.support.test.InstrumentationRegistry;
@@ -36,7 +32,6 @@
import android.support.test.rule.logging.AtraceLogger;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
@@ -47,7 +42,6 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedHashMap;
@@ -116,7 +110,7 @@
mPackageManager = getInstrumentation().getContext().getPackageManager();
mContext = getInstrumentation().getContext();
mArgs = InstrumentationRegistry.getArguments();
- mActivityManager = ActivityManagerNative.getDefault();
+ mActivityManager = ActivityManager.getService();
mDevice = UiDevice.getInstance(getInstrumentation());
mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
createLaunchIntentMappings();
diff --git a/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/LatencyTests.java b/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/LatencyTests.java
new file mode 100644
index 0000000..f8b1f48
--- /dev/null
+++ b/tests/perf/PerfTransitionTest/src/com/android/apptransition/tests/LatencyTests.java
@@ -0,0 +1,184 @@
+/*
+ * 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.apptransition.tests;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.system.helpers.LockscreenHelper;
+import android.system.helpers.OverviewHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests to test various latencies in the system.
+ */
+public class LatencyTests {
+
+ private static final int DEFAULT_ITERATION_COUNT = 10;
+ private static final String KEY_ITERATION_COUNT = "iteration_count";
+ private static final long CLOCK_SETTLE_DELAY = 2000;
+ private static final String FINGERPRINT_WAKE_FAKE_COMMAND = "am broadcast -a "
+ + "com.android.systemui.latency.ACTION_FINGERPRINT_WAKE";
+ private static final String TURN_ON_SCREEN_COMMAND = "am broadcast -a "
+ + "com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+ private static final String AM_START_COMMAND_TEMPLATE = "am start -a %s";
+ private static final String PIN = "1234";
+
+ private UiDevice mDevice;
+ private int mIterationCount;
+
+ @Before
+ public void setUp() throws Exception {
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ Bundle args = InstrumentationRegistry.getArguments();
+ mIterationCount = Integer.parseInt(args.getString(KEY_ITERATION_COUNT,
+ Integer.toString(DEFAULT_ITERATION_COUNT)));
+ mDevice.pressHome();
+ }
+
+ /**
+ * Test to track how long it takes to expand the notification shade when swiping.
+ * <p>
+ * Every iteration will output a log in the form of "LatencyTracker/action=0 delay=x".
+ */
+ @Test
+ public void testExpandNotificationsLatency() throws Exception {
+ for (int i = 0; i < mIterationCount; i++) {
+ swipeDown();
+ mDevice.waitForIdle();
+ swipeUp();
+ mDevice.waitForIdle();
+
+ // Wait for clocks to settle down
+ SystemClock.sleep(CLOCK_SETTLE_DELAY);
+ }
+ }
+
+ private void swipeDown() {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2,
+ 0, mDevice.getDisplayWidth() / 2,
+ mDevice.getDisplayHeight() / 2,
+ 15);
+ }
+
+ private void swipeUp() {
+ mDevice.swipe(mDevice.getDisplayWidth() / 2,
+ mDevice.getDisplayHeight() / 2,
+ mDevice.getDisplayWidth() / 2,
+ 0,
+ 15);
+ }
+
+ /**
+ * Test to track how long it takes until the animation starts in a fingerprint-wake-and-unlock
+ * sequence.
+ * <p>
+ * Every iteration will output a log in the form of "LatencyTracker/action=2 delay=x".
+ */
+ @Test
+ public void testFingerprintWakeAndUnlock() throws Exception {
+ for (int i = 0; i < mIterationCount; i++) {
+ mDevice.sleep();
+
+ // Wait for clocks to settle down
+ SystemClock.sleep(CLOCK_SETTLE_DELAY);
+
+ mDevice.executeShellCommand(FINGERPRINT_WAKE_FAKE_COMMAND);
+ mDevice.waitForIdle();
+ }
+ }
+
+ /**
+ * Test how long it takes until the screen is fully turned on.
+ * <p>
+ * Every iteration will output a log in the form of "LatencyTracker/action=5 delay=x".
+ */
+ @Test
+ public void testScreenTurnOn() throws Exception {
+ for (int i = 0; i < mIterationCount; i++) {
+ mDevice.sleep();
+
+ // Wait for clocks to settle down
+ SystemClock.sleep(CLOCK_SETTLE_DELAY);
+
+ mDevice.executeShellCommand(TURN_ON_SCREEN_COMMAND);
+ mDevice.waitForIdle();
+ }
+
+ // Put device to home screen.
+ mDevice.pressMenu();
+ mDevice.waitForIdle();
+ }
+
+ /**
+ * Test how long it takes until the credential (PIN) is checked.
+ * <p>
+ * Every iteration will output a log in the form of "LatencyTracker/action=3 delay=x".
+ */
+ @Test
+ public void testPinCheckDelay() throws Exception {
+ LockscreenHelper.getInstance().setScreenLockViaShell(PIN, LockscreenHelper.MODE_PIN);
+ for (int i = 0; i < mIterationCount; i++) {
+ mDevice.sleep();
+
+ // Make sure not to launch camera with "double-tap".
+ Thread.sleep(300);
+ mDevice.wakeUp();
+ LockscreenHelper.getInstance().unlockScreen(PIN);
+ mDevice.waitForIdle();
+ }
+ LockscreenHelper.getInstance().removeScreenLockViaShell(PIN);
+ mDevice.pressHome();
+ }
+
+ /**
+ * Test that measure how long the total time takes until recents is visible after pressing it.
+ * Note that this is different from {@link AppTransitionTests#testAppToRecents} as we are
+ * measuring the full latency here, but in the app transition test we only measure the time
+ * spent after startActivity is called. This might be different as SystemUI does a lot of binder
+ * calls before calling startActivity.
+ * <p>
+ * Every iteration will output a log in the form of "LatencyTracker/action=1 delay=x".
+ */
+ @Test
+ public void testAppToRecents() throws Exception {
+ OverviewHelper.getInstance().populateManyRecentApps();
+ for (int i = 0; i < mIterationCount; i++) {
+ mDevice.executeShellCommand(String.format(AM_START_COMMAND_TEMPLATE,
+ Settings.ACTION_SETTINGS));
+ mDevice.waitForIdle();
+
+ // Wait for clocks to settle.
+ SystemClock.sleep(CLOCK_SETTLE_DELAY);
+ pressUiRecentApps();
+ mDevice.waitForIdle();
+
+ // Make sure all the animations are really done.
+ SystemClock.sleep(200);
+ }
+ }
+
+ private void pressUiRecentApps() throws Exception {
+ mDevice.findObject(By.res("com.android.systemui", "recent_apps")).click();
+ }
+}
diff --git a/tests/perf/PerformanceAppTest/Android.mk b/tests/perf/PerformanceAppTest/Android.mk
index 9039d2d..6c87256 100644
--- a/tests/perf/PerformanceAppTest/Android.mk
+++ b/tests/perf/PerformanceAppTest/Android.mk
@@ -23,7 +23,9 @@
LOCAL_PACKAGE_NAME := PerformanceAppTest
LOCAL_CERTIFICATE := platform
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
include $(BUILD_PACKAGE)
diff --git a/tests/perf/PerformanceAppTest/AndroidTest.xml b/tests/perf/PerformanceAppTest/AndroidTest.xml
new file mode 100644
index 0000000..2d56796
--- /dev/null
+++ b/tests/perf/PerformanceAppTest/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs PerformanceAppTest.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="PerformanceAppTest.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="PerformanceAppTest" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.performanceapp.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/perf/PerformanceLaunch/Android.mk b/tests/perf/PerformanceLaunch/Android.mk
index 15d8289..f3604ed 100644
--- a/tests/perf/PerformanceLaunch/Android.mk
+++ b/tests/perf/PerformanceLaunch/Android.mk
@@ -22,4 +22,6 @@
LOCAK_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/perf/PerformanceLaunch/AndroidTest.xml b/tests/perf/PerformanceLaunch/AndroidTest.xml
new file mode 100644
index 0000000..df61047
--- /dev/null
+++ b/tests/perf/PerformanceLaunch/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs PerformanceLaunch tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="PerformanceLaunch.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="PerformanceLaunch" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.performanceLaunch" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/perf/PerformanceLaunch/res/values-ar-rXB/strings.xml b/tests/perf/PerformanceLaunch/res/values-ar-rXB/strings.xml
deleted file mode 100644
index 8f90e7c..0000000
--- a/tests/perf/PerformanceLaunch/res/values-ar-rXB/strings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="many_config_1">ManyConfig1-ar-rXB</string>
- <string name="many_config_2">ManyConfig1-ar-rXB</string>
- <string name="many_config_3">ManyConfig1-ar-rXB</string>
- <string name="many_config_4">ManyConfig1-ar-rXB</string>
-</resources>
\ No newline at end of file
diff --git a/tests/perf/PerformanceLaunch/res/values-en-rXA/strings.xml b/tests/perf/PerformanceLaunch/res/values-en-rXA/strings.xml
deleted file mode 100644
index 67cf292..0000000
--- a/tests/perf/PerformanceLaunch/res/values-en-rXA/strings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="many_config_1">ManyConfig1-en-rXA</string>
- <string name="many_config_2">ManyConfig1-en-rXA</string>
- <string name="many_config_3">ManyConfig1-en-rXA</string>
- <string name="many_config_4">ManyConfig1-en-rXA</string>
-</resources>
\ No newline at end of file
diff --git a/tests/perf/PowerPerfTest/Android.mk b/tests/perf/PowerPerfTest/Android.mk
index bcf647a..d9d6a4b 100644
--- a/tests/perf/PowerPerfTest/Android.mk
+++ b/tests/perf/PowerPerfTest/Android.mk
@@ -17,8 +17,10 @@
LOCAL_PACKAGE_NAME := PowerPerfTest
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := PowerTestHelper-src ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := PowerTestHelper-src ub-uiautomator junit
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/perf/PowerPerfTest/AndroidTest.xml b/tests/perf/PowerPerfTest/AndroidTest.xml
new file mode 100644
index 0000000..87ae903
--- /dev/null
+++ b/tests/perf/PowerPerfTest/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Power Performance Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="PowerPerfTest.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="PowerPerfTest" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.powerperf.tests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/utils/crashcollector/Android.mk b/utils/crashcollector/Android.mk
index 631ff61..cc8bdd0 100644
--- a/utils/crashcollector/Android.mk
+++ b/utils/crashcollector/Android.mk
@@ -21,6 +21,7 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp/crashcollector
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := device-tests
include $(BUILD_JAVA_LIBRARY)
diff --git a/utils/crashcollector/src/android/test/crashcollector/Collector.java b/utils/crashcollector/src/android/test/crashcollector/Collector.java
index 515b73b..87f2385 100644
--- a/utils/crashcollector/src/android/test/crashcollector/Collector.java
+++ b/utils/crashcollector/src/android/test/crashcollector/Collector.java
@@ -16,7 +16,6 @@
package android.test.crashcollector;
-import android.app.ActivityManagerNative;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.content.Context;
@@ -84,7 +83,7 @@
do {
try {
// set activity controller
- IActivityManager iam = ActivityManagerNative.asInterface(am);
+ IActivityManager iam = IActivityManager.Stub.asInterface(am);
iam.setActivityController(controller, false);
// register death recipient for activity manager
am.linkToDeath(death, 0);
diff --git a/utils/dpad/src/android/platform/test/utils/DPadUtil.java b/utils/dpad/src/android/platform/test/utils/DPadUtil.java
index 3e7f1d6..dd74f50 100644
--- a/utils/dpad/src/android/platform/test/utils/DPadUtil.java
+++ b/utils/dpad/src/android/platform/test/utils/DPadUtil.java
@@ -87,19 +87,46 @@
}
public boolean pressDPadLeft() {
- return mDevice.pressDPadLeft();
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_DPAD_LEFT);
}
public boolean pressDPadRight() {
- return mDevice.pressDPadRight();
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_DPAD_RIGHT);
}
public boolean pressDPadUp() {
- return mDevice.pressDPadUp();
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_DPAD_UP);
}
public boolean pressDPadDown() {
- return mDevice.pressDPadDown();
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ public boolean pressDPadCenter() {
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_DPAD_CENTER);
+ }
+
+ public boolean pressEnter() {
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_ENTER);
+ }
+
+ public boolean pressPipKey() {
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_WINDOW);
+ }
+
+ public boolean pressSearch() {
+ return pressKeyCodeAndWait(KeyEvent.KEYCODE_SEARCH);
+ }
+
+ public boolean pressKeyCode(int keyCode) {
+ return pressKeyCodeAndWait(keyCode);
+ }
+ public boolean pressKeyCodeAndWait(int keyCode) {
+ boolean retVal = mDevice.pressKeyCode(keyCode);
+ // Dpad key presses will cause some UI change to occur.
+ // Wait for the accessibility event stream to become idle.
+ mDevice.waitForIdle();
+ return retVal;
}
public boolean pressHome() {
@@ -110,25 +137,10 @@
return mDevice.pressBack();
}
- public boolean pressDPadCenter() {
- return mDevice.pressDPadCenter();
- }
-
- public boolean pressEnter() {
- return mDevice.pressEnter();
- }
-
- public boolean pressPipKey() {
- return mDevice.pressKeyCode(KeyEvent.KEYCODE_WINDOW);
- }
-
- public boolean pressKeyCode(int keyCode) {
- return mDevice.pressKeyCode(keyCode);
- }
-
public boolean longPressKeyCode(int keyCode) {
try {
mDevice.executeShellCommand(String.format("input keyevent --longpress %d", keyCode));
+ mDevice.waitForIdle();
return true;
} catch (IOException e) {
// Ignore
@@ -139,14 +151,21 @@
/**
* Press the key code, and waits for the given condition to become true.
- * @param condition
* @param keyCode
+ * @param condition
+ * @param longpress
* @param timeout
* @param <R>
* @return
*/
+ public <R> R pressKeyCodeAndWait(int keyCode, EventCondition<R> condition, boolean longpress,
+ long timeout) {
+ return mDevice.performActionAndWait(new KeyEventRunnable(keyCode, longpress), condition,
+ timeout);
+ }
+
public <R> R pressKeyCodeAndWait(int keyCode, EventCondition<R> condition, long timeout) {
- return mDevice.performActionAndWait(new KeyEventRunnable(keyCode), condition, timeout);
+ return pressKeyCodeAndWait(keyCode, condition, false, timeout);
}
public <R> R pressDPadCenterAndWait(EventCondition<R> condition, long timeout) {
@@ -161,12 +180,21 @@
private class KeyEventRunnable implements Runnable {
private int mKeyCode;
+ private boolean mLongPress = false;
public KeyEventRunnable(int keyCode) {
mKeyCode = keyCode;
}
+ public KeyEventRunnable(int keyCode, boolean longpress) {
+ mKeyCode = keyCode;
+ mLongPress = longpress;
+ }
@Override
public void run() {
- mDevice.pressKeyCode(mKeyCode);
+ if (mLongPress) {
+ longPressKeyCode(mKeyCode);
+ } else {
+ pressKeyCode(mKeyCode);
+ }
}
}
}