| /* |
| * 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", |
| "[cC]lose", "CLOSE"}; |
| |
| /** |
| * {@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.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)); |
| } |
| } |