<commit_before>/**
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.agomezmoron.appiumhandler;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Platform;
import org.openqa.selenium.ScreenOrientation;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriver.TargetLocator;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.ExecuteMethod;
import org.openqa.selenium.remote.RemoteWebDriver;

import com.google.gson.JsonObject;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.MultiTouchAction;
import io.appium.java_client.TouchAction;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;

/**
 * Appium handler driver to work with any appium implementation iOS/Android and working with
 * the app independent of the app (native or hybrid).
 * 
 * @author <NAME> <<EMAIL>>
 *
 */
public class AppiumHandledDriver {

    /**
     * Log instance.
     */
    private final static Logger LOGGER = Logger.getLogger(AppiumHandledDriver.class);

    /**
     * Key to be used in the {@link DesiredCapabilities} checking.
     */
    private static String PLATFORM_TYPE_KEY = "platformName";

    /**
     * Key to be used in the {@link DesiredCapabilities} checking.
     */
    private static String APP_KEY = "app";

    /**
     * Key to be used in the {@link DesiredCapabilities} checking.
     */
    private static String APP_HYBRID = "appHybrid";

    /**
     * Parameter to have always the main window.
     */
    private String mainWindow;

    /**
     * Involved instance (decorator pattern).
     */
    private AppiumDriver<MobileElement> driver;

    /**
     * Flag to know if we're testing an hybrid app.
     */
    private boolean isAnHybridApp;

    /**
     * Builder method to create {@link AppiumHandledDriver} instances.
     * @param remoteAddress to be used.
     * @param desiredCapabilities to be used.
     * @return an {@link AppiumHandledDriver} instance with the custom implementation.
     */
    public static AppiumHandledDriver buildInstance(URL remoteAddress, DesiredCapabilities desiredCapabilities) {
        AppiumHandledDriver instance = null;

        // getting app path (if it's exists)
        Object appCapability = desiredCapabilities.getCapability(APP_KEY);
        if (appCapability != null && appCapability instanceof String) {
            String appPath = (String) appCapability;
            File file = new File(appPath);
            if (file.exists()) {
                desiredCapabilities.setCapability(APP_KEY, file.getAbsolutePath());
            } else {
                LOGGER.error("The app was defined but it cannot be found in " + appPath);
            }
        }

        AppiumDriver<MobileElement> driver = null;

        // building the instance
        if (isIOS(desiredCapabilities)) {
            driver = new IOSDriver<MobileElement>(remoteAddress, desiredCapabilities);
        } else if (isAndroid(desiredCapabilities)) {
            driver = new AndroidDriver<MobileElement>(remoteAddress, desiredCapabilities);
        } else {
            // TODO: work on it. Nowadays just iOS and android are supported by this handler.
            driver = new AndroidDriver<MobileElement>(remoteAddress, desiredCapabilities);
        }

        // implicit wait for slow devices
        driver.manage().timeouts().implicitlyWait(35, TimeUnit.SECONDS);
        Boolean isHybrid = false;
        Object appHybrid = desiredCapabilities.getCapability(APP_HYBRID);
        if (appHybrid != null && appHybrid instanceof Boolean) {
            isHybrid = (Boolean) appHybrid;
            if (isHybrid) {
                // if the app is hybrid, we have to wait until the WEBVIEW context handler exists
                driver = switchToWebViewContext(driver);
            }
        }
        // now the driver is configured, we create the wrapper
        instance = new AppiumHandledDriver(driver, isHybrid);
        return instance;
    }

    /**
     * This method switches to webview context (for hybrid apps).
     * @param driver to be switched to.
     * @param 
     * @return modified driver instance.
     */
    private static AppiumDriver<MobileElement> switchToWebViewContext(AppiumDriver<MobileElement> driver) {
        long start = new Date().getTime();
        long end = new Date().getTime();
        long limit = 15; // waiting no more than 15 seconds to switch to a WEBVIEW context
        boolean switched = false;
        // for HYBRID APPS, switching the context
        do {
            sleepFor(1);
            Set<String> contextHandles = driver.getContextHandles();
            for (String context : contextHandles) {
                if (context.contains("WEBVIEW")) {
                    // the context change needs some extra time
                    do {
                        sleepFor(5);
                        driver.context(context);
                    } while (!driver.getContext().contains("WEBVIEW"));
                    switched = true;
                }
            }
            end = new Date().getTime();
        } while (!switched && (start + (limit * 1000)) > end);

        if (!switched) {
            LOGGER.error("After waiting for " + limit
                    + " seconds, the driver couldn't switched to the WEBVIEW context, so the test of the hybrid application will failed!");
        }
        return driver;
    }

    /**
     * Method to know if the driver is ready to test hybrid / native apps.
     * @return true if the driver is well configured or false otherwise.<commit_msg>Adding retries for switching context<commit_after>/**
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.agomezmoron.appiumhandler;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Platform;
import org.openqa.selenium.ScreenOrientation;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriver.TargetLocator;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.ExecuteMethod;
import org.openqa.selenium.remote.RemoteWebDriver;

import com.google.gson.JsonObject;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.MultiTouchAction;
import io.appium.java_client.TouchAction;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;

/**
 * Appium handler driver to work with any appium implementation iOS/Android and working with
 * the app independent of the app (native or hybrid).
 * 
 * @author <NAME> <<EMAIL>>
 *
 */
public class AppiumHandledDriver {

    /**
     * Log instance.
     */
    private final static Logger LOGGER = Logger.getLogger(AppiumHandledDriver.class);

    /**
     * Key to be used in the {@link DesiredCapabilities} checking.
     */
    private static String PLATFORM_TYPE_KEY = "platformName";

    /**
     * Key to be used in the {@link DesiredCapabilities} checking.
     */
    private static String APP_KEY = "app";

    /**
     * Key to be used in the {@link DesiredCapabilities} checking.
     */
    private static String APP_HYBRID = "appHybrid";

    /**
     * Parameter to have always the main window.
     */
    private String mainWindow;

    /**
     * Involved instance (decorator pattern).
     */
    private AppiumDriver<MobileElement> driver;

    /**
     * Flag to know if we're testing an hybrid app.
     */
    private boolean isAnHybridApp;

    /**
     * Builder method to create {@link AppiumHandledDriver} instances.
     * @param remoteAddress to be used.
     * @param desiredCapabilities to be used.
     * @return an {@link AppiumHandledDriver} instance with the custom implementation.
     */
    public static AppiumHandledDriver buildInstance(URL remoteAddress, DesiredCapabilities desiredCapabilities) {
        AppiumHandledDriver instance = null;

        // getting app path (if it's exists)
        Object appCapability = desiredCapabilities.getCapability(APP_KEY);
        if (appCapability != null && appCapability instanceof String) {
            String appPath = (String) appCapability;
            File file = new File(appPath);
            if (file.exists()) {
                desiredCapabilities.setCapability(APP_KEY, file.getAbsolutePath());
            } else {
                LOGGER.error("The app was defined but it cannot be found in " + appPath);
            }
        }

        AppiumDriver<MobileElement> driver = null;

        // building the instance
        if (isIOS(desiredCapabilities)) {
            driver = new IOSDriver<MobileElement>(remoteAddress, desiredCapabilities);
        } else if (isAndroid(desiredCapabilities)) {
            driver = new AndroidDriver<MobileElement>(remoteAddress, desiredCapabilities);
        } else {
            // TODO: work on it. Nowadays just iOS and android are supported by this handler.
            driver = new AndroidDriver<MobileElement>(remoteAddress, desiredCapabilities);
        }

        // implicit wait for slow devices
        driver.manage().timeouts().implicitlyWait(35, TimeUnit.SECONDS);
        Boolean isHybrid = false;
        Object appHybrid = desiredCapabilities.getCapability(APP_HYBRID);
        if (appHybrid != null && appHybrid instanceof Boolean) {
            isHybrid = (Boolean) appHybrid;
            if (isHybrid) {
                // if the app is hybrid, we have to wait until the WEBVIEW context handler exists
                driver = switchToWebViewContext(driver);
            }
        }
        // now the driver is configured, we create the wrapper
        instance = new AppiumHandledDriver(driver, isHybrid);
        return instance;
    }

    /**
     * This method switches to webview context (for hybrid apps).
     * @param driver to be switched to.
     * @param 
     * @return modified driver instance.
     */
    private static AppiumDriver<MobileElement> switchToWebViewContext(AppiumDriver<MobileElement> driver) {
        long start = new Date().getTime();
        long end = new Date().getTime();
        long limit = 15; // waiting no more than 15 seconds to switch to a WEBVIEW context
        boolean switched = false;
        int maxRetries = 5;
        // for HYBRID APPS, switching the context
        do {
            sleepFor(1);
            Set<String> contextHandles = driver.getContextHandles();
            for (String context : contextHandles) {
                if (context.contains("WEBVIEW")) {
                    // the context change needs some extra time
                    int retries = 0;
                    do {
                        sleepFor(5);
                        try {
                            driver.context(context);
                        } catch (Exception ex) {
                            LOGGER.warn("An error occurred switching the context. Trying again...");
                        }
                    } while (!driver.getContext().contains("WEBVIEW") && retries < maxRetries);
                    switched = true;
                }
            }
            end = new Date().getTime();
        } while (!switched && (start + (limit * 1000)) > end);

        if (!switched) {
            LOGGER.error("After waiting for " + limit
                    + " seconds, the driver couldn't switched to the WEBVIEW context, so the test of the hybrid application will failed!");
        }
        return driver;
    }

    /**
     * Method to know if the driver is ready to test hybrid / native apps.
     * @return true if the driver is well configured or false otherwise.
{'fraction_non_alphanumeric': 0.08559113300492611, 'fraction_numerical': 0.002736726874657909, 'mean_word_length': 2.628848063555114, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6541892', 'n_tokens_mistral': 3925, 'n_tokens_neox': 3764, 'n_words': 1313}