package com.jens.automation2;

import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.MediaStore;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
import android.telephony.SmsManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;

import androidx.annotation.RequiresApi;

import com.jens.automation2.actions.wifi_router.MyOnStartTetheringCallback;
import com.jens.automation2.actions.wifi_router.MyOreoWifiManager;
import com.jens.automation2.location.WifiBroadcastReceiver;
import com.jens.automation2.receivers.ConnectivityReceiver;
import com.jens.automation2.receivers.NotificationListener;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.util.InetAddressUtils;
import org.apache.http.impl.client.DefaultHttpClient;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.security.KeyStore;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import eu.chainfire.libsuperuser.Shell;

public class Actions
{
    public static AutomationService automationServerRef;
    public static Context context;
    private static Intent playMusicIntent;
    public final static String smsSeparator = "&sms&";
    public final static String dummyPackageString = "dummyPkg239asd";

    public static final String wireguard_tunnel_up = "com.wireguard.android.action.SET_TUNNEL_UP";
    public static final String wireguard_tunnel_down = "com.wireguard.android.action.SET_TUNNEL_DOWN";
    public static final String wireguard_tunnel_refresh = "com.wireguard.android.action.REFRESH_TUNNEL_STATES";
    public static final String alarm_set = "android.intent.action.SET_TIMER";

    public static void createNotification(Action action)
    {
        String[] elements = action.getParameter2().split(Action.actionParameter2Split);

        Miscellaneous.logEvent("w", "createNotification", "Creating notification with title " + elements[0] + " and text " + elements[1], 3);

        // Create a new notification ID each time
        int notificationId = Math.round(Calendar.getInstance().getTimeInMillis() / 1000);

        try
        {
            String title = Miscellaneous.replaceVariablesInText(elements[0], Miscellaneous.getAnyContext());
            String text = Miscellaneous.replaceVariablesInText(elements[1], Miscellaneous.getAnyContext());
            Miscellaneous.createDismissibleNotification(title, text, notificationId, false, AutomationService.NOTIFICATION_CHANNEL_ID_RULES, null);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("w", "createNotification", "Error occurred while replacing vars: " + Log.getStackTraceString(e), 3);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void closeNotification(Action action)
    {
        NotificationManager nm = (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
        {
            String[] params = action.getParameter2().split(Action.actionParameter2Split, -1);

            String myApp = params[0];
            String myTitleDir = params[1];
            String requiredTitle = params[2];
            String myTextDir = params[3];
            String requiredText = params[4];
            String method = ActivityManageActionCloseNotification.dismissRegularString;

            if (params.length >= 6 && !params[5].equals(ActivityManageActionCloseNotification.dismissRegularString))
                method = params[5];

            for (StatusBarNotification sbn : NotificationListener.getInstance().getActiveNotifications())
            {
                NotificationListener.SimpleNotification sn = NotificationListener.convertNotificationToSimpleNotification(true, sbn);

                Miscellaneous.logEvent("i", "NotificationCloseCheck", "Checking if this notification should be closed in the context of rule " + action.getParentRule().getName() + ": " + sn.toString(), 5);

                if (!myApp.equals(Trigger.anyAppString))
                {
                    if (!myApp.equalsIgnoreCase(sn.getApp()))
                    {
                        Miscellaneous.logEvent("i", "NotificationCloseCheck", "Notification app name does not match rule.", 5);
                        continue;
                    }
                }
                else
                {
						/*
						 	Notifications from Automation are disregarded to avoid infinite loops.
						 */
                    if (myApp.equals(BuildConfig.APPLICATION_ID))
                    {
                        continue;
                    }
                }

				/*
					If there are multiple notifications ("stacked") title or text might be null:
					https://stackoverflow.com/questions/28047767/notificationlistenerservice-not-reading-text-of-stacked-notifications
				 */

                // T I T L E
                if (!StringUtils.isEmpty(requiredTitle))
                {
                    if (!Miscellaneous.compare(myTitleDir, requiredTitle, sn.getTitle()))
                    {
                        Miscellaneous.logEvent("i", "NotificationCloseCheck", "Notification title does not match rule.", 5);
                        continue;
                    }
                }

                // T E X T
                if (!StringUtils.isEmpty(requiredText))
                {
                    if (!Miscellaneous.compare(myTextDir, requiredText, sn.getText()))
                    {
                        Miscellaneous.logEvent("i", "NotificationCloseCheck", "Notification text does not match rule.", 5);
                        continue;
                    }
                }

                Miscellaneous.logEvent("i", "NotificationCloseCheck", "All criteria matches. Closing notification: " + sbn.getNotification().toString(), 3);
                if (NotificationListener.getInstance() != null)
                {
                    if (method == ActivityManageActionCloseNotification.dismissRegularString)
                        NotificationListener.getInstance().dismissNotification(sbn);
                    else
                        NotificationListener.getInstance().clickNotificationButton(sbn, method);
                }
                else
                    Miscellaneous.logEvent("i", "NotificationCloseCheck", "NotificationListener instance is null. Can\'t close notification.", 3);
            }
        }
    }

    public static void sendBroadcast(Context context, String action)
    {
        Miscellaneous.logEvent("i", "sendBroadcast", "Sending broadcast with action " + action, 5);
        Intent broadcastIntent = new Intent();

        if (action.contains(Action.actionParameter2Split))
        {
            String[] parts = action.split(Action.actionParameter2Split);
            broadcastIntent.setAction(parts[0]);

            String[] intentParts = parts[1].split(";");
            broadcastIntent = packParametersIntoIntent(broadcastIntent, intentParts, 0);
        }
        else
            broadcastIntent.setAction(action);

        context.sendBroadcast(broadcastIntent);
    }

    public static void setVariable(String parameter2)
    {
        String[] parts = parameter2.split(Action.actionParameter2Split);

        if (AutomationService.isMyServiceRunning(Miscellaneous.getAnyContext()))
        {
            Map<String, String> map = AutomationService.getInstance().getVariableMap();

            if (parts.length > 1)
            {
                try
                {
                    map.put(parts[0], Miscellaneous.replaceVariablesInText(parts[1], Miscellaneous.getAnyContext()));
                }
                catch (Exception e)
                {
                    map.put(parts[0], parts[1]);
                }
            }
            else
                map.remove(parts[0]);
        }

        Miscellaneous.logEvent("i", "Variable", "Checking for applicable rules after variable has been set or deleted.", 2);
        List<Rule> ruleCandidates = Rule.findRuleCandidates(Trigger.Trigger_Enum.checkVariable);
        for (int i = 0; i < ruleCandidates.size(); i++)
        {
            if (ruleCandidates.get(i).haveEnoughPermissions() && ruleCandidates.get(i).getsGreenLight(AutomationService.getInstance()))
            {
                Miscellaneous.logEvent("i", "Variable", "Rule " + ruleCandidates.get(i).getName() + " applies after variable has been set or deleted.", 2);
                ruleCandidates.get(i).activate(AutomationService.getInstance(), false);
            }
        }
        Miscellaneous.logEvent("i", "Variable", "Done checking for applicable rules after variable has been set or deleted.", 2);

    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public static boolean setSystemSetting(String parameter2, AutomationService instance)
    {
        Miscellaneous.logEvent("i", "Variable", "Setting system setting: " + parameter2, 4);

        boolean result = false;

        ContentResolver cr = instance.getContentResolver();

        // "String", "Long", "Int", "Float"
        String[] parts = parameter2.split(Action.actionParameter2Split, -1);

        if(parts.length == 3)
        {
            if (parts[0].equalsIgnoreCase("String"))
                result = android.provider.Settings.Global.putString(cr, parts[1], parts[2]);
            else if (parts[0].equalsIgnoreCase("Long"))
                result = android.provider.Settings.Global.putLong(cr, parts[1], Long.parseLong(parts[2]));
            else if (parts[0].equalsIgnoreCase("Int"))
                result = android.provider.Settings.Global.putInt(cr, parts[1], Integer.parseInt(parts[2]));
            else if (parts[0].equalsIgnoreCase("Float"))
                result = android.provider.Settings.Global.putFloat(cr, parts[1], Float.parseFloat(parts[2]));
        }
        else
        {
            if(parts[0].equals(ActivityManageActionSetSystemSetting.targets[0]))
            {
                if (parts[1].equalsIgnoreCase("String"))
                    result = android.provider.Settings.Global.putString(cr, parts[2], parts[3]);
                else if (parts[1].equalsIgnoreCase("Long"))
                    result = android.provider.Settings.Global.putLong(cr, parts[2], Long.parseLong(parts[3]));
                else if (parts[1].equalsIgnoreCase("Int"))
                    result = android.provider.Settings.Global.putInt(cr, parts[2], Integer.parseInt(parts[3]));
                else if (parts[1].equalsIgnoreCase("Float"))
                    result = android.provider.Settings.Global.putFloat(cr, parts[2], Float.parseFloat(parts[3]));
            }
            else if(parts[0].equals(ActivityManageActionSetSystemSetting.targets[1]))
            {
                if (parts[1].equalsIgnoreCase("String"))
                    result = android.provider.Settings.System.putString(cr, parts[2], parts[3]);
                else if (parts[1].equalsIgnoreCase("Long"))
                    result = android.provider.Settings.System.putLong(cr, parts[2], Long.parseLong(parts[3]));
                else if (parts[1].equalsIgnoreCase("Int"))
                    result = android.provider.Settings.System.putInt(cr, parts[2], Integer.parseInt(parts[3]));
                else if (parts[1].equalsIgnoreCase("Float"))
                    result = android.provider.Settings.System.putFloat(cr, parts[2], Float.parseFloat(parts[3]));
            }
            else if(parts[0].equals(ActivityManageActionSetSystemSetting.targets[2]))
            {
                if (parts[1].equalsIgnoreCase("String"))
                    result = android.provider.Settings.Secure.putString(cr, parts[2], parts[3]);
                else if (parts[1].equalsIgnoreCase("Long"))
                    result = android.provider.Settings.Secure.putLong(cr, parts[2], Long.parseLong(parts[3]));
                else if (parts[1].equalsIgnoreCase("Int"))
                    result = android.provider.Settings.Secure.putInt(cr, parts[2], Integer.parseInt(parts[3]));
                else if (parts[1].equalsIgnoreCase("Float"))
                    result = android.provider.Settings.Secure.putFloat(cr, parts[2], Float.parseFloat(parts[3]));
            }
            else
                result = false;
        }

        Miscellaneous.logEvent("i", "Variable", "Result of system setting change: " + String.valueOf(result), 4);

        return result;
    }

    public static class WifiStuff
    {
        public static Boolean setWifi(Context context, Boolean desiredState, String parameter2, boolean toggleActionIfPossible)
        {
            boolean forceUseRoot = false;

            try
            {
                forceUseRoot = Boolean.parseBoolean(parameter2);
            }
            catch (Exception e)
            {

            }

            if (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q || forceUseRoot)
                return setWifiWithRoot(context, desiredState, toggleActionIfPossible);
            else
                return setWifiOldFashioned(context, desiredState, toggleActionIfPossible);
        }

        public static Boolean setWifiWithRoot(Context context, Boolean desiredState, boolean toggleActionIfPossible)
        {
            Miscellaneous.logEvent("i", "Wifi", "Changing wifi to " + String.valueOf(desiredState) + ", but with root permissions.", 4);

            String command = null;

            String desiredStateString;
            if (desiredState)
                desiredStateString = "enable";
            else
                desiredStateString = "disable";

            try
            {
                command = "svc wifi " + desiredStateString;
                Miscellaneous.logEvent("i", "setWifiWithRoot()", "Running command as root: " + command.toString(), 5);
                return executeCommandViaSu(new String[]{command});
            }
            catch (Exception e)
            {
                // Oops! Something went wrong, so we throw the exception here.
                throw e;
            }
        }

        public static Boolean setWifiOldFashioned(Context context, Boolean desiredState, boolean toggleActionIfPossible)
        {
            Miscellaneous.logEvent("i", "Wifi", "Changing wifi to " + String.valueOf(desiredState), 4);

            try
            {
                if (desiredState && Settings.useWifiForPositioning)
                    WifiBroadcastReceiver.startWifiReceiver(automationServerRef.getLocationProvider());
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("w", "setWifiOldFashioned()", Log.getStackTraceString(e), 4);
            }

            WifiManager myWifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

            // toggle
            if (toggleActionIfPossible)
            {
                Toast.makeText(context, context.getResources().getString(R.string.toggling) + " " + context.getResources().getString(R.string.wifi), Toast.LENGTH_LONG).show();
                desiredState = !myWifi.isWifiEnabled();
            }

            // Only perform action if necessary
            if ((!myWifi.isWifiEnabled() && desiredState) | (myWifi.isWifiEnabled() && !desiredState))
            {
                String wifiString = "";

                if (desiredState)
                {
                    wifiString = context.getResources().getString(R.string.activating) + " " + context.getResources().getString(R.string.wifi);
                }
                else
                {
                    wifiString = context.getResources().getString(R.string.deactivating) + " " + context.getResources().getString(R.string.wifi);
                }

                Toast.makeText(context, wifiString, Toast.LENGTH_LONG).show();

                boolean returnValue = myWifi.setWifiEnabled(desiredState);
                if (!returnValue)
                    Miscellaneous.logEvent("i", "Wifi", "Error changing Wifi to " + String.valueOf(desiredState), 2);
                else
                    Miscellaneous.logEvent("i", "Wifi", "Wifi changed to " + String.valueOf(desiredState), 2);

                return returnValue;
            }

            return true;
        }
    }

    public static void setDisplayRotation(Context myContext, Boolean desiredState, boolean toggleActionIfPossible)
    {
        Miscellaneous.logEvent("i", "ScreenRotation", "Changing ScreenRotation to " + String.valueOf(desiredState), 4);
        try
        {
            if (toggleActionIfPossible)
            {
                Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.toggling), 2);
                boolean currentStatus = android.provider.Settings.System.getInt(myContext.getContentResolver(), android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 0;
                if (currentStatus)
                    desiredState = !currentStatus;
            }

            if (desiredState)
            {
                if (android.provider.Settings.System.getInt(myContext.getContentResolver(), android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 0)
                {
                    android.provider.Settings.System.putInt(myContext.getContentResolver(), android.provider.Settings.System.ACCELEROMETER_ROTATION, 1);
                    Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationEnabled), 2);
                }
                else
                    Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationAlreadyEnabled), 2);
            }
            else
            {
                if (android.provider.Settings.System.getInt(myContext.getContentResolver(), android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 1)
                {
                    android.provider.Settings.System.putInt(myContext.getContentResolver(), android.provider.Settings.System.ACCELEROMETER_ROTATION, 0);
                    Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationDisabled), 2);
                }
                else
                    Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationAlreadyDisabled), 2);
            }
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "setScreenRotation", myContext.getResources().getString(R.string.errorChangingScreenRotation) + ": " + Log.getStackTraceString(e), 2);
        }
    }

    private static boolean isWifiApEnabled(Context context)
    {
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        boolean currentlyEnabled = false;

        Method[] methods = wifiManager.getClass().getDeclaredMethods();
        for (Method method : methods)
        {
            if (method.getName().equals("isWifiApEnabled"))
            {
                try
                {
                    Object returnObject = method.invoke(wifiManager);
                    currentlyEnabled = Boolean.valueOf(returnObject.toString());

                    if (currentlyEnabled)
                        Miscellaneous.logEvent("i", "isWifiApEnabled", "true", 5);
                    else
                        Miscellaneous.logEvent("i", "isWifiApEnabled", "false", 5);
                }
                catch (Exception e)
                {
                    Miscellaneous.logEvent("i", "isWifiApEnabled", context.getResources().getString(R.string.errorDeterminingWifiApState) + ": " + e.getMessage(), 4);
                }
            }
        }
        return currentlyEnabled;
    }

    public static Boolean setWifiTethering(Context context, Boolean desiredState, boolean toggleActionIfPossible)
    {
        Miscellaneous.logEvent("i", "WifiTethering", "Changing WifiTethering to " + String.valueOf(desiredState), 4);

        boolean state = Actions.isWifiApEnabled(context);

        if (toggleActionIfPossible)
        {
            Miscellaneous.logEvent("i", "WifiAp", context.getResources().getString(R.string.toggling), 2);
            desiredState = !state;
        }

        if (((state && !desiredState) || (!state && desiredState)))
        {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
            {
                WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                Method[] methods = wifiManager.getClass().getDeclaredMethods();
                for (Method method : methods)
                {
                    Miscellaneous.logEvent("i", "WifiAp", "Trying to find appropriate method... " + method.getName(), 5);
                    if (method.getName().equals("setWifiApEnabled"))
                    {
                        try
                        {
                            String desiredString = "";
                            if (desiredState)
                                desiredString = "activate";
                            else
                                desiredString = "deactivate";

                            if (!toggleActionIfPossible)
                            {
                                Miscellaneous.logEvent("i", "WifiAp", "Trying to " + desiredString + " wifi ap...", 2);
                                if (!method.isAccessible())
                                    method.setAccessible(true);
                                method.invoke(wifiManager, null, desiredState);
                            }
                            else
                            {
                                Miscellaneous.logEvent("i", "WifiAp", "Trying to " + context.getResources().getString(R.string.toggle) + " wifi ap...", 2);
                                if (!method.isAccessible())
                                    method.setAccessible(true);
                                method.invoke(wifiManager, null, !state);
                            }

                            Miscellaneous.logEvent("i", "WifiAp", "Wifi ap " + desiredString + "d.", 2);
                        }
                        catch (Exception e)
                        {
                            Miscellaneous.logEvent("i", "WifiAp", context.getResources().getString(R.string.errorActivatingWifiAp) + ". " + e.getMessage(), 2);
                        }
                    }
                }
            }
            else
            {
                MyOnStartTetheringCallback cb = new MyOnStartTetheringCallback()
                {
                    @Override
                    public void onTetheringStarted()
                    {
                        Log.i("Tether", "Läuft");
                    }

                    @Override
                    public void onTetheringFailed()
                    {
                        Log.i("Tether", "Doof");
                    }
                };

                MyOreoWifiManager mowm = new MyOreoWifiManager(context);
                if (desiredState)
                    mowm.startTethering(cb);
                else
                    mowm.stopTethering();
            }
        }
        return true;
    }

    public static class BluetoothTetheringClass
    {
        static Object instance = null;
        static Method setTetheringOn = null;
        static Method isTetheringOn = null;
        static Object mutex = new Object();

        public static Boolean setBluetoothTethering(Context context, Boolean desiredState, boolean toggleActionIfPossible)
        {
            Miscellaneous.logEvent("i", "Bluetooth Tethering", "Changing Bluetooth Tethering to " + String.valueOf(desiredState), 4);

//			boolean state = isTetheringOn(context);

//			if (toggleActionIfPossible)
//			{
//				Miscellaneous.logEvent("i", "Bluetooth Tethering", context.getResources().getString(R.string.toggling), 2);
//				desiredState = !state;
//			}

//			if (((state && !desiredState) || (!state && desiredState)))
//			{
            BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            Class<?> classBluetoothPan = null;
            Constructor<?> BTPanCtor = null;
            Object BTSrvInstance = null;
            Method mBTPanConnect = null;

            String sClassName = "android.bluetooth.BluetoothPan";
            try
            {
                classBluetoothPan = Class.forName(sClassName);
                Constructor<?> ctor = classBluetoothPan.getDeclaredConstructor(Context.class, BluetoothProfile.ServiceListener.class);

                ctor.setAccessible(true);
                //  Set Tethering ON

                Class[] paramSet = new Class[1];
                paramSet[0] = boolean.class;

                synchronized (mutex)
                {
                    setTetheringOn = classBluetoothPan.getDeclaredMethod("setBluetoothTethering", paramSet);
                    isTetheringOn = classBluetoothPan.getDeclaredMethod("isTetheringOn", null);
                    instance = ctor.newInstance(context, new BTPanServiceListener(context));
                }

                classBluetoothPan = Class.forName("android.bluetooth.BluetoothPan");
                mBTPanConnect = classBluetoothPan.getDeclaredMethod("connect", BluetoothDevice.class);
                BTPanCtor = classBluetoothPan.getDeclaredConstructor(Context.class, BluetoothProfile.ServiceListener.class);
                BTPanCtor.setAccessible(true);
                BTSrvInstance = BTPanCtor.newInstance(context, new BTPanServiceListener(context));

                Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

                // If there are paired devices
                if (pairedDevices.size() > 0)
                {
                    // Loop through paired devices
                    for (BluetoothDevice device : pairedDevices)
                    {
                        try
                        {
                            mBTPanConnect.invoke(BTSrvInstance, device);
                        }
                        catch (Exception e)
                        {
                            Miscellaneous.logEvent("e", "Bluetooth Tethering", Log.getStackTraceString(e), 1);
                        }
                    }
                }
                return true;
            }
            catch (NoSuchMethodException e)
            {
                Miscellaneous.logEvent("e", "Bluetooth Tethering", Log.getStackTraceString(e), 1);
            }
            catch (ClassNotFoundException e)
            {
                Miscellaneous.logEvent("e", "Bluetooth Tethering", Log.getStackTraceString(e), 1);
            }
            catch (InvocationTargetException e)
            {
				/*
					Exact error message: "Bluetooth binder is null"
					This means this device doesn't have bluetooth.
				 */
                Miscellaneous.logEvent("e", "Bluetooth Tethering", "Device probably doesn't have bluetooth. " + Log.getStackTraceString(e), 1);
                Toast.makeText(context, context.getResources().getString(R.string.deviceDoesNotHaveBluetooth), Toast.LENGTH_SHORT).show();
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("e", "Bluetooth Tethering", Log.getStackTraceString(e), 1);
            }

            return false;
        }

        public static class BTPanServiceListener implements BluetoothProfile.ServiceListener
        {
            private final Context context;

            public BTPanServiceListener(final Context context)
            {
                this.context = context;
            }

            @Override
            public void onServiceConnected(final int profile, final BluetoothProfile proxy)
            {
                //Some code must be here or the compiler will optimize away this callback.

                try
                {
                    synchronized (mutex)
                    {
                        setTetheringOn.invoke(instance, true);
                        if ((Boolean) isTetheringOn.invoke(instance, null))
                        {
                            Miscellaneous.logEvent("e", "Bluetooth Tethering", "BT Tethering is on", 1);
                        }
                        else
                        {
                            Miscellaneous.logEvent("e", "Bluetooth Tethering", "BT Tethering is off", 1);
                        }
                    }
                }
                catch (InvocationTargetException e)
                {
                    Miscellaneous.logEvent("e", "Bluetooth Tethering", Log.getStackTraceString(e), 1);
                }
                catch (IllegalAccessException e)
                {
                    Miscellaneous.logEvent("e", "Bluetooth Tethering", Log.getStackTraceString(e), 1);
                }
            }

            @Override
            public void onServiceDisconnected(final int profile)
            {
            }
        }
    }

    public static boolean setUsbTethering(Context context2, Boolean desiredState, boolean toggleActionIfPossible)
    {
        //TODO:toggle not really implemented, yet

        Miscellaneous.logEvent("i", "UsbTethering", "Changing UsbTethering to " + String.valueOf(desiredState), 4);

        boolean state = false; //Actions.isWifiApEnabled(context);
        Object connectivityServiceObject = null;
        ConnectivityManager connMgr = null;

        try
        {
            connectivityServiceObject = context.getSystemService(Context.CONNECTIVITY_SERVICE);
            connMgr = (ConnectivityManager) connectivityServiceObject;
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "UsbTethering", context2.getResources().getString(R.string.logErrorGettingConnectionManagerService), 2);
            return false;
        }

        try
        {
            if ((state && !desiredState) || (!state && desiredState))
            {
                try
                {
                    Method method = connectivityServiceObject.getClass().getDeclaredMethod("getTetheredIfaces");
                    if (!method.isAccessible())
                        method.setAccessible(true);
                    String[] tetheredInterfaces = (String[]) method.invoke(connectivityServiceObject);
                    if (tetheredInterfaces.length > 0)
                        state = true;
                }
                catch (NoSuchMethodException e)
                {
                    // System doesn't have that method, try another way

                    String ipAddr = getIPAddressUsb(true);
                    if (ipAddr.length() == 0)
                        state = false;    //	tethering not enabled
                    else
                        state = true;    //	tethering enabled
                }
            }
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("w", "UsbTethering", context2.getResources().getString(R.string.logErrorDeterminingCurrentUsbTetheringState), 3);
        }

        if (toggleActionIfPossible)
        {
            Miscellaneous.logEvent("w", "UsbTethering", context2.getResources().getString(R.string.toggling), 3);
            desiredState = !state;
        }


        if ((state && !desiredState) || (!state && desiredState))
        {
            String desiredString = "";
            if (desiredState)
                desiredString = "activate";
            else
                desiredString = "deactivate";

            try
            {
                Method method = null;

                for (Method m : connectivityServiceObject.getClass().getDeclaredMethods())
                {
                    if (desiredState && m.getName().equals("tether"))
                    {
                        method = m;
                        break;
                    }

                    if (!desiredState && m.getName().equals("untether"))
                    {
                        method = m;
                        break;
                    }
                }

                if (method == null)
                    throw new NoSuchMethodException();

                /*
                 * For some reason this doesn't work, throws NoSuchMethodExpection even if the method is present.
                 */
//				if(desiredState)
//					method = connectivityServiceObject.getClass().getDeclaredMethod("tether");
//				else
//					method = connectivityServiceObject.getClass().getDeclaredMethod("untether");

                // DETECT INTERFACE NAME
                Miscellaneous.logEvent("i", "UsbTethering", context2.getResources().getString(R.string.logDetectingTetherableUsbInterface), 4);
                String[] available = null;
                Method[] wmMethods = connMgr.getClass().getDeclaredMethods();
                for (Method getMethod : wmMethods)
                {
                    if (getMethod.getName().equals("getTetherableUsbRegexs"))
                    {
                        try
                        {
                            if (!method.isAccessible())
                                method.setAccessible(true);
                            available = (String[]) getMethod.invoke(connMgr);
//						            break;
                        }
                        catch (Exception e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
                // DETECT INTERFACE NAME


                if (available.length > 0)
                {
                    for (String interfaceName : available)
                    {
                        Miscellaneous.logEvent("i", "UsbTethering", "Detected " + String.valueOf(available.length) + " tetherable usb interfaces.", 5);
                        Miscellaneous.logEvent("i", "UsbTethering", "Trying to " + desiredString + " UsbTethering on interface " + interfaceName + "...", 5);
                        if (!method.isAccessible())
                            method.setAccessible(true);
                        Integer returnCode = (Integer) method.invoke(connectivityServiceObject, interfaceName);
                        if (returnCode == 0)
                        {
                            Miscellaneous.logEvent("i", "UsbTethering", "UsbTethering " + desiredString + "d.", 5);
                            return true;
                        }
                        else
                        {
                            Miscellaneous.logEvent("w", "UsbTethering", "Failed to " + desiredString + "Usb Tethering. ReturnCode of method " + method.getName() + ": " + String.valueOf(returnCode), 5);
                        }
                    }
                }
            }
            catch (NoSuchMethodException e)
            {
                Miscellaneous.logEvent("w", "UsbTethering", "Error while trying to " + desiredString + " UsbTethering. This kind of error may indicate we are above Android 2.3: " + Log.getStackTraceString(e), 3);
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("w", "UsbTethering", "Error while trying to " + desiredString + " UsbTethering. " + Log.getStackTraceString(e), 3);
            }
        }
        return false;
    }

    public static Boolean setBluetooth(Context context, Boolean desiredState, boolean toggleActionIfPossible)
    {
        Miscellaneous.logEvent("i", "Bluetooth", "Changing bluetooth to " + String.valueOf(desiredState), 4);
//        mPhone.setDataEnabled(enable);
        try
        {
            BluetoothAdapter myBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

            // toggle
            if (toggleActionIfPossible)
            {
                Miscellaneous.logEvent("e", "SetBluetooth", context.getResources().getString(R.string.toggling), 2);
                desiredState = !myBluetoothAdapter.isEnabled();
            }

            // activate
            if (!myBluetoothAdapter.isEnabled() && desiredState)
            {
                Toast.makeText(context, context.getResources().getString(R.string.activating) + " Bluetooth.", Toast.LENGTH_LONG).show();
                return myBluetoothAdapter.enable();
                //return true;
            }

            // deactivate
            if (myBluetoothAdapter.isEnabled() && !desiredState)
            {
                Toast.makeText(context, context.getResources().getString(R.string.deactivating) + " Bluetooth.", Toast.LENGTH_LONG).show();
                return myBluetoothAdapter.disable();
                //return true;
            }
        }
        catch (NullPointerException e)
        {
            Miscellaneous.logEvent("e", "SetBluetooth", context.getResources().getString(R.string.failedToTriggerBluetooth), 2);
            Toast.makeText(context, context.getResources().getString(R.string.bluetoothFailed), Toast.LENGTH_LONG).show();
        }

        return false;
    }

    public static void setDoNotDisturb(Context context, int desiredDndMode)
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
        {
			/*
				if (!notificationManager.isNotificationPolicyAccessGranted())
				--> done externally
			*/

            Miscellaneous.logEvent("i", context.getResources().getString(R.string.soundSettings), "Changing DND to " + String.valueOf(desiredDndMode), 4);
            NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            mNotificationManager.setInterruptionFilter(desiredDndMode);
        }
        else
            Miscellaneous.logEvent("w", context.getResources().getString(R.string.soundSettings), "Cannot change DND to " + String.valueOf(desiredDndMode) + ". This Android version is too and doesn\'t have that feature, yet.", 4);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public static boolean isDoNotDisturbActive(Context context)
    {
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        int result = notificationManager.getCurrentInterruptionFilter();
        return (notificationManager.getCurrentInterruptionFilter() != NotificationManager.INTERRUPTION_FILTER_ALL);
    }

    public static void setSound(Context context, int desiredSoundSetting)
    {
        Miscellaneous.logEvent("i", context.getResources().getString(R.string.soundSettings), "Changing sound to " + String.valueOf(desiredSoundSetting), 4);

        AudioManager myAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

//		if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && desiredSoundSetting == AudioManager.RINGER_MODE_SILENT)
//		{
//			AudioManager am = (AudioManager) Miscellaneous.getAnyContext().getSystemService(Context.AUDIO_SERVICE);
//			am.setStreamVolume(AudioManager.STREAM_NOTIFICATION, 0, AudioManager.FLAG_PLAY_SOUND);
//			am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_OFF);
//		}
//		else
        myAudioManager.setRingerMode(desiredSoundSetting);
    }

    private static String getIPAddressUsb(final boolean useIPv4)
    {
        try
        {
            final List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
            for (final NetworkInterface intf : interfaces)
            {
                if (intf.getDisplayName().startsWith("usb"))    // ro "rndis0"
                {
                    final List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
                    for (final InetAddress addr : addrs)
                    {
                        final String sAddr = addr.getHostAddress().toUpperCase();
                        final boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);
                        if (useIPv4)
                        {
                            if (isIPv4)
                            {
                                return sAddr;
                            }
                        }
                        else
                        {
                            if (!isIPv4)
                            {
                                final int delim = sAddr.indexOf('%');
                                return delim < 0 ? sAddr : sAddr.substring(0, delim);
                            }
                        }
                    }
                }
            }
        }
        catch (final Exception ex)
        {
            // for now eat exceptions
        }
        return "";
    }

    public static void playSound(boolean alwaysPlay, String soundFileLocation)
    {
        if (alwaysPlay || ((AudioManager) Miscellaneous.getAnyContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode() == AudioManager.RINGER_MODE_NORMAL)
        {
            MediaPlayer mp = new MediaPlayer();
            try
            {
                File file = new File(soundFileLocation);
                if (file.exists())
                {
                    Uri fileUri = Uri.parse(soundFileLocation);
                    mp.setLooping(false);
                    mp.setDataSource(Miscellaneous.getAnyContext(), fileUri);
                    mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
                    {
                        @Override
                        public void onCompletion(MediaPlayer mp)
                        {
                            mp.release();
                        }
                    });
                    mp.prepare();
                    mp.start();
                }
                else
                {
                    Miscellaneous.logEvent("w", "Play sound file", "Sound file " + soundFileLocation + " does not exist. Can't play it.", 2);
                    Toast.makeText(context, String.format(context.getResources().getString(R.string.cantFindSoundFile), soundFileLocation), Toast.LENGTH_SHORT).show();
                }
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("e", "Play sound file", "Error playing sound: " + Log.getStackTraceString(e), 2);
            }
        }
        else
            Miscellaneous.logEvent("i", "Play sound file", "Not playing sound file because phone is on some kind of mute state.", 2);
    }

    public static void vibrate(boolean parameter1, String parameter2)
    {
        String[] vibrateDurations = parameter2.split(Action.vibrateSeparator);

        int counter = 1;

        Vibrator vibrator = (Vibrator) Miscellaneous.getAnyContext().getSystemService(Context.VIBRATOR_SERVICE);

        if(ActivityPermissions.havePermission(Manifest.permission.VIBRATE, Miscellaneous.getAnyContext()))
            Miscellaneous.logEvent("i", "Vibrate", "Have permission.", 3);

        if(vibrator != null && vibrator.hasVibrator())
        {
            for (String vibrateDuration : vibrateDurations)
            {
                vibrator.cancel();

                if (counter % 2 != 0)
                {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                    {
                        VibrationEffect ve = VibrationEffect.createOneShot(Long.parseLong(vibrateDuration), VibrationEffect.DEFAULT_AMPLITUDE);
                        vibrator.vibrate(ve);
                    }
                    else
                        vibrator.vibrate(Long.parseLong(vibrateDuration));

                    // Wait for the vibration to complete.
                    try
                    {
                        Thread.sleep(Long.parseLong(vibrateDuration));
                    }
                    catch (Exception e)
                    {
                        Miscellaneous.logEvent("e", "VibrateSleep", Log.getStackTraceString(e), 5);
                    }
                }
                else
                {
                    try
                    {
                        Thread.sleep(Long.parseLong(vibrateDuration));
                    }
                    catch (Exception e)
                    {
                        Miscellaneous.logEvent("e", "VibrateSleep", Log.getStackTraceString(e), 5);
                    }
                }

                counter++;
            }
        }
        else
            Miscellaneous.logEvent("w", "Vibrate", "Device doesn't have vibrator.", 3);
    }

    public void useDownloadedWebpage(String result)
    {
//		Toast.makeText(context, "Result: " + result, Toast.LENGTH_LONG).show();
        Actions.setVariable("last_trigger_url_result" + Action.actionParameter2Split + result);
    }

    public static HttpClient getInsecureSslClient(HttpClient client)
    {
        try
        {
            SSLContext ctx = SSLContext.getInstance("TLS");
            SSLSocketFactory ssf = null;
//	        MySSLSocketFactoryInsecure ssfI = null;

//	    	if(!Settings.httpAcceptAllCertificates)
//	    	{
//	    		ssf = new MySSLSocketFactory(ctx);
//	    		ssf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
//	    		
//		        char[] keystorePass="insecure".toCharArray();   //passphrase for keystore
//		        KeyStore keyStore=KeyStore.getInstance("BKS");
//		        
////		        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
//		        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
//		        tmf.init(keyStore);
//		        
//		        Miscellaneous.logEvent("i", "SSL Keystore", context.getCacheDir().toString(), 4);
//		        InputStream is = context.getResources().openRawResource(R.raw.keystore); 
//		        keyStore.load(is,keystorePass);
//	    		
//	    		ctx.init(null, tmf.getTrustManagers(), null);
//	    	}
//	    	else
//	    	{
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            ssf = new MySSLSocketFactoryInsecure(trustStore);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ctx.init(null, null, null);
//	    	}

            ClientConnectionManager ccm = client.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();

//	        if(!Settings.httpAcceptAllCertificates)
            sr.register(new Scheme("https", ssf, 443));
//	        else
//	        	sr.register(new Scheme("https", ssfI, 443));

            return new DefaultHttpClient(ccm, client.getParams());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return null;
        }
    }

    public static void startOtherActivity(boolean startByAction, String param)
    {
        Miscellaneous.logEvent("i", "StartOtherActivity", "Starting other Activity...", 4);

        String[] params;

        if (param.contains(Action.actionParameter2Split))
            params = param.split(Action.actionParameter2Split, -1);
        else
            params = param.split(";", -1);

        try
        {
            Intent externalApplicationIntent;

            String packageName, className, activityOrAction, startupType;

            packageName = params[0];
            className = params[1];
            activityOrAction = params[2];
            startupType = params[3];

            if (!startByAction)
            {
                // start by activity
                Miscellaneous.logEvent("i", "StartOtherApp", "Starting app by activity: " + packageName + " " + className, 3);

                externalApplicationIntent = new Intent(Intent.ACTION_MAIN);
                externalApplicationIntent.addCategory(Intent.CATEGORY_LAUNCHER);

                if (packageName.equals("dummyPkg") || packageName.length() == 0)
                    externalApplicationIntent.setAction(className);

                externalApplicationIntent.setPackage(packageName);
                externalApplicationIntent.setClassName(packageName, activityOrAction);

                if (!Miscellaneous.doesActivityExist(externalApplicationIntent, Miscellaneous.getAnyContext()))
                    Miscellaneous.logEvent("w", "StartOtherApp", "Activity not found: " + className, 2);
            }
            else
            {
                // start by action
                Miscellaneous.logEvent("i", "StartOtherApp", "Starting app by action: " + param, 3);

                externalApplicationIntent = new Intent();

                if (!packageName.equals(dummyPackageString))
                {
                    if(!packageName.isEmpty())
                        externalApplicationIntent.setPackage(packageName);

                    if (!StringUtils.isEmpty(className))
                        externalApplicationIntent.setClassName(packageName, className);
                }

                externalApplicationIntent.setAction(activityOrAction);

                if (startupType.equals(ActivityManageActionStartActivity.startByServiceString) || startupType.equals(ActivityManageActionStartActivity.startByForegroundServiceString))
                {
                    externalApplicationIntent.setComponent(new ComponentName(packageName, className));
                }
            }

            externalApplicationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//            externalApplicationIntent.setType(android.intent.extra.)

            // Pack intents
            if (params.length >= 5)
            {
                if (Miscellaneous.isNumeric(startupType))
                    externalApplicationIntent = packParametersIntoIntent(externalApplicationIntent, params, 4);
//                else
//                    externalApplicationIntent = packParametersIntoIntent(externalApplicationIntent, params, 3);
            }

            Miscellaneous.logEvent("i", "StartOtherApp", "Starting other app with intent: " + externalApplicationIntent.toString() + " / " + externalApplicationIntent.getDataString(), 5);

            if (startupType.equals(ActivityManageActionStartActivity.startByActivityString))
                automationServerRef.startActivity(externalApplicationIntent);
            else if (startupType.equals(ActivityManageActionStartActivity.startByServiceString))
                automationServerRef.startService(externalApplicationIntent);
            else if (startupType.equals(ActivityManageActionStartActivity.startByForegroundServiceString) && Build.VERSION.SDK_INT >= 26)
                automationServerRef.startForegroundService(externalApplicationIntent);
            else
                automationServerRef.sendBroadcast(externalApplicationIntent);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "StartOtherApp", automationServerRef.getResources().getString(R.string.errorStartingOtherActivity) + ": " + Log.getStackTraceString(e), 2);
            Toast.makeText(automationServerRef, automationServerRef.getResources().getString(R.string.errorStartingOtherActivity) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    public static Intent packParametersIntoIntent(Intent intent, String[] params, int startIndex)
    {
        for (int i = startIndex; i < params.length; i++)
        {
            String[] singleParam = params[i].split(Action.intentPairSeparator);

    			/*Class c = Class.forName(singleParam[0]);
				for(Method m : c.getMethods())
				{
					if(m.getName().startsWith("parse"))
					{
						Object o = m.invoke(null, singleParam[0]);
						externalActivityIntent.putExtra(singleParam[1], o);
					}
				}*/

            if (singleParam[0].equals("boolean"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Boolean.parseBoolean(singleParam[2]));
            }
            else if (singleParam[0].equals("byte"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Byte.parseByte(singleParam[2]));
            }
            else if (singleParam[0].equals("char"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], singleParam[2].charAt(0));
            }
            else if (singleParam[0].equals("CharSequence"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], (CharSequence) singleParam[2]);
            }
            else if (singleParam[0].equals("double"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Double.parseDouble(singleParam[2]));
            }
            else if (singleParam[0].equals("float"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Float.parseFloat(singleParam[2]));
            }
            else if (singleParam[0].equals("int"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Integer.parseInt(singleParam[2]));
            }
            else if (singleParam[0].equals("long"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Long.parseLong(singleParam[2]));
            }
            else if (singleParam[0].equals("short"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                intent.putExtra(singleParam[1], Short.parseShort(singleParam[2]));
            }
            else if (singleParam[0].equals("Uri"))
            {
                try
                {
                    if (singleParam[1].equalsIgnoreCase("IntentData"))
                    {
                        Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with value " + singleParam[2] + " as standard data parameter.", 3);
                        intent.setData(Uri.parse(Miscellaneous.replaceVariablesInText(singleParam[2], context)));
                    }
                    else
                    {
                        Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                        intent.putExtra(singleParam[1], Uri.parse(Miscellaneous.replaceVariablesInText(singleParam[2], context)));
                    }
                }
                catch (Exception e)
                {
                    throw new RuntimeException(e);
                }
            }
            else if (singleParam[0].equals("String"))
            {
                Miscellaneous.logEvent("i", "StartOtherApp", "Adding parameter of type " + singleParam[0] + " with name " + singleParam[1] + " and value " + singleParam[2], 3);
                try
                {
                    intent.putExtra(singleParam[1], Miscellaneous.replaceVariablesInText(singleParam[2], context));
                }
                catch (Exception e)
                {
                    intent.putExtra(singleParam[1], singleParam[2]);
                }
            }
            else
                Miscellaneous.logEvent("w", "StartOtherApp", "Unknown type of parameter " + singleParam[0] + " found.  Name " + singleParam[1] + " and value " + singleParam[2], 3);
        }

        return intent;
    }

    public static void waitBeforeNextAction(Long waitTime)
    {
        Miscellaneous.logEvent("i", "waitBeforeNextAction", "waitBeforeNextAction for " + String.valueOf(waitTime) + " milliseconds.", 4);

        try
        {
            Thread.sleep(waitTime);
        }
        catch (InterruptedException e)
        {
            Miscellaneous.logEvent("e", "waitBeforeNextAction", Log.getStackTraceString(e), 2);
        }
    }

    public static void wakeupDevice(Long awakeTime)
    {
        String duration = "default";
        if (awakeTime > 0)
            duration = String.valueOf(awakeTime) + " milliseconds";

        Miscellaneous.logEvent("i", "wakeupDevice", "wakeupDevice for " + String.valueOf(duration) + ".", 4);

        if (awakeTime > 0)
        {
            /*
             * This action needs to be performed in a separate thread. If it ran in the same one
             * the screen would turn on, the specified amount of time would pass and the screen
             * would turn off again before any other action in the rule would be ran.
             */
            Thread t = new Thread(new WakeUpDeviceClass(awakeTime));
            t.start();
        }
    }

    public static void sendTextMessage(Context context, String[] parametersArray)
    {
        String phoneNumber, message, messageType = ActivityManageActionSendTextMessage.messageTypeSms, filePath = null;

        phoneNumber = parametersArray[0];
        message = parametersArray[1];

        if (parametersArray.length > 2)
        {
            messageType = parametersArray[2];

            if (parametersArray.length > 3)
                filePath = parametersArray[3];
        }

        try
        {
            String textToSend = Miscellaneous.replaceVariablesInText(message, context);
            if (messageType.equals("sms"))
                sendSmsMessage(phoneNumber, textToSend);
            else
                sendMmsMessage(phoneNumber, textToSend, filePath);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", Miscellaneous.getAnyContext().getString(R.string.sendTextMessage), "Error in sendTextMessage: " + Log.getStackTraceString(e), 3);
        }
    }

    private static void sendSmsMessage(String phoneNumber, String textToSend)
    {
        try
        {
            PendingIntent pi = PendingIntent.getActivity(context, 0, new Intent(context, Actions.class), 0);
            SmsManager sms = SmsManager.getDefault();
            sms.sendTextMessage(phoneNumber, null, textToSend, pi, null);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", Miscellaneous.getAnyContext().getString(R.string.sendTextMessage), "Error in sendTextMessage: " + Log.getStackTraceString(e), 3);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static void sendMmsMessage(String phoneNumber, String textToSend, String fileToBeAttached)
    {
        try
        {
            Miscellaneous.logEvent("i", "sendMmsMessage()", "Sending MMS message...", 2);
//			Uri contentUri = Uri.fromFile(new File(fileToBeAttached));
            String str2 = "send." + String.valueOf(Math.abs(new Random().nextLong())) + ".dat";
            Uri contentUri = new Uri.Builder().authority(context.getPackageName() + ".MmsFileProvider").path(str2).scheme("content").build();

//			Bundle a3 = C3326a.m16196a(new C8792q("enableGroupMms", true), new C8792q("maxMessageSize", Integer.valueOf(C3664a.m17428d())));

            SmsManager.getDefault().sendMultimediaMessage(context, contentUri, phoneNumber, null, null);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "sendMmsMessage()", "Error in sendMmsMessage(): " + Log.getStackTraceString(e), 3);
        }
    }

    private static class WakeUpDeviceClass implements Runnable
    {
        private long awakeTime;

        public WakeUpDeviceClass(long awakeTime)
        {
            super();
            this.awakeTime = awakeTime;
        }

        @Override
        public void run()
        {
            try
            {
                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                WakeLock wakeLock = pm.newWakeLock((PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP), "Automation:Wakelock");
                wakeLock.acquire();

                try
                {
                    Thread.sleep(awakeTime);
                }
                catch (InterruptedException e)
                {
                    Miscellaneous.logEvent("w", context.getResources().getString(R.string.wakeupDevice), "Error keeping device awake: " + Log.getStackTraceString(e), 4);
                }

                wakeLock.release();
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("e", "Wakeup device action", "Error while waking up device: " + Log.getStackTraceString(e), 1);
            }
        }
    }

	/*public static void turnOnScreen()
	{
		// turn on screen
		Miscellaneous.logEvent("i", "Actions", "Turning screen on.", 3);
		PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
		WakeLock wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, AutomationService.NOTIFICATION_CHANNEL_ID + ":turnOffScreen");
		wakeLock.acquire();
	}*/

    @TargetApi(21) //Suppress lint error for PROXIMITY_SCREEN_OFF_WAKE_LOCK
    public static void turnOffScreen()
    {
        Miscellaneous.logEvent("i", "Actions", "Turning screen off.", 3);


		/*params.flags |= LayoutParams.FLAG_KEEP_SCREEN_ON;
		params.screenBrightness = 0;
		getWindow().setAttributes(params);*/

//		PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
//		WakeLock wakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,AutomationService.NOTIFICATION_CHANNEL_ID + ":turnOffScreen");
//		WakeLock wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK , AutomationService.NOTIFICATION_CHANNEL_ID + ":turnOffScreen");
//		wakeLock.acquire();

//		WakeLock wakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "tag");
//		WakeLock wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK , "tag");
//		wakeLock.acquire();

//		wakeLock.release();
        lockScreen();
    }

    public static void lockScreen()
    {
        Miscellaneous.logEvent("i", "Actions", "Locking screen.", 3);

        // Works, but requires Manifest.permission.BIND_DEVICE_ADMIN
//		https://stackoverflow.com/questions/23898406/java-lang-securityexception-no-active-admin-owned-by-uid-10047-for-policy-4-on
        DevicePolicyManager deviceManager = (DevicePolicyManager) Miscellaneous.getAnyContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
        deviceManager.lockNow();
    }

    // using root
	/*private void turnOffScreen()
	{
		try
		{
			Class c = Class.forName("android.os.PowerManager");
			PowerManager  mPowerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
			for(Method m : c.getDeclaredMethods())
			{
				if(m.getName().equals("goToSleep"))
				{
					m.setAccessible(true);
					if(m.getParameterTypes().length == 1)
					{
						m.invoke(mPowerManager,SystemClock.uptimeMillis()-2);
					}
				}
			}
		}
		catch (Exception e)
		{
		}
	}*/

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @SuppressLint("NewApi")
    public static boolean setAirplaneMode(boolean desiredState, boolean toggleActionIfPossible)
    {
		/*
			Beginning from SDK Version 17 this may not work anymore.
			Setting airplane_mode_on has moved from android.provider.Settings.System to android.provider.Settings.Global, value is unchanged.

			https://stackoverflow.com/questions/22349928/permission-denial-not-allowed-to-send-broadcast-android-intent-action-airplane
			https://stackoverflow.com/questions/7066427/turn-off-airplane-mode-in-android
		 */

        boolean returnValue = false;

        try
        {
            boolean isEnabled = ConnectivityReceiver.isAirplaneMode(automationServerRef);

            if (isEnabled)
                Miscellaneous.logEvent("i", "Airplane mode", "Current status is enabled.", 4);
            else
                Miscellaneous.logEvent("i", "Airplane mode", "Current status is disabled.", 4);

            if (toggleActionIfPossible)
            {
                Miscellaneous.logEvent("i", "Airplane mode", context.getResources().getString(R.string.toggling), 4);
                desiredState = !isEnabled;
            }

            if (isEnabled != desiredState)
            {
                int desiredValueInt = 0;

                if (desiredState)
                    desiredValueInt = 1;

                if (Build.VERSION.SDK_INT < 17 || ActivityPermissions.havePermission(Manifest.permission.WRITE_SECURE_SETTINGS, context))
                {
                    //returnValue = android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.AIRPLANE_MODE_ON, desiredValueInt);

                    returnValue = android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.Global.AIRPLANE_MODE_ON, desiredValueInt);

//					Intent airplaneIntent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
//					airplaneIntent.putExtra("state", desiredState);
//					context.sendBroadcast(airplaneIntent);
                }
                else
                {
                    if (desiredState)
                    {
                        String[] commands = new String[]
                                {
                                        "settings put global airplane_mode_on 1",
                                        "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state true"
                                };
                        returnValue = executeCommandViaSu(commands);
                    }
                    else
                    {
                        String[] commands = new String[]
                                {
                                        "settings put global airplane_mode_on 0",
                                        "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false"
                                };
                        returnValue = executeCommandViaSu(commands);
                    }

//					returnValue = android.provider.Settings.Global.putString(context.getContentResolver(), "airplane_mode_on", String.valueOf(desiredValueInt));
                }

                // Post an intent to reload
                Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
                intent.putExtra("state", !isEnabled);
                context.sendBroadcast(intent);
            }
            else
                Miscellaneous.logEvent("i", "Airplane mode", "Airplane mode is already in status " + String.valueOf(desiredState) + ". Nothing to do.", 3);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "Airplane mode", Log.getStackTraceString(e), 2);
        }

        return returnValue;
    }

    /**
     * Toggles the device between the different types of networks.
     * Might not work. It seems only system apps are allowed to do this.
     */
    public static boolean setNetworkType(int desiredType)
    {
        Miscellaneous.logEvent("i", "setNetworkType", "Asked to set network type to: " + String.valueOf(desiredType), 3);
        if (desiredType > 0)
        {
            try
            {
                ConnectivityManager connManager = (ConnectivityManager) Miscellaneous.getAnyContext().getSystemService(context.CONNECTIVITY_SERVICE);

                //			TelephonyManager.NETWORK_TYPE_EDGE

                if (connManager.getNetworkPreference() == desiredType)
                {
                    Miscellaneous.logEvent("i", "setNetworkType", "Desired networkType already set. Not doing anything.", 4);
                }
                else
                {
                    Miscellaneous.logEvent("i", "setNetworkType", "Setting network type to: " + String.valueOf(desiredType), 3);
                    connManager.setNetworkPreference(desiredType);
                }
                return true;
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("e", "setNetworkType", "Error changing network type: " + Log.getStackTraceString(e), 2);
                return false;
            }
        }
        else
        {
            Miscellaneous.logEvent("w", "setNetworkType", "Invalid type of network specified: " + String.valueOf(desiredType), 4);
            return false;
        }
    }

    public static void speakText(String parameter2)
    {
        try
        {
            String textToSpeak = Miscellaneous.replaceVariablesInText(parameter2, context);
            automationServerRef.speak(textToSpeak, true);
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "Speak text", "Error in speak text: " + Log.getStackTraceString(e), 3);
        }
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
    public static boolean playMusic(boolean desiredState, boolean toggleActionIfPossible)
    {
        try
        {
            //TODO:toggle
            //		if(desiredState)
            //		{
            Miscellaneous.logEvent("e", "Play music", "Starting music player...", 3);

            String deviceName = Build.MODEL;
            /*
             * Fix for Samsung devices: http://stackoverflow.com/questions/12532207/open-music-player-on-galaxy-s3
             */
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 | deviceName.contains("SM-"))
                playMusicIntent = new Intent(Intent.CATEGORY_APP_MUSIC);
            else
                playMusicIntent = new Intent(MediaStore.INTENT_ACTION_MUSIC_PLAYER);

            playMusicIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(playMusicIntent);

            return true;
        }
        catch (ActivityNotFoundException e)
        {
            Toast.makeText(context, "Error: No music player found.", Toast.LENGTH_LONG).show();
            Miscellaneous.logEvent("e", "Play music", "Error in playerMusic(): No music player found. " + Log.getStackTraceString(e), 3);
            return false;
        }
        catch (Exception e)
        {
            Toast.makeText(context, "Error starting music player.", Toast.LENGTH_LONG).show();
            Miscellaneous.logEvent("e", "Play music", "Error in playerMusic(): " + Log.getStackTraceString(e), 3);
            return false;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static boolean controlMediaPlayback(Context context, int command)
    {
        int keyCode = -1;
        switch (command)
        {
            case 0:
                keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
                break;
            case 1:
                keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
                break;
            case 2:
                keyCode = KeyEvent.KEYCODE_MEDIA_PAUSE;
                break;
            case 3:
                keyCode = KeyEvent.KEYCODE_MEDIA_STOP;
                break;
            case 4:
                keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
                break;
            case 5:
                keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
                break;
        }

        return controlMediaByDispatch(keyCode);
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    static boolean controlMediaByDispatch(int keyCode)
    {
        AudioManager mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
        mAudioManager.dispatchMediaKeyEvent(event);

        return true;
    }

    private String getTransactionCode()
    {
        try
        {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
            Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
            getITelephonyMethod.setAccessible(true);
            Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
            Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());

            Class stub = ITelephonyClass.getDeclaringClass();
            Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled");
            field.setAccessible(true);
            return String.valueOf(field.getInt(null));
        }
        catch (Exception e)
        {
            if (Build.VERSION.SDK_INT >= 22)
                return "86";
            else if (Build.VERSION.SDK_INT == 21)
                return "83";
        }
        return "";
    }

    private static String getTransactionCodeFromApi20(Context context) throws Exception
    {
        try
        {
            final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
            final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
            mTelephonyMethod.setAccessible(true);
            final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
            final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
            final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
            final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
            field.setAccessible(true);
            return String.valueOf(field.getInt(null));
        }
        catch (Exception e)
        {
            // The "TRANSACTION_setDataEnabled" field is not available,
            // or named differently in the current API level, so we throw
            // an exception and inform users that the method is not available.
            throw e;
        }
    }

    public static class MobileDataStuff
    {
        // https://stackoverflow.com/questions/31120082/latest-update-on-enabling-and-disabling-mobile-data-programmatically

        /**
         * Turns data on and off.
         * Requires root permissions from lollipop on.
         *
         * @param toggleActionIfPossible
         */
        public static boolean setDataConnection(boolean desiredState, boolean toggleActionIfPossible)
        {
            Miscellaneous.logEvent("i", "setData", "Asked to turn data to: " + String.valueOf(desiredState), 3);

            try
            {
                ConnectivityManager connManager = (ConnectivityManager) Miscellaneous.getAnyContext().getSystemService(Miscellaneous.getAnyContext().CONNECTIVITY_SERVICE);
                final Class conmanClass = Class.forName(connManager.getClass().getName());
                final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
                iConnectivityManagerField.setAccessible(true);
                final Object iConnectivityManager = iConnectivityManagerField.get(connManager);
                final Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());

                Boolean isEnabled = isMobileDataEnabled();

                if (toggleActionIfPossible)
                {
                    context.getResources().getString(R.string.toggling);
                    desiredState = !isEnabled;
                }

                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH)
                {
                    for (Method m : iConnectivityManagerClass.getDeclaredMethods())
                    {
                        Miscellaneous.logEvent("i", "method", m.getName(), 5);
                    }

                    final Method setMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
                    setMobileDataEnabledMethod.setAccessible(true);

                    setMobileDataEnabledMethod.invoke(iConnectivityManager, desiredState);
                }
                else
                {
                    return setDataConnectionWithRoot(desiredState);
                }

                return true;
            }
            catch (Exception e)
            {
                Miscellaneous.logEvent("e", "setData", "Error setting data connection: " + Log.getStackTraceString(e), 2);
                return false;
            }
        }

        protected static boolean setDataConnectionWithRoot(boolean desiredState)
        {
            try
            {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
                {
                    if (MobileDataStuff.setMobileNetworkFromAndroid9(desiredState, automationServerRef))
                    {
                        Miscellaneous.logEvent("i", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootSuccess), 2);
                        return true;
                    }
                    else
                    {
                        Miscellaneous.logEvent("e", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootFail), 2);
                        return false;
                    }
                }
                else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
                {
                    if (MobileDataStuff.setMobileNetworkAndroid6Till8(desiredState, automationServerRef))
                    {
                        Miscellaneous.logEvent("i", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootSuccess), 2);
                        return true;
                    }
                    else
                    {
                        Miscellaneous.logEvent("e", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootFail), 2);
                        return false;
                    }
                }
                else
                {
                    if (MobileDataStuff.setMobileNetworkTillAndroid5(desiredState, automationServerRef))
                    {
                        Miscellaneous.logEvent("i", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootSuccess), 2);
                        return true;
                    }
                    else
                    {
                        Miscellaneous.logEvent("e", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootFail), 2);
                        return false;
                    }
                }
            }
            catch (Exception e)
            {
                String rootString;
                if (Miscellaneous.isPhoneRooted())
                    rootString = Miscellaneous.getAnyContext().getResources().getString(R.string.phoneIsRooted);
                else
                    rootString = Miscellaneous.getAnyContext().getResources().getString(R.string.phoneIsNotRooted);

                Miscellaneous.logEvent("e", "setDataWithRoot()", "Error setting data setting with root. " + rootString + ": " + Log.getStackTraceString(e), 3);
            }

            return false;
        }

        @SuppressLint("NewApi")
        public static boolean setMobileNetworkAndroid6Till8(boolean desiredState, Context context) throws Exception
        {
            String command = null;

            try
            {
                int desiredStateString;
                if (desiredState)
                    desiredStateString = 1;
                else
                    desiredStateString = 0;

                // Get the current state of the mobile network.
//				boolean state = isMobileDataEnabled() ? 0 : 1;
                // Get the value of the "TRANSACTION_setDataEnabled" field.
                String transactionCode = getTransactionCode(context);
                // Android 5.1+ (API 22) and later.
                SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
                // Loop through the subscription list i.e. SIM list.
                for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++)
                {
                    if (transactionCode != null && transactionCode.length() > 0)
                    {
                        // Get the active subscription ID for a given SIM card.
                        int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId();
                        // Execute the command via `su` to turn off
                        // mobile network for a subscription service.
                        command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + desiredStateString;
                        Miscellaneous.logEvent("i", "setMobileNetworkAndroid6Till8()", "Running command: " + command.toString(), 5);
                        return executeCommandViaSu(new String[]{command});
                    }
                }
            }
            catch (Exception e)
            {
                // Oops! Something went wrong, so we throw the exception here.
                throw e;
            }

            return false;
        }

        @SuppressLint("NewApi")
        public static boolean setMobileNetworkTillAndroid5(boolean desiredState, Context context) throws Exception
        {
            String command = null;

            try
            {
                int desiredStateString;
                if (desiredState)
                    desiredStateString = 1;
                else
                    desiredStateString = 0;

                // Get the current state of the mobile network.
//				int currentState = isMobileDataEnabled() ? 0 : 1;
                // Get the value of the "TRANSACTION_setDataEnabled" field.
                String transactionCode = getTransactionCode(context);
                // Android 5.0 (API 21) only.
                if (transactionCode != null && transactionCode.length() > 0)
                {
                    // Execute the command via `su` to turn off mobile network.
                    command = "service call phone " + transactionCode + " i32 " + desiredStateString;
                    Miscellaneous.logEvent("i", "setMobileNetworkTillAndroid5()", "Running command: " + command.toString(), 5);
                    return executeCommandViaSu(new String[]{command});
                }
            }
            catch (Exception e)
            {
                // Oops! Something went wrong, so we throw the exception here.
                throw e;
            }

            return false;
        }

        @SuppressLint("NewApi")
        public static boolean setMobileNetworkFromAndroid9(boolean desiredState, Context context) throws Exception
        {
            String command = null;

            String desiredStateString;
            if (desiredState)
                desiredStateString = "enable";
            else
                desiredStateString = "disable";

            try
            {
				/*
					Android 8.1 is the last version on which the transaction code can be determined
					with the below method. From 9.0 on the field TRANSACTION_setDataEnabled does not
					exist anymore. Usually it was 83 and we'll just try this number hardcoded.
					Alternatively the bottom of this might be an approach:
					https://stackoverflow.com/questions/26539445/the-setmobiledataenabled-method-is-no-longer-callable-as-of-android-l-and-later
				 */

                // Execute the command via `su` to turn off
                // mobile network for a subscription service.
                command = "svc data " + desiredStateString;
                Miscellaneous.logEvent("i", "setMobileNetworkFromAndroid9()", "Running command: " + command.toString(), 5);
                return executeCommandViaSu(new String[]{command});
            }
            catch (Exception e)
            {
                // Oops! Something went wrong, so we throw the exception here.
                throw e;
            }
        }

        @SuppressLint("NewApi")
        public static boolean isMobileDataEnabled()
        {
            boolean isEnabled = false;

            if (Build.VERSION.SDK_INT <= 20)
            {
                try
                {
                    ConnectivityManager connManager = (ConnectivityManager) Miscellaneous.getAnyContext().getSystemService(Miscellaneous.getAnyContext().CONNECTIVITY_SERVICE);
                    final Class conmanClass = Class.forName(connManager.getClass().getName());
                    final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
                    iConnectivityManagerField.setAccessible(true);
                    final Object iConnectivityManager = iConnectivityManagerField.get(connManager);
                    final Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());

                    Method getMobileDataEnabledMethod = null;
                    for (Method m : iConnectivityManagerClass.getDeclaredMethods())
                    {
                        Miscellaneous.logEvent("i", "Methods", m.getName(), 5);
                        if (m.getName().equals("getMobileDataEnabled"))
                        {
                            getMobileDataEnabledMethod = m;
                            break;
                        }
                    }
                    //		    final Method getMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("getMobileDataEnabled", null);

                    getMobileDataEnabledMethod.setAccessible(true);

                    isEnabled = (Boolean) getMobileDataEnabledMethod.invoke(iConnectivityManager, (Object[]) null);
                }
                catch (Exception e)
                {
                    Miscellaneous.logEvent("e", "isMobileDataEnabled()", "Error checking if mobile data is enabled: " + Log.getStackTraceString(e), 3);
                }
            }
            else
            {
                isEnabled = android.provider.Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1;
            }

            return isEnabled;
        }

        private static String getTransactionCode(Context context) throws Exception
        {
            try
            {
                final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
                final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
                final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
                mTelephonyMethod.setAccessible(true);
                final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
                final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
                final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
                final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
                field.setAccessible(true);
                return String.valueOf(field.getInt(null));
            }
            catch (Exception e)
            {
                // The "TRANSACTION_setDataEnabled" field is not available,
                // or named differently in the current API level, so we throw
                // an exception and inform users that the method is not available.
                throw e;
            }
        }
    }

    protected static boolean executeCommandViaSu(String[] commands)
    {
        boolean suAvailable = false;
        String suVersion = null;
        String suVersionInternal = null;
        int suResult;

        boolean success = false;

        try
        {
            suAvailable = Shell.SU.available();
            if (suAvailable)
            {
                suVersion = Shell.SU.version(false);
                suVersionInternal = Shell.SU.version(true);

                Miscellaneous.logEvent("i", "executeCommandViaSu()", "suVersion: " + suVersion + ", suVersionInternal: " + suVersionInternal, 5);
                Miscellaneous.logEvent("i", "executeCommandViaSu()", "calling method: " + Miscellaneous.getCallingMethodName(), 5);

                suResult = Shell.Pool.SU.run(commands);

                if (Miscellaneous.getCallingMethodName().equals("runExecutable"))
                {
                    Actions.setVariable("last_run_executable_exit_code" + Action.actionParameter2Split + String.valueOf(suResult));
//					Actions.setVariable("last_run_executable_output" + Action.actionParameter2Split + (String) result[1]);
                }

                Miscellaneous.logEvent("i", "executeCommandViaSu()", "RC=" + String.valueOf(suResult), 3);

                if (suResult == 0)
                    success = true;
            }
            else
                Miscellaneous.logEvent("w", "executeCommandViaSu()", "su not available.", 4);
        }
        catch (Exception e)
        {
            success = false;
        }

        Miscellaneous.logEvent("i", "executeCommandViaSu()", "Returning " + String.valueOf(success), 4);

        return success;
    }

    public static void setScreenBrightness(boolean autoBrightness, int brightnessValue)
    {
        if (autoBrightness)
            android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
        else
            android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);

        // Old mathematical approach doesn't seem to be working anymore
//		int actualBrightnessValue = (int) ((float) brightnessValue / 100.0 * 255.0);

//		try
//		{
//			for (int i = 160; i < 255; i++)
//			{
//				android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, i);
//				Thread.sleep(1000);
//				Log.i("Brightness", "Set: " + String.valueOf(i) + ", result: " + String.valueOf(android.provider.Settings.System.getInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, -1)));
//			}
//		}
//		catch (Exception e)
//		{
//
//		}

        int actualBrightnessValue = getBrightnessSetting(brightnessValue);

        Miscellaneous.logEvent("i", "Screen brightness", "Setting screen brightness to " + String.valueOf(brightnessValue) + " by using " + String.valueOf(actualBrightnessValue), 4);
        android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, actualBrightnessValue);
    }

    static int getBrightnessSetting(int percentage)
    {
        if (percentage == 0)
            return 1;    // seems to be the minimum, 0 isn't working

		/*
			By taking notes which value results in which brightness percentage I came up with this lookup list.
		 	Somebody at Google must have been smoking something.

		 	Not all percentages can be implemented. Hence the next possible value will be used.
		 */

        Map<Integer, Integer> percentageSettingValueMap = new HashMap<>();

        if (Build.VERSION.SDK_INT < 34)
        {
            // Noted from Android 10
            percentageSettingValueMap.put(11, 10);
            percentageSettingValueMap.put(16, 11);
            percentageSettingValueMap.put(19, 12);
            percentageSettingValueMap.put(22, 13);
            percentageSettingValueMap.put(25, 14);
            percentageSettingValueMap.put(27, 15);
            percentageSettingValueMap.put(29, 16);
            percentageSettingValueMap.put(31, 17);
            percentageSettingValueMap.put(33, 18);
            percentageSettingValueMap.put(35, 19);
            percentageSettingValueMap.put(37, 20);
            percentageSettingValueMap.put(38, 21);
            percentageSettingValueMap.put(40, 22);
            percentageSettingValueMap.put(41, 23);
            percentageSettingValueMap.put(43, 24);
            percentageSettingValueMap.put(44, 25);
            percentageSettingValueMap.put(46, 26);
            percentageSettingValueMap.put(47, 27);
            percentageSettingValueMap.put(48, 28);
            percentageSettingValueMap.put(49, 29);
            percentageSettingValueMap.put(51, 30);
            percentageSettingValueMap.put(52, 31);
            percentageSettingValueMap.put(53, 32);
            percentageSettingValueMap.put(54, 33);
            percentageSettingValueMap.put(55, 34);
            percentageSettingValueMap.put(56, 35);
            percentageSettingValueMap.put(57, 36);
            percentageSettingValueMap.put(58, 38);
            percentageSettingValueMap.put(59, 39);
            percentageSettingValueMap.put(60, 40);
            percentageSettingValueMap.put(61, 42);
            percentageSettingValueMap.put(62, 43);
            percentageSettingValueMap.put(63, 45);
            percentageSettingValueMap.put(64, 46);
            percentageSettingValueMap.put(65, 48);
            percentageSettingValueMap.put(66, 50);
            percentageSettingValueMap.put(67, 52);
            percentageSettingValueMap.put(68, 54);
            percentageSettingValueMap.put(69, 56);
            percentageSettingValueMap.put(70, 59);
            percentageSettingValueMap.put(71, 61);
            percentageSettingValueMap.put(72, 64);
            percentageSettingValueMap.put(73, 67);
            percentageSettingValueMap.put(74, 70);
            percentageSettingValueMap.put(75, 73);
            percentageSettingValueMap.put(76, 76);
            percentageSettingValueMap.put(77, 80);
            percentageSettingValueMap.put(78, 84);
            percentageSettingValueMap.put(79, 87);
            percentageSettingValueMap.put(80, 91);
            percentageSettingValueMap.put(81, 96);
            percentageSettingValueMap.put(82, 100);
            percentageSettingValueMap.put(83, 105);
            percentageSettingValueMap.put(84, 111);
            percentageSettingValueMap.put(85, 116);
            percentageSettingValueMap.put(86, 122);
            percentageSettingValueMap.put(87, 128);
            percentageSettingValueMap.put(88, 134);
            percentageSettingValueMap.put(89, 141);
            percentageSettingValueMap.put(90, 148);
            percentageSettingValueMap.put(91, 156);
            percentageSettingValueMap.put(92, 164);
            percentageSettingValueMap.put(93, 173);
            percentageSettingValueMap.put(94, 182);
            percentageSettingValueMap.put(95, 191);
            percentageSettingValueMap.put(96, 201);
            percentageSettingValueMap.put(97, 212);
            percentageSettingValueMap.put(98, 223);
            percentageSettingValueMap.put(99, 235);
            percentageSettingValueMap.put(100, 247);
        }
        else
        {
            // Noted from Android 14
            percentageSettingValueMap.put(11, 1);
            percentageSettingValueMap.put(15, 2);
            percentageSettingValueMap.put(19, 3);
            percentageSettingValueMap.put(22, 4);
            percentageSettingValueMap.put(24, 5);
            percentageSettingValueMap.put(27, 6);
            percentageSettingValueMap.put(29, 7);
            percentageSettingValueMap.put(31, 8);
            percentageSettingValueMap.put(33, 9);
            percentageSettingValueMap.put(34, 10);
            percentageSettingValueMap.put(36, 11);
            percentageSettingValueMap.put(38, 12);
            percentageSettingValueMap.put(39, 13);
            percentageSettingValueMap.put(41, 14);
            percentageSettingValueMap.put(42, 15);
            percentageSettingValueMap.put(43, 16);
            percentageSettingValueMap.put(45, 17);
            percentageSettingValueMap.put(46, 18);
            percentageSettingValueMap.put(47, 47);
            percentageSettingValueMap.put(48, 49);
            percentageSettingValueMap.put(50, 21);
            percentageSettingValueMap.put(51, 22);
            percentageSettingValueMap.put(52, 23);
            percentageSettingValueMap.put(53, 24);
            percentageSettingValueMap.put(54, 25);
            percentageSettingValueMap.put(55, 26);
            percentageSettingValueMap.put(56, 27);
            percentageSettingValueMap.put(57, 28);
            percentageSettingValueMap.put(58, 30);
            percentageSettingValueMap.put(59, 31);
            percentageSettingValueMap.put(60, 32);
            percentageSettingValueMap.put(61, 34);
            percentageSettingValueMap.put(62, 35);
            percentageSettingValueMap.put(63, 37);
            percentageSettingValueMap.put(64, 39);
            percentageSettingValueMap.put(65, 41);
            percentageSettingValueMap.put(66, 43);
            percentageSettingValueMap.put(67, 45);
            percentageSettingValueMap.put(68, 47);
            percentageSettingValueMap.put(69, 49);
            percentageSettingValueMap.put(70, 52);
            percentageSettingValueMap.put(71, 54);
            percentageSettingValueMap.put(72, 57);
            percentageSettingValueMap.put(73, 60);
            percentageSettingValueMap.put(74, 63);
            percentageSettingValueMap.put(75, 66);
            percentageSettingValueMap.put(76, 70);
            percentageSettingValueMap.put(77, 73);
            percentageSettingValueMap.put(78, 77);
            percentageSettingValueMap.put(79, 81);
            percentageSettingValueMap.put(80, 85);
            percentageSettingValueMap.put(81, 90);
            percentageSettingValueMap.put(82, 95);
            percentageSettingValueMap.put(83, 100);
            percentageSettingValueMap.put(84, 105);
            percentageSettingValueMap.put(85, 111);
            percentageSettingValueMap.put(86, 117);
            percentageSettingValueMap.put(87, 124);
            percentageSettingValueMap.put(88, 130);
            percentageSettingValueMap.put(89, 137);
            percentageSettingValueMap.put(90, 144);
            percentageSettingValueMap.put(91, 152);
            percentageSettingValueMap.put(92, 161);
            percentageSettingValueMap.put(93, 170);
            percentageSettingValueMap.put(94, 179);
            percentageSettingValueMap.put(95, 189);
            percentageSettingValueMap.put(96, 199);
            percentageSettingValueMap.put(97, 210);
            percentageSettingValueMap.put(98, 223);
            percentageSettingValueMap.put(99, 235);
            percentageSettingValueMap.put(100, 248);
        }

        if (percentageSettingValueMap.containsKey(percentage))
            return percentageSettingValueMap.get(percentage);
        else
        {
            // Find next best value
            for (int i = percentage; i <= 100; i++)
            {
                if (percentageSettingValueMap.containsKey(i))
                    return percentageSettingValueMap.get(i);
            }
        }

        return 0;
    }

    public boolean isAirplaneModeOn(Context context)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
        {
            return android.provider.Settings.System.getInt(context.getContentResolver(), android.provider.Settings.System.AIRPLANE_MODE_ON, 0) != 0;
        }
        else
        {
            return android.provider.Settings.Global.getInt(context.getContentResolver(), android.provider.Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
        }
    }

    public static boolean runExecutable(Context context, boolean runAsRoot, String path, String parameters)
    {
        if (runAsRoot)
        {
            if (!StringUtils.isEmpty(parameters))
                return executeCommandViaSu(new String[]{path + " " + parameters});
            else
                return executeCommandViaSu(new String[]{path});
        }
        else
        {
            Object[] result;

            File executable = new File(path);
            File workingDir = new File(executable.getParent());

            if (!StringUtils.isEmpty(parameters))
                result = runExternalApplication(path, 0, workingDir, parameters);
            else
                result = runExternalApplication(path, 0, workingDir, null);

            boolean execResult = ((int) result[0] == 0);

            Actions.setVariable("last_run_executable_exit_code" + Action.actionParameter2Split + String.valueOf((int) result[0]));
            Actions.setVariable("last_run_executable_output" + Action.actionParameter2Split + (String) result[1]);

            return execResult;
        }
    }

    /**
     * @param commandToExecute
     * @param timeout
     * @param params
     * @return Returns an array: 0=exit code, 1=cmdline output
     */
    public static Object[] runExternalApplication(String commandToExecute, long timeout, File workingDirectory, String params)
    {
        /*
         * Classes stolen from https://github.com/stleary/JSON-java
         */

        String fullCommand;

        if (!StringUtils.isEmpty(params))
            fullCommand = commandToExecute + " " + params;
        else
            fullCommand = commandToExecute;

        Miscellaneous.logEvent("i", "Running executable", "Running external application " + fullCommand, 4);

        Object[] returnObject = new Object[2];

        StringBuilder output = new StringBuilder();
        String line = null;
        OutputStream stdin = null;
        InputStream stderr = null;
        InputStream stdout = null;

        try
        {
            Process process = null;

            if (workingDirectory != null)
                process = Runtime.getRuntime().exec(fullCommand, null, workingDirectory);
            else
                process = Runtime.getRuntime().exec(fullCommand);
            stdin = process.getOutputStream();
            stderr = process.getErrorStream();
            stdout = process.getInputStream();

            stdin.close();

            // clean up if any output in stdout
            BufferedReader brCleanUp = new BufferedReader(new InputStreamReader(stdout));
            while ((line = brCleanUp.readLine()) != null)
            {
                Miscellaneous.logEvent("i", "Running executable", "[Stdout] " + line, 4);
                output.append(line);
            }
            brCleanUp.close();

            // clean up if any output in stderr
            brCleanUp = new BufferedReader(new InputStreamReader(stderr));
            while ((line = brCleanUp.readLine()) != null)
            {
                Miscellaneous.logEvent("i", "Running executable", "[Stderr] " + line, 4);
                output.append(line);
            }
            brCleanUp.close();

            try
            {
                // Wait for the process to exit, we want the return code
                if (timeout > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                {
                    try
                    {
                        if (!process.waitFor(timeout, TimeUnit.MILLISECONDS))
                        {
                            Miscellaneous.logEvent("i", "Running executable", "Timeout of " + String.valueOf(timeout) + " ms reached. Killing check attempt.", 3);
                            process.destroyForcibly();
                        }
                    }
                    catch (NoSuchMethodError e)
                    {
                        process.waitFor();
                    }
                }
                else
                    process.waitFor();
            }
            catch (InterruptedException e)
            {
                Miscellaneous.logEvent("i", "Running executable", "Waiting for process failed: " + Log.getStackTraceString(e), 4);
                Miscellaneous.logEvent("i", "Running executable", Log.getStackTraceString(e), 1);
            }

            if (process.exitValue() == 0)
                Miscellaneous.logEvent("i", "Running executable", "ReturnCode: " + String.valueOf(process.exitValue()), 4);
            else
                Miscellaneous.logEvent("i", "Running executable", "External execution (RC=" + String.valueOf(process.exitValue()) + ") returned error: " + output.toString(), 3);

            returnObject[0] = process.exitValue();
            returnObject[1] = output.toString();

            return returnObject;
        }
        catch (IOException e)
        {
            Miscellaneous.logEvent("e", "Running executable", Log.getStackTraceString(e), 1);
        }

        Miscellaneous.logEvent("i", "Running executable", "Error running external application.", 1);

        return null;
    }

    public static boolean isTetheringActive1(Context context)
    {
        try
        {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); )
            {
                NetworkInterface intf = en.nextElement();

                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); )
                {
                    InetAddress inetAddress = enumIpAddr.nextElement();

                    if (!intf.isLoopback())
                    {
                        if (intf.getName().contains("rndis"))
                        {
                            return true;
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
            Miscellaneous.logEvent("e", "isTetheringActive()", Log.getStackTraceString(e), 3);
        }

        return false;
    }

    public final static int wakeLockTimeoutDisabled = -1;
    static boolean wakeLockStopRequested = false;

    public static void wakeLockStart(Context context, long duration)
    {
        Thread lockThread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                wakeLockStopRequested = false;

                long waited = 0;
                int step = 2000;

                try
                {
                    PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                    PowerManager.WakeLock fullWakeLock = powerManager.newWakeLock((PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP), "Loneworker - FULL WAKE LOCK");
                    fullWakeLock.acquire(); // turn on

                    do
                    {

                        try
                        {
                            Thread.sleep(step); // turn on duration
                        }
                        catch (InterruptedException e)
                        {
                            e.printStackTrace();
                        }

                        if (duration > 0)
                            waited += step;

                        if (wakeLockStopRequested) //stop requested
                            Miscellaneous.logEvent("i", "WakeLockStart", "Stop requested.", 4);
                    }
                    while (!wakeLockStopRequested && (duration < 0 || waited <= duration));

                    fullWakeLock.release();
                }
                catch (Exception e)
                {
                }
            }
        });

        lockThread.start();
    }

    public static void wakeLockStop()
    {
        Miscellaneous.logEvent("i", "WakeLockStart", "Requesting stop.", 4);
        wakeLockStopRequested = true;
    }

    public static void startPhoneCall(Context context, String phoneNumber)
    {
        Intent intent;

		/*
			Bug in Android 14 makes it necessary to add double quotes around MMI code.
			More precisely it's required for codes containing the # character.
		 */

        if (Build.VERSION.SDK_INT >= 34)
            intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + Uri.encode("\"" + phoneNumber + "\"")));
        else
            intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + Uri.encode(phoneNumber)));

//			intent.setClassName("com.android.phone","com.android.phone.OutgoingCallBroadcaster");
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_FROM_BACKGROUND);
        context.startActivity(intent);
    }

    public static void endPhoneCall(Context context)
    {
        if (Build.VERSION.SDK_INT < 21)
        {
            TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            try
            {
                Class c = Class.forName(tm.getClass().getName());
                Method m = c.getDeclaredMethod("getITelephony");
                m.setAccessible(true);
                Object telephonyService = m.invoke(tm);

                c = Class.forName(telephonyService.getClass().getName());
                m = c.getDeclaredMethod("endCall");
                m.setAccessible(true);
                m.invoke(telephonyService);

            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
        else
        {
            TelecomManager mgr = (TelecomManager) context.getSystemService(context.TELECOM_SERVICE);
            mgr.endCall();
        }
    }

    public static void copyToClipboard(Context context, String text)
    {
        Miscellaneous.logEvent("i", "Clipboard", "Copying data to clipboard: " + text, 4);

        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
        {
            android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
            clipboard.setText(text);
        }
        else
        {
            android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
            android.content.ClipData clip = android.content.ClipData.newPlainText("Data-from-Automation", text);
            clipboard.setPrimaryClip(clip);
        }
    }

    public static void takeScreenshot()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
        {
            if (!BuildConfig.FLAVOR.equals(AutomationService.flavor_name_googleplay))
                MyAccessibilityService.getInstance().performGlobalAction(AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
        }
    }

    public static void setLocationService(int desiredState, Context context)
    {
//		if(desiredState)
//		{
//			android.provider.Settings.Secure.putString(context.getContentResolver(), android.provider.Settings.Secure.LOCATION_MODE, new Integer(android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY).toString());
//		}
//		else
//		{
//			android.provider.Settings.Secure.putString(context.getContentResolver(), android.provider.Settings.Secure.LOCATION_MODE, new Integer(android.provider.Settings.Secure.LOCATION_MODE_OFF).toString());
//		}
        android.provider.Settings.Secure.putString(context.getContentResolver(), android.provider.Settings.Secure.LOCATION_MODE, String.valueOf(desiredState));
    }

}