io.appium.uiautomator2.utils.AlertHelpers.java Source code

Java tutorial

Introduction

Here is the source code for io.appium.uiautomator2.utils.AlertHelpers.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * 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 io.appium.uiautomator2.utils;

import android.util.Log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

import androidx.annotation.Nullable;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
import io.appium.uiautomator2.common.exceptions.InvalidElementStateException;
import io.appium.uiautomator2.common.exceptions.NoAlertOpenException;

import static android.text.TextUtils.join;
import static io.appium.uiautomator2.utils.Device.getUiDevice;
import static org.apache.commons.lang.StringUtils.isBlank;

public class AlertHelpers {
    private static final String TAG = AlertHelpers.class.getSimpleName();

    private static final String regularAlertButtonResIdPrefix = "android:id/button";
    private static final Pattern regularAlertButtonResIdPattern = Pattern
            .compile("^" + regularAlertButtonResIdPrefix + "\\d+$");
    private static final String alertContentResId = "android:id/content";
    private static final Pattern regularAlertTitleResIdPattern = Pattern.compile(".+:id/(alertTitle|custom)$");
    private static final Pattern permissionAlertTitleResIdPattern = Pattern.compile(".+:id/permission_message$");
    private static final Pattern permissionAlertButtonResIdPattern = Pattern
            .compile(".+:id/permission_\\w+_button$");
    private static final Pattern alertElementsResIdPattern = Pattern.compile(".+:id/.+");

    private static String buttonResIdByIdx(int index) {
        return String.format("%s%s", regularAlertButtonResIdPrefix, index);
    }

    private static AlertType getAlertType() {
        Device.waitForIdle();

        if (!getUiDevice().findObjects(By.res(regularAlertTitleResIdPattern)).isEmpty()) {
            Log.d(TAG, "Regular alert has been detected");
            return AlertType.REGULAR;
        }
        if (!getUiDevice().findObjects(By.res(permissionAlertTitleResIdPattern)).isEmpty()) {
            Log.d(TAG, "Permission alert has been detected");
            return AlertType.PERMISSION;
        }

        throw new NoAlertOpenException();
    }

    @Nullable
    private static UiObject2 filterButtonByLabel(Collection<UiObject2> buttons, String label) {
        for (UiObject2 button : buttons) {
            if (Objects.equals(button.getText(), label)) {
                return button;
            }
        }
        return null;
    }

    @Nullable
    private static UiObject2 getRegularAlertButton(AlertAction action, @Nullable String buttonLabel) {
        final Map<String, UiObject2> alertButtonsMapping = new HashMap<>();
        final List<Integer> buttonIndexes = new ArrayList<>();
        for (final UiObject2 button : getUiDevice().findObjects(By.res(regularAlertButtonResIdPattern))) {
            final String resId = button.getResourceName();
            alertButtonsMapping.put(resId, button);
            buttonIndexes.add(Integer.parseInt(resId.substring(regularAlertButtonResIdPrefix.length())));
        }
        if (alertButtonsMapping.isEmpty()) {
            return null;
        }
        Log.d(TAG, String.format("Found %d buttons on the alert", alertButtonsMapping.size()));

        if (buttonLabel == null) {
            final int minIdx = Collections.min(buttonIndexes);
            return action == AlertAction.ACCEPT ? alertButtonsMapping.get(buttonResIdByIdx(minIdx))
                    : alertButtonsMapping
                            .get(buttonResIdByIdx(alertButtonsMapping.size() > 1 ? minIdx + 1 : minIdx));
        }
        return filterButtonByLabel(alertButtonsMapping.values(), buttonLabel);
    }

    @Nullable
    private static UiObject2 getPermissionAlertButton(AlertAction action, @Nullable String buttonLabel) {
        final List<UiObject2> buttons = getUiDevice().findObjects(By.res(permissionAlertButtonResIdPattern));
        if (buttons.isEmpty()) {
            return null;
        }
        Log.d(TAG, String.format("Found %d buttons on the alert", buttons.size()));

        if (buttonLabel == null) {
            if (action == AlertAction.ACCEPT) {
                return buttons.size() > 1 ? buttons.get(1) : buttons.get(0);
            }
            if (action == AlertAction.DISMISS) {
                return buttons.get(0);
            }
        } else {
            return filterButtonByLabel(buttons, buttonLabel);
        }
        return null;
    }

    /**
     * Accept or dismiss on-screen alert.
     *
     * @param action      either ACCEPT or DISMISS
     * @param buttonLabel if this parameter is set then the method
     *                    will look for the dialog button with this particular
     *                    text instead of the default one (usually it is the first button
     *                    for ACCEPT and the last one for DISMISS action)
     * @return the actual label of the clicked button
     * @throws NoAlertOpenException         if no dialog is present on the screen
     * @throws InvalidElementStateException if no matching button can be found
     */
    public static String handle(AlertAction action, @Nullable String buttonLabel) {
        final AlertType alertType = getAlertType();

        final UiObject2 dstButton = alertType == AlertType.REGULAR ? getRegularAlertButton(action, buttonLabel)
                : getPermissionAlertButton(action, buttonLabel);
        if (dstButton == null) {
            throw new InvalidElementStateException("The expected button cannot be detected on the alert");
        }

        final String actualLabel = dstButton.getText();
        Logger.info(String.format("Clicking alert button '%s' in order to %s it", actualLabel,
                action.name().toLowerCase()));
        dstButton.click();
        return actualLabel;
    }

    /**
     * @return The actual text of the on-screen dialog. An empty
     * string is going to be returned if the dialog contains no text.
     * @throws NoAlertOpenException if no dialog is present on the screen
     */
    public static String getText() {
        final AlertType alertType = getAlertType();

        final List<UiObject2> alertRoots = getUiDevice().findObjects(By.res(alertContentResId));
        if (alertRoots.isEmpty()) {
            Log.w(TAG, "Alert content container is missing");
            throw new NoAlertOpenException();
        }

        final List<String> result = new ArrayList<>();
        final List<UiObject2> alertElements = alertRoots.get(0).findObjects(By.res(alertElementsResIdPattern));
        Log.d(TAG, String.format("Detected %d alert elements", alertElements.size()));
        final String alertButtonsResIdPattern = alertType == AlertType.REGULAR
                ? regularAlertButtonResIdPattern.toString()
                : permissionAlertButtonResIdPattern.toString();
        for (final UiObject2 element : alertElements) {
            final String resName = element.getResourceName();
            if (resName == null || resName.matches(alertButtonsResIdPattern)) {
                continue;
            }

            final String text = element.getText();
            if (isBlank(text)) {
                continue;
            }

            result.add(text);
        }
        return join("\n", result);
    }

    public enum AlertAction {
        ACCEPT, DISMISS
    }

    public enum AlertType {
        REGULAR, PERMISSION
    }
}