aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJinhui Wang <jinhuiw@google.com>2016-06-13 13:51:11 -0700
committerJinhui Wang <jinhuiw@google.com>2016-06-15 11:54:55 -0700
commit2e8c262068ccf34de556967679083d72626c7876 (patch)
treed46177d55d3fb82126bdff16acb67848cb4a309c
parentaa2ab41e04862169a365a62facefc57a2ccdd736 (diff)
downloadAfwTestHarness-2e8c262068ccf34de556967679083d72626c7876.tar.gz
Import UiAutomatorLib
This lib contains commonly used UiAutomator related functions, such as ui element finding and ui navigation. Change-Id: I080c48b679873e181d160564e225840e363a0e40
-rw-r--r--libs/UiAutomatorLib/Android.mk29
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java225
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java65
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java231
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java152
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java103
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java90
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java113
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java95
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java117
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java74
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java61
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java89
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java86
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java59
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java112
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java81
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java61
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java61
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java268
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java254
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java71
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java158
-rw-r--r--libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java329
24 files changed, 2984 insertions, 0 deletions
diff --git a/libs/UiAutomatorLib/Android.mk b/libs/UiAutomatorLib/Android.mk
new file mode 100644
index 0000000..8f508f3
--- /dev/null
+++ b/libs/UiAutomatorLib/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator AfwThCommonLib
+
+LOCAL_MODULE := AfwThUiAutomatorLib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java
new file mode 100644
index 0000000..51e3101
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java
@@ -0,0 +1,225 @@
+/*
+ * 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.afwtest.uiautomator;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RadioButton;
+
+import java.util.regex.Pattern;
+
+/**
+ * Constants for this lib and test packages.
+ */
+public final class Constants {
+
+ /**
+ * Android package name.
+ */
+ public static final String ANDROID_PKG_NAME = "android";
+
+ /**
+ * GMSCore Pakcage name.
+ */
+ public static final String GMS_PKG_NAME = "com.google.android.gms";
+
+ /**
+ * Managed provisioning package name.
+ */
+ public static final String MANAGED_PROVISIONING_PKG_NAME = "com.android.managedprovisioning";
+
+ /**
+ * Android package installer package name.
+ */
+ public static final String PACKAGE_INSTALLER_PKG_NAME = "com.android.packageinstaller";
+
+ /**
+ * TestDpc package name.
+ */
+ public static final String TESTDPC_PKG_NAME = "com.afwsamples.testdpc";
+
+ /**
+ * Default file name for provisioning performance stats.
+ */
+ public static final String PROVISIONING_STATS_FILE = "Provisioning-Stats.csv";
+
+ /**
+ * Representation name of TestDpc work profile creation time.
+ */
+ public static final String STAT_TESTDPC_WORK_PROFILE_CREATION_TIME =
+ "testDPC Work Profile creation";
+
+ /**
+ * Representation name of Managed Provisioning work profile creation name.
+ */
+ public static final String STAT_MANAGED_PROVISIONING_WORK_PROFILE_CREATION_TIME =
+ "MP Work Profile creation";
+
+ /**
+ * Regular expression string to match resource id of GMSCore NEXT button.
+ */
+ public static final String GMS_NEXT_BTN_RESOURCE_ID_REGEX =
+ GMS_PKG_NAME + ":id/(auth_setup_wizard_navbar_next|suw_navbar_next)";
+
+ /**
+ * {@link BySelector} for {@link Button} in GMS core with description "ACCEPT".
+ */
+ public static final BySelector GMS_ACCEPT_BUTTON_SELECTOR = By.pkg(GMS_PKG_NAME).clazz(
+ Button.class.getName()).desc("ACCEPT");
+
+ /**
+ * {@link BySelector} for {@link Button} in GMS core with description "NEXT".
+ */
+ public static final BySelector GMS_NEXT_BUTTON_SELECTOR = By.pkg(GMS_PKG_NAME).clazz(
+ Button.class.getName()).desc("NEXT");
+
+ /**
+ * Resource Id {@link BySelector} for GmsCore NEXT button.
+ */
+ public static final BySelector GMS_NEXT_BUTTON_RES_SELECTOR =
+ By.res(Pattern.compile(GMS_NEXT_BTN_RESOURCE_ID_REGEX))
+ .enabled(true)
+ .clickable(true);
+
+ /**
+ * {@link BySelector} for {@link EditText} in GMS core.
+ */
+ public static final BySelector GMS_TEXT_FIELD_SELECTOR = By.pkg(GMS_PKG_NAME).clazz(
+ EditText.class.getName());
+
+ /**
+ * {@link BySelector} for {@link CheckBox} in GMS core with resource-id "agree_backup".
+ */
+ public static final BySelector GMS_CHECK_BOX_SELECTOR = By.res(GMS_PKG_NAME, "agree_backup");
+
+ /**
+ * @{@link BySelector} for downloading MDMs in GmsCore.
+ */
+ public static final BySelector GMS_DOWNLOADING_DIALOG_SELECTOR =
+ By.text(Pattern.compile("Downloading.*"))
+ .res(ANDROID_PKG_NAME, "message")
+ .pkg(GMS_PKG_NAME);
+
+ /**
+ * @{@link BySelector} unique to the add account page of GmsCore.
+ */
+ public static final BySelector GMS_ADD_ACCOUNT_PAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME).desc("Add your account");
+
+ /**
+ * {@link BySelector} for {@link Button} in ManagedProvisioning with with
+ * resource-id suw_navbar_next.
+ */
+ public static final BySelector MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR = By.res(
+ MANAGED_PROVISIONING_PKG_NAME, "suw_navbar_next");
+
+ /**
+ * {@link BySelector} in ManagedProvisioning.
+ */
+ public static final BySelector MANAGED_PROVISIONING_PKG_SELECTOR = By.pkg(
+ MANAGED_PROVISIONING_PKG_NAME);
+
+
+ /**
+ * {@link BySelector} for "Learn more" button on some Managed Provisioning dialog.
+ */
+ public static final BySelector MANAGED_PROVISIONING_LEARN_MORE_LINK_SELECTOR =
+ By.res(MANAGED_PROVISIONING_PKG_NAME, "learn_more_link")
+ .enabled(true)
+ .clickable(true);
+
+ /**
+ * {@link BySelector} for {@link Button} in ManagedProvisioning with resource-id
+ * positive_button.
+ */
+ public static final BySelector MANAGED_PROVISIONING_OK_BUTTON_SELECTOR = By.res(
+ MANAGED_PROVISIONING_PKG_NAME, "positive_button");
+
+ /**
+ * {@link BySelector} for {@link Button} in Package Installer with resource-id ok_button.
+ */
+ public static final BySelector PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR = By.res(
+ PACKAGE_INSTALLER_PKG_NAME, "ok_button");
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id setup_device_owner on
+ * TestDpc Setup Management page.
+ */
+ public static final BySelector TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "setup_device_owner")
+ .clazz(RadioButton.class.getName())
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id setup_managed_profile on
+ * TestDpc Setup Management page.
+ */
+ public static final BySelector TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "setup_managed_profile")
+ .clazz(RadioButton.class.getName())
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for {@link RadioButton} with resource-id add_account on
+ * TestDpc Setup finished page.
+ */
+ public static final BySelector TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "add_account")
+ .clazz(RadioButton.class.getName())
+ .clickable(true)
+ .checkable(true);
+
+ /**
+ * {@link BySelector} for the 'NEXT' navigate button on TestDpc pages.
+ */
+ public static final BySelector TESTDPC_NEXT_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "suw_navbar_next")
+ .clazz(Button.class.getName())
+ .clickable(true);
+
+ /**
+ * {@link BySelector} for Managed Provisioning error message.
+ */
+ public static final BySelector MANAGED_PROVISIONING_ERROR_MSG_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).res(ANDROID_PKG_NAME, "message");
+
+ /**
+ * {@link BySelector} for Android platform alert message.
+ */
+ public static final BySelector ANDROID_ALERT_MSG_SELECTOR = By.res(ANDROID_PKG_NAME, "message");
+
+ /**
+ * {@link BySelector} for Android platform button.
+ */
+ public static final BySelector ANDROID_DIALOG_BTN_SELECTOR = By.clazz(Button.class.getName());
+
+ /**
+ * {@link BySelector} for {@link Button} in TestDpc with resource-id finish_setup.
+ */
+ public static final BySelector TESTDPC_FINISH_SKIP_BUTTON_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "btn_add_account_skip");
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private Constants() {
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java
new file mode 100644
index 0000000..4b99dc6
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java
@@ -0,0 +1,65 @@
+/*
+ * 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.afwtest.uiautomator.pages;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+
+/**
+ * A {@link UiPage} that won't navigate any further.
+ *
+ * <p>Such page is usually used as the last page of an automation flow.</p>
+ */
+public final class LandingPage extends UiPage {
+
+ /**
+ * Unique element that this page should wait for.
+ */
+ private final BySelector mUniqueElementSelector;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param uniqueElementSelector {@link BySelector} of the ui element to wait for
+ */
+ public LandingPage(UiDevice uiDevice,
+ TestConfig config,
+ BySelector uniqueElementSelector) {
+ super(uiDevice, config);
+ mUniqueElementSelector = uniqueElementSelector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return mUniqueElementSelector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ // Do nothing.
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java
new file mode 100644
index 0000000..de15059
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.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 com.android.afwtest.uiautomator.pages;
+
+import static android.support.test.uiautomator.Direction.UP;
+import static com.android.afwtest.common.Constants.ACTION_CHECK;
+import static com.android.afwtest.common.Constants.ACTION_CLICK;
+import static com.android.afwtest.common.Constants.ACTION_SCROLL;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClick;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeFling;
+
+import android.os.SystemClock;
+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.text.TextUtils;
+import android.util.Log;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.OemWidget;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.BySelectorHelper;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * A help class to skip pages until the expected page appears.
+ */
+public final class PageSkipper extends UiPage {
+
+ private static final String TAG = "afwtest.PageSkipper";
+
+ /**
+ * Thread sleep time.
+ */
+ private static final long THREAD_SLEEP_TIME_MS = TimeUnit.SECONDS.toMillis(3);
+
+ /**
+ * Words to match when finding available "NEXT" navigation buttons.
+ *
+ * The order of the list is preferring skip over continue or next.
+ */
+ private static final String[] NEXT_WORDS = {
+ "[sS]kip", "SKIP",
+ "[fF]inish", "FINISH",
+ "[dD]one", "DONE",
+ "[nN]ext", "NEXT",
+ "[cC]ontinue", "CONTINUE",
+ "[pP]roceed", "PROCEED",
+ "[yY][eE][sS]", "[oO][kK]"};
+
+ /**
+ * {@link Pattern} to match for NEXT buttons.
+ */
+ private static final Pattern NEXT_BTN_PATTERN =
+ Pattern.compile(TextUtils.join("|", NEXT_WORDS));
+
+ /**
+ * Buttons with text matching {@link #NEXT_BTN_PATTERN}.
+ */
+ private static final BySelector NAVIGATION_BTN_TEXT_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .text(NEXT_BTN_PATTERN);
+
+ /**
+ * Buttons with content description matching {@link #NEXT_BTN_PATTERN}.
+ */
+ private static final BySelector NAVIGATION_BTN_DESC_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .desc(NEXT_BTN_PATTERN);
+
+ /**
+ * List of package names whose buttons should not be clicked.
+ */
+ private Set<String> mPackageNameBlacklist = null;
+
+ /**
+ * Stop skipping when any object with this {@link BySelector} is found.
+ */
+ private final BySelector mSkipEndSelector;
+
+ /**
+ * Creates a new page skipper.
+ *
+ * @param uiDevice {@link UiDevice} object to access UIAutomator API
+ * @param skipEndSelector stop skipping pages if this any object matching this
+ * {@link BySelector} is found
+ * @param testConfig {@link testConfig} to get test configurations
+ */
+ public PageSkipper(UiDevice uiDevice, BySelector skipEndSelector, TestConfig testConfig) {
+ super(uiDevice, testConfig);
+
+ mSkipEndSelector = skipEndSelector;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean waitForLoading() throws Exception {
+ // Do nothing.
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ // No unique element.
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ long timeout = TimeUnit.MINUTES.toMillis(1) * getTestConfig().getTimeoutSize();
+ Timer timer = new Timer(timeout);
+ List<OemWidget> oemWidgets = getTestConfig().getOemWidgets();
+ timer.start();
+ while (!timer.isTimeUp() && !getUiDevice().hasObject(mSkipEndSelector)) {
+
+ if (!clickAvailableNextButton()) {
+ iterateOemWidgets(oemWidgets);
+ }
+
+ // Assert on fatal app crash
+ assertOnFatalAppCrash();
+
+ SystemClock.sleep(THREAD_SLEEP_TIME_MS);
+ }
+
+ if (timer.isTimeUp()) {
+ Log.e(TAG, "Timeout");
+ }
+ }
+
+ /**
+ * Finds buttons with text or content description that match pattern {@link NEXT_BTN_PATTERN}
+ * and click on them.
+ *
+ * @return {@code true} if any button found, {@code false} otherwise
+ */
+ private boolean clickAvailableNextButton() {
+ return clickAnyButton(getUiDevice().findObjects(NAVIGATION_BTN_TEXT_SELECTOR))
+ || clickAnyButton(getUiDevice().findObjects(NAVIGATION_BTN_DESC_SELECTOR));
+ }
+
+ /**
+ * Clicks any button from given list.
+ *
+ * @param buttons list of buttons to click
+ * @return {@code true} if there is any button clicked successfully, {@code false} otherwise
+ */
+ private boolean clickAnyButton(List<UiObject2> navigationBtns) {
+ for (UiObject2 obj : navigationBtns) {
+ // Skip widget with package name in mPackageNameBlacklist.
+ String pkgName = WidgetUtils.getPackageName(obj);
+ if (mPackageNameBlacklist != null && mPackageNameBlacklist.contains(pkgName)) {
+ continue;
+ }
+
+ if (safeClick(obj)) {
+ // Returns immediately after the first 'Next' button is found and clicked; because
+ // after clicking the current view hierarchy will change; the found navigation
+ // buttons will be stale.
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Iterates OEM widgets and act on found ones if necessary.
+ *
+ * @param oemWidgets {@link List} of {@link OemWidget} to operate
+ */
+ private void iterateOemWidgets(List<OemWidget> oemWidgets) {
+
+ for (OemWidget widget : oemWidgets) {
+ BySelector selector = BySelectorHelper.getSelector(widget);
+
+ if (getUiDevice().hasObject(selector)) {
+ Log.d(TAG, "Found OEM widget: " + widget.toString());
+
+ if (widget.getAction().equals(ACTION_CHECK)) {
+ safeClick(getUiDevice().findObject(selector));
+ } else if (widget.getAction().equals(ACTION_SCROLL)){
+ safeFling(getUiDevice().findObject(selector), UP);
+ } else if (widget.getAction().equals(ACTION_CLICK)) {
+ safeClick(getUiDevice().findObject(selector));
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets list of package names which should not be skipped.
+ *
+ * @param blacklist list of package names
+ */
+ public void setPackageNameBlacklist(Set<String> blacklist) {
+ mPackageNameBlacklist = blacklist;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java
new file mode 100644
index 0000000..11f4c69
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java
@@ -0,0 +1,152 @@
+/*
+ * 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.afwtest.uiautomator.pages;
+
+import static com.android.afwtest.uiautomator.Constants.PROVISIONING_STATS_FILE;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.StatsLogger;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.test.AfwTestUiWatcher;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Abstract class that represents the screen view of the device.
+ */
+public abstract class UiPage {
+
+ private static final String TAG = "afwtest.UiPage";
+
+ /**
+ * Default waiting for a {@link UiPage}.
+ */
+ private static final long DEFAULT_LOAD_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * Reference to {@link UiDevice} object.
+ */
+ private final UiDevice mUiDevice;
+
+ /**
+ * Test configuration.
+ */
+ private final TestConfig mTestConfig;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public UiPage(UiDevice uiDevice, TestConfig config) {
+ mUiDevice = uiDevice;
+ mTestConfig = config;
+ }
+
+ /**
+ * Gets {@link UiDevice}.
+ *
+ * @return {@link UiDevice} object
+ */
+ protected UiDevice getUiDevice() {
+ return mUiDevice;
+ }
+
+ /**
+ * Gets test configuration.
+ *
+ * @return {@link TestConfig} object
+ */
+ protected TestConfig getTestConfig() {
+ return mTestConfig;
+ }
+
+ /**
+ * Gets the page loading timeout.
+ *
+ * @return page loading timeout in millisecond
+ */
+ protected long getLoadingTimeoutInMs() throws IOException {
+ return DEFAULT_LOAD_TIMEOUT_MS * mTestConfig.getTimeoutSize();
+ }
+
+ /**
+ * Waits for this page to load.
+ *
+ * @return {@code true} if this page is loaded successful, {@code false} otherwise
+ */
+ public boolean waitForLoading() throws Exception {
+ Timer timer = new Timer(getLoadingTimeoutInMs());
+ timer.start();
+ do {
+ if (WidgetUtils.safeWait(getUiDevice(), uniqueElement()) != null) {
+ return true;
+ }
+ assertOnFatalAppCrash();
+ } while (!timer.isTimeUp());
+
+ Log.e(TAG, String.format("UiPage.waitForLoading timeout(%sms).", getLoadingTimeoutInMs()));
+
+ return false;
+ }
+
+ /**
+ * Asserts if fatal app crash is found.
+ */
+ protected void assertOnFatalAppCrash() {
+ String appCrashMsg = AfwTestUiWatcher.getFatalAppCrashMsg();
+ if (appCrashMsg != null) {
+ throw new RuntimeException(appCrashMsg);
+ }
+ }
+
+ /**
+ * Gets provisioning stats logger.
+ *
+ * @return {@link StatsLogger} object
+ */
+ protected StatsLogger getProvisioningStatsLogger() throws IOException {
+ return StatsLogger.getInstance(PROVISIONING_STATS_FILE);
+ }
+
+ /**
+ * Gets unique element of this page described by {@link BySelector}.
+ *
+ * <p>The returned element is used to identify this page, such as to determine if this page
+ * is visible. For example, {@link waitForLoading} function is using the returned element
+ * of this function to determine if this page has successfully loaded.
+ * </p>
+ *
+ * @return unique element of this page described by {@link BySelector}
+ */
+ public abstract BySelector uniqueElement();
+
+ /**
+ * Navigates this page properly to next page.
+ *
+ * @throws Exception if navigation failed
+ */
+ public abstract void navigate() throws Exception;
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java
new file mode 100644
index 0000000..5440cb6
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java
@@ -0,0 +1,103 @@
+/*
+ * 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.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_ADD_ACCOUNT_PAGE_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_TEXT_FIELD_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.TextField;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * GMS Core add account page.
+ */
+public final class AddAccountPage extends UiPage {
+
+ /**
+ * Default waiting time for widget, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * Username of the account to add.
+ */
+ private final String mUsername;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public AddAccountPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ mUsername = config.getWorkAccountUsername();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param customAccountKey property key of the account in {@link TestConfig}
+ */
+ public AddAccountPage(UiDevice uiDevice, TestConfig config, String customAccountKey) {
+ super(uiDevice, config);
+ mUsername = config.getProperty(customAccountKey);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() throws IOException {
+ // "Checking..." and "Loading" may take longer time than normal pages
+ return 3 * super.getLoadingTimeoutInMs();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return GMS_ADD_ACCOUNT_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ TextField.enterTextAndActivateNavigationBtn(
+ getUiDevice(),
+ GMS_TEXT_FIELD_SELECTOR,
+ mUsername,
+ GMS_NEXT_BUTTON_SELECTOR);
+
+ WidgetUtils.safeWaitAndClick(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR, DEFAULT_TIMEOUT_MS);
+ WidgetUtils.waitToBeGone(getUiDevice(), GMS_ADD_ACCOUNT_PAGE_SELECTOR);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java
new file mode 100644
index 0000000..0e17929
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.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 com.android.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.GMS_TEXT_FIELD_SELECTOR;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.TextField;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * GMS Core auth enter password page.
+ */
+public final class EnterPasswordPage extends UiPage {
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector ENTER_PASSWORD_PAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME).desc("Forgot password?");
+
+ /**
+ * Password of the account to add.
+ */
+ private final String mPassword;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public EnterPasswordPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ mPassword = config.getWorkAccountPassword();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param customPasswordKey property key of the password in {@link TestConfig}
+ */
+ public EnterPasswordPage(UiDevice uiDevice, TestConfig config, String customPasswordKey) {
+ super(uiDevice, config);
+ mPassword = config.getProperty(customPasswordKey);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return ENTER_PASSWORD_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ TextField.enterTextAndActivateNavigationBtn(getUiDevice(),
+ GMS_TEXT_FIELD_SELECTOR,
+ mPassword,
+ GMS_NEXT_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java
new file mode 100644
index 0000000..9b14ace
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java
@@ -0,0 +1,113 @@
+/*
+ * 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.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_DOWNLOADING_DIALOG_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_RES_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * GMS Google Apps device policy page.
+ */
+public final class GoogleAppsDevicePolicyPage extends UiPage {
+
+ /**
+ * Timeout for starting downloading mdm.
+ */
+ private static final long START_DOWNLOAD_MDM_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Timeout for downloading the dmd.
+ */
+ private static final long DOWNLOAD_MDM_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * Number of attempts to start downloading MDM.
+ *
+ * <p>
+ * It's found on some devices it takes some time for the MDM app icon to load;
+ * and before the icon is loaded, clicking the Next button doesn't work.
+ * Add retry logic to keep clicking until the downloading starts.
+ * </p>
+ */
+ private static final int CLICK_NEXT_BTN_ATTEMPTS = 5;
+
+ /**
+ * {@link BySelector} for the MDM app icon.
+ */
+ private static final BySelector MDM_ICON_SELECTOR =
+ By.res(GMS_PKG_NAME, "auth_device_management_download_app_icon");
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public GoogleAppsDevicePolicyPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return MDM_ICON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ // Clicks the Next button until downloading starts.
+ int attempts = CLICK_NEXT_BTN_ATTEMPTS;
+ while (getUiDevice().hasObject(GMS_NEXT_BUTTON_RES_SELECTOR) && attempts > 0) {
+
+ --attempts;
+
+ // Clicks the next button to start downloading mdm
+ WidgetUtils.safeClick(getUiDevice().findObject(GMS_NEXT_BUTTON_RES_SELECTOR));
+
+ // Wait for downloading mdm to appear
+ if (WidgetUtils.safeWait(getUiDevice(),
+ GMS_DOWNLOADING_DIALOG_SELECTOR, START_DOWNLOAD_MDM_TIMEOUT_MS) != null) {
+ break;
+ }
+ }
+
+ // Waits until the download finishes
+ Timer timer = new Timer(DOWNLOAD_MDM_TIMEOUT_MS);
+ timer.start();
+ while (!timer.isTimeUp() && getUiDevice().hasObject(GMS_DOWNLOADING_DIALOG_SELECTOR)) {
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
+ }
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java
new file mode 100644
index 0000000..4a832b3
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.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 com.android.afwtest.uiautomator.pages.gms;
+
+import static com.android.afwtest.uiautomator.Constants.GMS_CHECK_BOX_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_RES_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.widget.ScrollView;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * GMS Core Google Services page.
+ */
+public final class GoogleServicesPage extends UiPage {
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector GOOGLE_SERVICES_PAGE_SELECTOR =
+ By.pkg(GMS_PKG_NAME).text("Google services");
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public GoogleServicesPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getLoadingTimeoutInMs() throws IOException {
+ // Accessing Google server takes long time
+ return 3 * super.getLoadingTimeoutInMs();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return GOOGLE_SERVICES_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+
+ WidgetUtils.waitAndClick(getUiDevice(), GMS_CHECK_BOX_SELECTOR, DEFAULT_TIMEOUT_MS);
+
+ // Scroll down
+ UiScrollable page = new UiScrollable(
+ new UiSelector().className(ScrollView.class.getName()));
+ page.flingToEnd(5);
+
+ WidgetUtils.waitAndClick(getUiDevice(), GMS_NEXT_BUTTON_RES_SELECTOR, DEFAULT_TIMEOUT_MS);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java
new file mode 100644
index 0000000..971a9f8
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java
@@ -0,0 +1,117 @@
+/*
+ * 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_ERROR_MSG_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_LEARN_MORE_LINK_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_OK_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_SELECTOR;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.Timer;
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for all Managed Provisioning pages to keep common functionality in one place.
+ */
+public abstract class BasePage extends UiPage {
+
+ /**
+ * Thread sleep time, in milliseconds.
+ */
+ protected static final long THREAD_SLEEP_TIME_MS = TimeUnit.SECONDS.toMillis(1);
+
+ /**
+ * Default provisioning timeout in milliseconds.
+ */
+ private static final long DEFAULT_PROVISIONING_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public BasePage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * Gets provisioning error message.
+ *
+ * @return Error message if provisioning is showing error dialog, {@code null} otherwise
+ */
+ protected String getProvisioningError() {
+ // Get any managed provisioning error dialog
+ if (getUiDevice().hasObject(MANAGED_PROVISIONING_ERROR_MSG_SELECTOR)) {
+ return getUiDevice().findObject(MANAGED_PROVISIONING_ERROR_MSG_SELECTOR).getText();
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the provisioning timeout in millisecond.
+ *
+ * @return provisioning timeout in millisecond
+ */
+ protected long getProvisioningTimeoutInMs() throws IOException {
+ return DEFAULT_PROVISIONING_TIMEOUT_MS * getTestConfig().getTimeoutSize();
+ }
+
+ /**
+ * Waits for the provisioning to finish.
+ *
+ * @param timeout Waiting timeout in milliseconds
+ * @return {@code true} if the provisioning is finished, {@code false} otherwise
+ */
+ protected boolean waitForProvisioningToFinish() throws Exception {
+ Timer timer = new Timer(getProvisioningTimeoutInMs());
+ timer.start();
+ while (!timer.isTimeUp()) {
+ if (WidgetUtils.safeWait(getUiDevice(), MANAGED_PROVISIONING_PKG_SELECTOR) == null) {
+ assertOnFatalAppCrash();
+ return true;
+ }
+
+ // Dismiss the dialog with: "Your administrator has the ability to monitor...".
+ if (getUiDevice().hasObject(MANAGED_PROVISIONING_LEARN_MORE_LINK_SELECTOR)) {
+ getUiDevice().findObject(MANAGED_PROVISIONING_OK_BUTTON_SELECTOR).click();
+ SystemClock.sleep(THREAD_SLEEP_TIME_MS);
+ continue;
+ }
+
+ String errorMsg = getProvisioningError();
+ if (errorMsg != null) {
+ throw new RuntimeException("Provisioning error: " + errorMsg);
+ }
+
+ assertOnFatalAppCrash();
+
+ SystemClock.sleep(THREAD_SLEEP_TIME_MS);
+ }
+
+ return false;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java
new file mode 100644
index 0000000..fa734d1
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java
@@ -0,0 +1,74 @@
+/*
+ * 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link UiPage} representing error dialog popped up from Managed Provisioning.
+ */
+public class ErrorPage extends BasePage {
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final BySelector ERROR_PAGE_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Oops!");
+
+ private static final BySelector OK_BUTTON_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME)
+ .text(Pattern.compile("[Oo][Kk]"))
+ .clickable(true);
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public ErrorPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return ERROR_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), OK_BUTTON_SELECTOR, DEFAULT_TIMEOUT_MS);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java
new file mode 100644
index 0000000..b2c1e4f
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java
@@ -0,0 +1,61 @@
+/*
+ * 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+
+/**
+ * A {@link UiPage} to manage NFC provisioning process.
+ */
+public final class NfcProvisioningPage extends BasePage {
+
+ private static final String TAG = "afwtest.NfcProvisioningPage";
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public NfcProvisioningPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return MANAGED_PROVISIONING_PKG_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ if (!waitForProvisioningToFinish()) {
+ throw new RuntimeException("NFC Provisioning timeout");
+ }
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java
new file mode 100644
index 0000000..5357d2d
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java
@@ -0,0 +1,89 @@
+/*
+ * 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Managed Provisioning setup your device page.
+ */
+public class SetupYourDevicePage extends BasePage {
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector SET_UP_YOUR_DEVICE_PAGE_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Set up your device");
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupYourDevicePage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return SET_UP_YOUR_DEVICE_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR,
+ DEFAULT_TIMEOUT_MS);
+
+ onProvisioningStarted();
+
+ // Wait for the provisioning to finish.
+ if (!waitForProvisioningToFinish()) {
+ throw new RuntimeException("DO Provisioning timeout");
+ }
+ }
+
+ /**
+ * Handles provisioning started event.
+ */
+ protected void onProvisioningStarted() throws Exception {
+ // Default to be TestDpc provisioning
+ getProvisioningStatsLogger().startTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java
new file mode 100644
index 0000000..2fa5568
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java
@@ -0,0 +1,86 @@
+/*
+ * 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.afwtest.uiautomator.pages.managedprovisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Managed Provisioning setup your profile page.
+ */
+public class SetupYourProfilePage extends BasePage {
+
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector SET_UP_YOUR_PROFILE_PAGE_SELECTOR =
+ By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Set up your profile");
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupYourProfilePage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return SET_UP_YOUR_PROFILE_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR,
+ DEFAULT_TIMEOUT_MS);
+
+ onProvisioningStarted();
+
+ // Wait for the provisioning to finish.
+ if (!waitForProvisioningToFinish()) {
+ throw new RuntimeException("PO Provisioning timeout");
+ }
+ }
+
+ /**
+ * Handles provisioning started event.
+ */
+ protected void onProvisioningStarted() throws Exception {
+ // Default to be TestDpc provisioning
+ getProvisioningStatsLogger().startTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java
new file mode 100644
index 0000000..3c1f930
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java
@@ -0,0 +1,59 @@
+/*
+ * 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.afwtest.uiautomator.pages.packageinstaller;
+
+import static com.android.afwtest.uiautomator.Constants.PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * Package Installer device access page.
+ */
+public final class DeviceAccessPage extends UiPage {
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public DeviceAccessPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(), PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR);
+ }
+}
+
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java
new file mode 100644
index 0000000..66c3998
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java
@@ -0,0 +1,112 @@
+/*
+ * 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * Setup finished page, which should be launched after DO or PO provisioning is successful.
+ */
+public final class SetupFinishPage extends UiPage {
+
+ /**
+ * Flag indicating if account was migrated.
+ */
+ private final boolean mIsAccountMigrated;
+
+ /**
+ * {@link BySelector} for TestDpc SuW layout title "Finish setup".
+ */
+ private static final BySelector TESTDPC_FINISH_SETUP_PAGE_TITLE_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "suw_layout_title")
+ .text("Finish setup");
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ * @param isAccountMigrated whether account was migrated
+ */
+ public SetupFinishPage(UiDevice uiDevice, TestConfig config, boolean isAccountMigrated) {
+ super(uiDevice, config);
+ mIsAccountMigrated = isAccountMigrated;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupFinishPage(UiDevice uiDevice, TestConfig config) {
+ this(uiDevice, config, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_FINISH_SETUP_PAGE_TITLE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ if (mIsAccountMigrated) {
+ // Find message that account migration was successful.
+ BySelector succeedResult =
+ By.pkg(TESTDPC_PKG_NAME).text("Added account that is now managed:");
+
+ // Find managed work account name to verify required account is migrated.
+ BySelector managedAccount =
+ By.res(TESTDPC_PKG_NAME, "managed_account_name")
+ .text(getTestConfig().getWorkAccountUsername().toLowerCase());
+
+ if (WidgetUtils.safeWait(getUiDevice(), succeedResult) == null ||
+ WidgetUtils.safeWait(getUiDevice(), managedAccount) == null) {
+ throw new RuntimeException(String.format(
+ "Provisioning failed: %s is not setup to be managed by TestDpc!",
+ getTestConfig().getWorkAccountUsername()));
+ }
+
+ // Save Time metric
+ getProvisioningStatsLogger().stopTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ getProvisioningStatsLogger().writeStatsToFile();
+
+ } else {
+ BySelector succeedResult = By.pkg(TESTDPC_PKG_NAME)
+ .text("To manage the new managed profile, visit the badged version of this");
+ WidgetUtils.safeWait(getUiDevice(), succeedResult);
+ }
+
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java
new file mode 100644
index 0000000..ef30914
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java
@@ -0,0 +1,81 @@
+/*
+ * 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Setup finished Add Account page in TestDpc.
+ */
+public final class SetupFinishedAddAccountPage extends UiPage {
+
+ /**
+ * {@link BySelector} unique to this page.
+ */
+ private static final BySelector TESTDPC_SETUP_FINISHED_PAGE_SELECTOR =
+ By.res(TESTDPC_PKG_NAME, "suw_layout_title")
+ .text("Setup finished");
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupFinishedAddAccountPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_SETUP_FINISHED_PAGE_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ getProvisioningStatsLogger().stopTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME);
+ getProvisioningStatsLogger().writeStatsToFile();
+
+ if (WidgetUtils.safeWait(getUiDevice(), TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR,
+ TimeUnit.MINUTES.toMillis(1)) == null
+ || WidgetUtils.safeWait(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR,
+ TimeUnit.MINUTES.toMillis(1)) == null) {
+ assertOnFatalAppCrash();
+ }
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java
new file mode 100644
index 0000000..64a5b80
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java
@@ -0,0 +1,61 @@
+/*
+ * 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * TestDpc Setup Management page during DO provisioning.
+ */
+public final class SetupManagementDoPage extends UiPage {
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupManagementDoPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(),
+ TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java
new file mode 100644
index 0000000..04a82e5
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java
@@ -0,0 +1,61 @@
+/*
+ * 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.afwtest.uiautomator.pages.testdpc;
+
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.utils.WidgetUtils;
+
+/**
+ * TestDpc Setup Management page during PO provisioning.
+ */
+public final class SetupManagementPoPage extends UiPage {
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param config {@link TestConfig} object holding test configurations
+ */
+ public SetupManagementPoPage(UiDevice uiDevice, TestConfig config) {
+ super(uiDevice, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BySelector uniqueElement() {
+ return TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void navigate() throws Exception {
+ WidgetUtils.waitAndClick(getUiDevice(),
+ TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR);
+ WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR);
+ }
+} \ No newline at end of file
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java
new file mode 100644
index 0000000..8a45c4c
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java
@@ -0,0 +1,268 @@
+/*
+ * 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.afwtest.uiautomator.provisioning;
+
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.afwtest.common.test.TestConfig;
+import com.android.afwtest.uiautomator.pages.LandingPage;
+import com.android.afwtest.uiautomator.pages.PageSkipper;
+import com.android.afwtest.uiautomator.pages.UiPage;
+import com.android.afwtest.uiautomator.pages.gms.AddAccountPage;
+import com.android.afwtest.uiautomator.pages.gms.EnterPasswordPage;
+import com.android.afwtest.uiautomator.pages.gms.GoogleAppsDevicePolicyPage;
+import com.android.afwtest.uiautomator.pages.gms.GoogleServicesPage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.ErrorPage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.NfcProvisioningPage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.SetupYourDevicePage;
+import com.android.afwtest.uiautomator.pages.managedprovisioning.SetupYourProfilePage;
+import com.android.afwtest.uiautomator.pages.packageinstaller.DeviceAccessPage;
+import com.android.afwtest.uiautomator.pages.testdpc.SetupFinishPage;
+import com.android.afwtest.uiautomator.pages.testdpc.SetupManagementDoPage;
+import com.android.afwtest.uiautomator.pages.testdpc.SetupManagementPoPage;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A help class that automates provisioning flows.
+ */
+public class AutomationDriver {
+
+ private static final String TAG = "afwtest.AutomationDriver";
+
+ /**
+ * {@link UiDevice} object.
+ */
+ private final UiDevice mUiDevice;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public AutomationDriver(UiDevice uiDevice) {
+ mUiDevice = uiDevice;
+ }
+
+ /**
+ * Gets {@link UiDevice} instance.
+ *
+ * @return {@link UiDevice} instances
+ */
+ protected UiDevice getUiDevice() {
+ return mUiDevice;
+ }
+
+ /**
+ * Runs the NFC provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runNfcProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getNfcProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the setup wizard device owner provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runSuwDoProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getSuwDoProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the setup wizard profile owner provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runSuwPoProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getSuwPoProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the non setup wizard profile owner provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runNonSuwPoProvisioning(TestConfig testConfig) throws Exception {
+ return navigate(getNonSuwPoProvisioningPages(testConfig));
+ }
+
+ /**
+ * Runs the non setup wizard profile owner provisioning flow when it is not allowed.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return {@code true} if provisioning completes, {@code false} otherwise
+ */
+ public boolean runNonSuwPoProvisioningDisallowed(TestConfig testConfig) throws Exception {
+ return navigate(getNonSuwPoProvisioningDisallowedPages(testConfig));
+ }
+
+ /**
+ * Navigates a list of {@link UiPage} one by one.
+ *
+ * @param pages list of {@link UiPage} to navigate
+ *
+ * @return {@code true} if all pages are navigated successfully, {@code false} otherwise
+ */
+ protected boolean navigate(List<UiPage> pages) throws Exception {
+ for (UiPage page : pages) {
+ if (!page.waitForLoading()) {
+ throw new RuntimeException("Failed to load page: " + page.getClass().getName());
+ }
+
+ Log.d(TAG, String.format("Navigating: %s", page.getClass().getName()));
+ page.navigate();
+ Log.d(TAG, String.format("Navigating: %s done", page.getClass().getName()));
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for NFC provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard DO provisioning flow
+ */
+ private List<UiPage> getNfcProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new NfcProvisioningPage(mUiDevice, testConfig));
+
+ // Land on the mdm page.
+ UiPage mdmPage =
+ new LandingPage(mUiDevice, testConfig, By.pkg(testConfig.getDeviceAdminPkgName()));
+ PageSkipper pageSkipper = new PageSkipper(mUiDevice, mdmPage.uniqueElement(), testConfig);
+
+ pages.add(pageSkipper);
+ pages.add(mdmPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(testConfig.getDeviceAdminPkgName());
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for setup wizard DO provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard DO provisioning flow
+ */
+ private List<UiPage> getSuwDoProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new AddAccountPage(mUiDevice, testConfig));
+ pages.add(new EnterPasswordPage(mUiDevice, testConfig));
+ pages.add(new GoogleServicesPage(mUiDevice, testConfig));
+ pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig));
+ pages.add(new DeviceAccessPage(mUiDevice, testConfig));
+ pages.add(new SetupManagementDoPage(mUiDevice, testConfig));
+ pages.add(new SetupYourDevicePage(mUiDevice, testConfig));
+
+ UiPage landingPage = new SetupFinishPage(mUiDevice, testConfig);
+ PageSkipper pageSkipper =
+ new PageSkipper(mUiDevice, landingPage.uniqueElement(), testConfig);
+ pages.add(pageSkipper);
+ pages.add(landingPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(TESTDPC_PKG_NAME);
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for setup wizard PO provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the setup wizard PO provisioning flow
+ */
+ private List<UiPage> getSuwPoProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new AddAccountPage(mUiDevice, testConfig));
+ pages.add(new EnterPasswordPage(mUiDevice, testConfig));
+ pages.add(new GoogleServicesPage(mUiDevice, testConfig));
+ pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig));
+ pages.add(new DeviceAccessPage(mUiDevice, testConfig));
+ pages.add(new SetupManagementPoPage(mUiDevice, testConfig));
+ pages.add(new SetupYourProfilePage(mUiDevice, testConfig));
+
+ UiPage landingPage = new SetupFinishPage(mUiDevice, testConfig);
+ PageSkipper pageSkipper =
+ new PageSkipper(mUiDevice, landingPage.uniqueElement(), testConfig);
+ pages.add(pageSkipper);
+ pages.add(landingPage);
+
+ // Setup page skipper.
+ Set<String> packageNameBlacklist = new HashSet<String>();
+ packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME);
+ packageNameBlacklist.add(TESTDPC_PKG_NAME);
+ pageSkipper.setPackageNameBlacklist(packageNameBlacklist);
+
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for non setup wizard PO provisioning flow.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} representing the non setup wizard PO provisioning flow
+ */
+ private List<UiPage> getNonSuwPoProvisioningPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new AddAccountPage(mUiDevice, testConfig));
+ pages.add(new EnterPasswordPage(mUiDevice, testConfig));
+ pages.add(new GoogleServicesPage(mUiDevice, testConfig));
+ pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig));
+ pages.add(new DeviceAccessPage(mUiDevice, testConfig));
+ pages.add(new SetupManagementPoPage(mUiDevice, testConfig));
+ pages.add(new SetupYourProfilePage(mUiDevice, testConfig));
+ pages.add(new SetupFinishPage(mUiDevice, testConfig));
+ return pages;
+ }
+
+ /**
+ * Gets the list of {@link UiPage} for testing disallowed provisioning test.
+ *
+ * @param testConfig test configurations required for provisioning automation
+ * @return list of {@link UiPage} for testing disallowed provisioning test.
+ */
+ private List<UiPage> getNonSuwPoProvisioningDisallowedPages(TestConfig testConfig) {
+ List<UiPage> pages = new LinkedList<>();
+ pages.add(new ErrorPage(mUiDevice, testConfig));
+ return pages;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java
new file mode 100644
index 0000000..3d9ba2f
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java
@@ -0,0 +1,254 @@
+/*
+ * 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.afwtest.uiautomator.test;
+
+import static com.android.afwtest.common.Constants.KEY_MUTE_APP_CRASH_DIALOGS;
+import static com.android.afwtest.uiautomator.Constants.ANDROID_PKG_NAME;
+import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClick;
+import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClickAny;
+
+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.UiWatcher;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Button;
+
+import com.android.afwtest.common.test.TestConfig;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * {@link UiWatcher} to handle common unexpected scenarios.
+ *
+ * <p>
+ * This is a singleton class. There can be only one
+ * </p>
+ */
+public final class AfwTestUiWatcher implements UiWatcher {
+
+ private static final String TAG = "afwtest.AfwTestUiWatcher";
+
+ /**
+ * Unique name of this ui watcher.
+ */
+ private static final String UI_WATCHER_NAME = "afw-test-uiwatcher";
+
+ /**
+ * Regex string for app crashed message.
+ */
+ private static final String APP_STOPPED_MSG_REGEX = "Unfortunately,.*has stopped.*";
+
+ /**
+ * {@link Pattern} to match app crashed message.
+ */
+ private static final Pattern APP_STOPPED_MSG_PATTERN = Pattern.compile(APP_STOPPED_MSG_REGEX);
+
+ /**
+ * {@link BySelector} for app crashed message.
+ */
+ private static final BySelector APP_STOPPED_MSG_SELECTOR =
+ By.res(ANDROID_PKG_NAME, "message")
+ .text(APP_STOPPED_MSG_PATTERN);
+
+ /**
+ * {@link BySelector} for "OK" button on app crash dialog.
+ */
+ private static final BySelector APP_STOPPED_DIALOG_OK_BUTTON_SELECTOR =
+ By.clazz(Button.class.getName())
+ .text("OK");
+
+ /**
+ * Words to match for "Accept" button's text or description.
+ */
+ private static final String[] ACCEPT_WORDS = {
+ "[aA]ccept", "ACCEPT",
+ "[aA]gree", "AGREE",
+ "[aA]llow", "ALLOW"};
+
+ /**
+ * {@link Pattern} to match any "Accept" word in {@link #ACCEPT_WORDS}.
+ */
+ private static final Pattern ACCEPT_BTN_PATTERN =
+ Pattern.compile(TextUtils.join("|", ACCEPT_WORDS));
+
+ /**
+ * Buttons with text matching {@link #ACCEPT_BTN_PATTERN}.
+ */
+ private static final BySelector ACCEPT_BTN_TEXT_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .text(ACCEPT_BTN_PATTERN);
+
+ /**
+ * Buttons with content description matching {@link #ACCEPT_BTN_PATTERN}.
+ */
+ private static final BySelector ACCEPT_BTN_DESC_SELECTOR =
+ By.enabled(true)
+ .checkable(false)
+ .clickable(true)
+ .desc(ACCEPT_BTN_PATTERN);
+
+ /**
+ * Non-null string indicates fatal app crashed.
+ */
+ private static String sFatalAppCrashMsg;
+
+ /**
+ * Assert if any of these app crashes.
+ */
+ private static final List<String> FATAL_APP_CRASHES =
+ Arrays.asList(MANAGED_PROVISIONING_PKG_NAME,
+ "Setup Wizard");
+
+ /**
+ * List of packages whose crash should be ignored.
+ */
+ private final List<String> mAppCrashWhitelist;
+
+ /**
+ * {@link UiDevice} object.
+ */
+ private final UiDevice mUiDevice;
+
+ /**
+ * Constructor.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ private AfwTestUiWatcher(UiDevice uiDevice) throws Exception {
+ mUiDevice = uiDevice;
+ mAppCrashWhitelist = new ArrayList<String>();
+ mAppCrashWhitelist.add("Google Play services");
+ mAppCrashWhitelist.addAll(TestConfig.getDefault().getAppCrashWhitelist());
+ }
+
+ /**
+ * Registers this ui watcher.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public static void register(UiDevice uiDevice) throws Exception {
+ uiDevice.registerWatcher(UI_WATCHER_NAME, new AfwTestUiWatcher(uiDevice));
+ }
+
+ /**
+ * Unregisters this ui watcher.
+ *
+ * @param uiDevice {@link UiDevice} object
+ */
+ public static void unregister(UiDevice uiDevice) {
+ uiDevice.removeWatcher(UI_WATCHER_NAME);
+ }
+
+ public static String getFatalAppCrashMsg() {
+ return sFatalAppCrashMsg;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean checkForCondition() {
+
+ // The order of the conditions matters.
+ try {
+ return clearAppStoppedDialog() || checkAcceptButtons();
+ } catch (Exception e) {
+ // Don't throw exception as it will crashes the test runner
+ Log.e(TAG, UI_WATCHER_NAME, e);
+ return false;
+ }
+ }
+
+ /**
+ * Clears dialogs: "Unfortunately, {app name} has stopped."
+ *
+ * @return {@code true} if any dialog is dismissed, {@code false} otherwise
+ */
+ private boolean clearAppStoppedDialog() throws IOException {
+
+ if (mUiDevice.hasObject(APP_STOPPED_MSG_SELECTOR)
+ && mUiDevice.hasObject(APP_STOPPED_DIALOG_OK_BUTTON_SELECTOR)) {
+
+ UiObject2 msgWidget = mUiDevice.findObject(APP_STOPPED_MSG_SELECTOR);
+ if (msgWidget != null) {
+ String msg = msgWidget.getText();
+ Log.w(TAG, String.format("Found app crash dialog: %s", msg));
+ if (isFatalAppCrash(msg)) {
+ Log.w(TAG, String.format("Fatal app crash. Test should abort.", msg));
+ sFatalAppCrashMsg = msg;
+ return true;
+ }
+ Log.w(TAG, String.format("Auto closing: %s", msg));
+ sFatalAppCrashMsg = null;
+ return safeClick(mUiDevice.findObject(APP_STOPPED_DIALOG_OK_BUTTON_SELECTOR));
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if app crash is fatal.
+ *
+ * @param appCrashMsg App crash message
+ * @return {@code true} if app crash is fatal, {@code false} otherwise.
+ */
+ private boolean isFatalAppCrash(String appCrashMsg) throws IOException {
+ for (String app : FATAL_APP_CRASHES) {
+ if (appCrashMsg.contains(app)) {
+ return true;
+ }
+ }
+
+ // if muting all app crash dialog is enabled, return
+ if (TestConfig.getDefault().muteAppCrashDialogs()) {
+ Log.i(TAG, String.format("%s=true", KEY_MUTE_APP_CRASH_DIALOGS));
+ return false;
+ }
+
+ // otherwise, auto close whitelisted app crashes, assert on others.
+ for (String app : mAppCrashWhitelist) {
+ if (appCrashMsg.contains(app)) {
+ Log.w(TAG, String.format("Whitelisted app crash: %s", app));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Clicks any visible accept button.
+ *
+ * @return {@code true} if any button found and clicked successfully;
+ * {@code false} otherwise
+ */
+ private boolean checkAcceptButtons() {
+ return safeClickAny(mUiDevice.findObjects(ACCEPT_BTN_TEXT_SELECTOR)) ||
+ safeClickAny(mUiDevice.findObjects(ACCEPT_BTN_DESC_SELECTOR));
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java
new file mode 100644
index 0000000..3763bac
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java
@@ -0,0 +1,71 @@
+/*
+ * 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.afwtest.uiautomator.utils;
+
+import static com.android.afwtest.common.Constants.ACTION_CHECK;
+import static com.android.afwtest.common.Constants.ACTION_SCROLL;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+
+import com.android.afwtest.common.test.OemWidget;
+
+/**
+ * Helper class for {@link BySelector}.
+ */
+public final class BySelectorHelper {
+
+ /**
+ * Gets the corresponding {@link BySelector} for {@link OemWidget}
+ *
+ * @param widget {@link OemWidget} object
+ * @return {@link BySelector} for the given {@link OemWidget}
+ */
+ public static BySelector getSelector(OemWidget widget) {
+ BySelector selector = By.enabled(true);
+
+ if (!widget.getText().isEmpty()) {
+ selector.text(widget.getText());
+ }
+
+ if (!widget.getDescription().isEmpty()) {
+ selector.desc(widget.getDescription());
+ }
+
+ if (!widget.getResourceId().isEmpty()) {
+ selector.res(widget.getResourceId());
+ }
+
+ if (!widget.getPackage().isEmpty()) {
+ selector.pkg(widget.getPackage());
+ }
+
+ if (!widget.getClassName().isEmpty()) {
+ selector.clazz(widget.getClassName());
+ }
+
+ if (widget.getAction().equals(ACTION_SCROLL)) {
+ selector.scrollable(true);
+ } else if (widget.getAction().equals(ACTION_CHECK)) {
+ selector.checkable(true).checked(false);
+ } else {
+ selector.clickable(true);
+ }
+
+ return selector;
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java
new file mode 100644
index 0000000..6a8bab6
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.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.afwtest.uiautomator.utils;
+
+import android.os.SystemClock;
+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.util.Log;
+import android.view.KeyEvent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Text field related util functions.
+ */
+public final class TextField {
+
+ private static final String TAG = "afwtest.TextField";
+
+ /**
+ * Default UI waiting time, in milliseconds.
+ */
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Keyboard pop up waiting time, in milliseconds.
+ */
+ private static final long KEYBOARD_START_TIME_MS = TimeUnit.SECONDS.toMillis(2);
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private TextField() {
+ }
+
+ /**
+ * Inputs text into a text field and activate the navigation button.
+ *
+ * <p>Normally {@link UiObject2}.setText() will input text into a text field.
+ * But on some activities, such as Add Google Account page, the navigation button (e.g. NEXT)
+ * will not be activated until there is some non-space characters entered into the text
+ * field. Calling {@link UiObject2}.setText() will set the text but not activate the
+ * navigate button.
+ * This util function solves this problem by simulating key pressing event to enter '0'
+ * into the text filed to activate the navigation button before calling {@link UiObject2}.
+ * setText() to set the text.
+ * </p>
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param textFieldSelector {@link BySelector} for the text field
+ * @param text text to set
+ * @param navigationBtnSelector navigation button to activate
+ */
+ public static void enterTextAndActivateNavigationBtn(
+ UiDevice uiDevice,
+ BySelector textFieldSelector,
+ String text,
+ BySelector navigationBtnSelector) throws Exception {
+
+ // Try 3 times
+ int maxAttempts = 3;
+ while (maxAttempts > 0) {
+
+ Log.i(TAG, String.format("Activating navigation button: %s.",
+ navigationBtnSelector.toString()));
+
+ try {
+ activateNavigationBtn(uiDevice, textFieldSelector, navigationBtnSelector);
+ // Navigation button activated, exit loop
+ break;
+ } catch (Exception e) {
+
+ --maxAttempts;
+
+ // Don't throw in the retry loop
+ Log.e(TAG, String.format("Failed to activate navigation button, attempts left: %d.",
+ maxAttempts), e);
+ }
+ }
+
+ // Throw exception if activation button is not activated.
+ if (maxAttempts <= 0) {
+ throw new RuntimeException(String.format("Failed to activate navigation button",
+ navigationBtnSelector.toString()));
+ }
+
+ // Hide keyboard.
+ uiDevice.pressBack();
+
+ // Set the text now.
+ UiObject2 textField = WidgetUtils.safeWait(uiDevice,
+ textFieldSelector,
+ DEFAULT_TIMEOUT_MS,
+ 3);
+ if (textField == null) {
+ throw new RuntimeException(
+ "Failed to find text field: " + textFieldSelector.toString());
+ }
+
+
+ textField.setText(text);
+ }
+
+ /**
+ * Activates the navigation button by simulating a key pressing event to enter '0'.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param textFieldSelector {@link BySelector} for the text field
+ * @param navigationBtnSelector navigation button to activate
+ */
+ private static void activateNavigationBtn(UiDevice uiDevice,
+ BySelector textFieldSelector,
+ BySelector navigationBtnSelector) throws Exception {
+ WidgetUtils.waitAndClick(uiDevice, textFieldSelector, DEFAULT_TIMEOUT_MS, 3);
+
+ // Clicking the text field will bring up the keyboard and change the view hierachy of
+ // current screen; textField may become stale. Try to find it again before simulating
+ // the keyboard events.
+ SystemClock.sleep(KEYBOARD_START_TIME_MS);
+ if (WidgetUtils.safeWait(uiDevice, textFieldSelector, DEFAULT_TIMEOUT_MS, 3) == null) {
+ throw new RuntimeException(
+ "Failed to find text field: " + textFieldSelector.toString());
+ }
+
+ // Simulate an event to enter '0' to the text field, this will activate the navigate button.
+ if (!uiDevice.pressKeyCode(KeyEvent.KEYCODE_0)) {
+ throw new RuntimeException(String.format("Failed to enter 0 into the text field %s",
+ textFieldSelector.toString()));
+ }
+
+ // Wait for the navigation button to be activated.
+ BySelector newNavigationBtnSelector = By.copy(navigationBtnSelector).enabled(true);
+ if (WidgetUtils.safeWait(uiDevice,
+ newNavigationBtnSelector,
+ DEFAULT_TIMEOUT_MS,
+ 3) == null) {
+ throw new RuntimeException(String.format("Failed to activate navigation button: %s",
+ navigationBtnSelector.toString()));
+ }
+ }
+}
diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java
new file mode 100644
index 0000000..f46dea4
--- /dev/null
+++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java
@@ -0,0 +1,329 @@
+/*
+ * 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.afwtest.uiautomator.utils;
+
+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 java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Widget utils.
+ */
+public class WidgetUtils {
+
+ private static final String TAG = "afwtest.WidgetUtils";
+
+ /**
+ * Waiting time for each call to UiDevice.wait().
+ */
+ private static final long DEFAULT_UI_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(5);
+
+ /**
+ * Clicks on given {@link UiObject2} without throwing any exception.
+ *
+ * @param obj {@link UiObject2} to click
+ * @return {@code true} if clicked, {@code false} otherwise
+ */
+ public static boolean safeClick(UiObject2 obj) {
+ String widgetProps = getWidgetPropertiesAsString(obj);
+ try {
+ obj.click();
+ Log.d(TAG, String.format("Clicked: %s", widgetProps));
+ return true;
+ } catch(Exception e) {
+ Log.e(TAG, String.format("Failed to click: %s", widgetProps) , e);
+ return false;
+ }
+ }
+
+
+ /**
+ * Tries to click any of the given list of buttons; return if any button
+ * clicked successfully.
+ *
+ * @param btns list of buttons to click
+ * @return {@code true} if any button clicked successfully; {@code false} otherwise
+ */
+ public static boolean safeClickAny(List<UiObject2> btns) {
+ for (UiObject2 obj : btns) {
+ if (safeClick(obj)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Perform fling gesture on given {@link UiObject2} until it cannot scroll any more without
+ * throwing any exception.
+ *
+ * @param obj {@link UiObject2} to scroll
+ * @param direction The direction in which to fling
+ * @return {@code true} if fling performed, {@code false} otherwise
+ */
+ public static boolean safeFling(UiObject2 obj, Direction direction) {
+ String widgetProps = getWidgetPropertiesAsString(obj);
+
+ try {
+ // Set limit to 100 times.
+ for (int i = 0; i < 100; ++i) {
+ if (!obj.fling(direction)) {
+ break;
+ }
+ }
+ Log.d(TAG, String.format("Scrolled: %s", widgetProps));
+ return true;
+ } catch(Exception e) {
+ Log.e(TAG, String.format("Failed to scroll: %s", widgetProps), e);
+ return false;
+ }
+ }
+
+ /**
+ * Waits for a widget without throwing any exception (N attempts).
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @param attempts number of attempts.
+ * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise
+ */
+ public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector, long timeoutMS,
+ int attempts) {
+ for (int i = 0; i < attempts; ++i) {
+ UiObject2 widget = safeWait(uiDevice, selector, timeoutMS);
+ if (widget != null) {
+ return widget;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Waits for a widget without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise
+ */
+ public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector, long timeoutMS) {
+ try {
+ return uiDevice.wait(Until.findObject(selector), timeoutMS);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait for widget ", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Waits for a widget without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @return {@link UiObject2} if expected widget appears within default timeout,
+ * {@code null} otherwise
+ */
+ public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector) {
+ return safeWait(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS);
+ }
+
+ /**
+ * Waits for a widget and click on it without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @return {@code true} if click action was performed, {@code false} otherwise.
+ */
+ public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector selector) {
+ try {
+ return waitAndClick(uiDevice, selector);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait and click for widget ", e);
+ }
+ return false;
+ }
+
+ /**
+ * Waits for a widget and click on it.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @return {@code true} if click action was performed, {@code false} otherwise
+ */
+ public static boolean waitAndClick(UiDevice uiDevice, BySelector selector)
+ throws Exception {
+ return waitAndClick(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS);
+ }
+
+ /**
+ * Waits for a widget and click on it without throwing any exception.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@code true} if click action was performed, {@code false} otherwise.
+ */
+ public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector selector,
+ long timeoutMS) {
+ try {
+ return waitAndClick(uiDevice, selector, timeoutMS);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait and click for widget ", e);
+ }
+ return false;
+ }
+
+ /**
+ * Waits for a widget and click on it (N attempts).
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @param attempts number of attempts
+ * @return {@code true} if click action was performed, {@code false} otherwise
+ */
+ public static boolean waitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS,
+ int attempts) throws Exception {
+ for (int i = 0; i < attempts; ++i) {
+ if(waitAndClick(uiDevice, selector, timeoutMS)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Waits for a widget and click on it.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the widget to wait
+ * @param timeoutMS timeout in milliseconds
+ * @return {@code true} if click action was performed, {@code false} otherwise
+ */
+ public static boolean waitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS)
+ throws Exception {
+ UiObject2 object = uiDevice.wait(Until.findObject(selector), timeoutMS);
+ if (object == null) {
+ throw new Exception(String.format("UI object not found: %s", selector.toString()));
+ }
+ object.click();
+ return true;
+ }
+
+ /**
+ * Waits for all elements with given {@link BySelector} to be gone.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the UI elements to wait
+ * @param timeoutMs timeout in mmilliseconds
+ */
+ public static void waitToBeGone(UiDevice uiDevice, BySelector selector, long timeoutMs)
+ throws Exception {
+ uiDevice.wait(Until.gone(selector), timeoutMs);
+ }
+
+ /**
+ * Waits for all elements with given {@link BySelector} to be gone.
+ *
+ * @param uiDevice {@link UiDevice} object
+ * @param selector {@link BySelector} of the UI elements to wait
+ */
+ public static void waitToBeGone(UiDevice uiDevice, BySelector selector) throws Exception {
+ waitToBeGone(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS);
+ }
+
+
+ /**
+ * Gets properties of a {@link UiObject2} as a String, for debugging purpose.
+ *
+ * @param obj {@link UiObject2} to get properties from
+ * @return properties of given {@link UiObject2} as String
+ */
+ public static String getWidgetPropertiesAsString(UiObject2 widget) {
+ try {
+ return String.format("text=[%s],desc=[%s],res=[%s],pkg=[%s],class=[%s]",
+ widget.getText(),
+ widget.getContentDescription(),
+ widget.getResourceName(),
+ widget.getApplicationPackage(),
+ widget.getClassName());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get properties from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the package name of a {@link UiObject2} safely.
+ *
+ * @param widget {@link UiObject2} to get property from
+ * @return package name of given widget or null if there is any error
+ */
+ public static String getPackageName(UiObject2 widget) {
+ try {
+ return widget.getApplicationPackage();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get package name from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the text of a {@link UiObject2} safely.
+ *
+ * @param widget {@link UiObject2} to get property from
+ * @return text of given widget or null if there is any error
+ */
+ public static String getText(UiObject2 widget) {
+ try {
+ return widget.getText();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get text from a widget", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the content description of a {@link UiObject2} safely.
+ *
+ * @param widget {@link UiObject2} to get property from
+ * @return content description of given widget or null if there is any error
+ */
+ public static String getContentDescription(UiObject2 widget) {
+ try {
+ return widget.getContentDescription();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get text from a widget", e);
+ }
+
+ return null;
+ }
+}