Automation/app/src/main/java/com/jens/automation2/Miscellaneous.java

2190 lines
62 KiB
Java
Raw Normal View History

2021-02-16 13:42:49 +01:00
package com.jens.automation2;
2021-05-16 19:51:22 +02:00
import android.Manifest;
2021-02-16 13:42:49 +01:00
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
2022-02-02 18:06:37 +01:00
import android.content.ContentUris;
2021-02-16 13:42:49 +01:00
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
2021-12-12 20:03:53 +01:00
import android.content.res.Configuration;
2023-02-26 18:12:47 +01:00
import android.content.res.Resources;
2021-02-16 13:42:49 +01:00
import android.database.Cursor;
2022-01-10 19:32:44 +01:00
import android.media.AudioAttributes;
import android.media.RingtoneManager;
2021-09-24 19:15:41 +02:00
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
2021-02-16 13:42:49 +01:00
import android.net.Uri;
2021-03-17 21:42:01 +01:00
import android.os.AsyncTask;
2021-02-16 13:42:49 +01:00
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
2022-02-02 18:06:37 +01:00
import android.provider.DocumentsContract;
2021-02-16 13:42:49 +01:00
import android.provider.MediaStore;
import android.provider.Settings.Secure;
2021-05-15 19:55:48 +02:00
import android.telephony.PhoneNumberUtils;
2022-01-27 11:34:04 +01:00
import android.telephony.TelephonyManager;
2021-02-16 13:42:49 +01:00
import android.util.Base64;
2023-02-26 18:12:47 +01:00
import android.util.DisplayMetrics;
2021-02-16 13:42:49 +01:00
import android.util.Log;
2021-03-29 16:36:21 +02:00
import android.widget.Toast;
2021-02-16 13:42:49 +01:00
import com.jens.automation2.location.LocationProvider;
import com.jens.automation2.receivers.CalendarReceiver;
2021-05-11 22:49:41 +02:00
import com.jens.automation2.receivers.NotificationListener;
2021-02-16 13:42:49 +01:00
import com.jens.automation2.receivers.PhoneStatusListener;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
2024-01-03 16:24:21 +01:00
import org.apache.http.NameValuePair;
2021-02-16 13:42:49 +01:00
import org.apache.http.client.HttpClient;
2024-01-03 16:24:21 +01:00
import org.apache.http.client.entity.UrlEncodedFormEntity;
2023-12-11 22:50:07 +01:00
import org.apache.http.client.methods.HttpGet;
2021-02-16 13:42:49 +01:00
import org.apache.http.client.methods.HttpPost;
2023-12-12 23:40:12 +01:00
import org.apache.http.client.methods.HttpRequestBase;
2021-02-16 13:42:49 +01:00
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
2024-01-03 16:24:21 +01:00
import org.apache.http.message.BasicNameValuePair;
2021-02-16 13:42:49 +01:00
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
2021-02-20 02:04:53 +01:00
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
2021-02-16 13:42:49 +01:00
2021-03-29 16:36:21 +02:00
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
2021-02-16 13:42:49 +01:00
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
2021-03-13 00:40:59 +01:00
import java.io.FileInputStream;
import java.io.FileNotFoundException;
2021-03-13 00:40:59 +01:00
import java.io.FileOutputStream;
2021-02-16 13:42:49 +01:00
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
2021-03-13 00:40:59 +01:00
import java.io.OutputStream;
2024-01-03 16:24:21 +01:00
import java.io.OutputStreamWriter;
2021-02-20 02:04:53 +01:00
import java.io.StringReader;
2023-10-19 22:30:13 +02:00
import java.io.UnsupportedEncodingException;
2021-02-16 13:42:49 +01:00
import java.lang.Thread.UncaughtExceptionHandler;
2021-02-16 23:38:38 +01:00
import java.lang.reflect.InvocationTargetException;
2021-02-16 20:24:12 +01:00
import java.lang.reflect.Method;
2021-02-16 13:42:49 +01:00
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.URL;
2023-10-19 22:30:13 +02:00
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
2022-02-02 18:06:37 +01:00
import java.security.DigestInputStream;
2021-02-16 13:42:49 +01:00
import java.security.KeyManagementException;
import java.security.KeyStore;
2022-02-02 18:06:37 +01:00
import java.security.MessageDigest;
2021-02-16 13:42:49 +01:00
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
2021-11-08 20:13:11 +01:00
import java.text.DateFormat;
import java.text.SimpleDateFormat;
2021-02-16 13:42:49 +01:00
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
2022-01-10 19:32:44 +01:00
import java.util.List;
2022-01-27 11:34:04 +01:00
import java.util.Locale;
2024-01-02 16:36:10 +01:00
import java.util.Map;
import java.util.Scanner;
2021-03-29 16:36:21 +02:00
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
2021-02-16 13:42:49 +01:00
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
2021-02-20 02:04:53 +01:00
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
2021-02-16 13:42:49 +01:00
2022-01-10 19:32:44 +01:00
import androidx.annotation.RequiresApi;
2021-03-13 00:40:59 +01:00
import androidx.core.app.NotificationCompat;
2021-05-10 19:56:54 +02:00
import androidx.documentfile.provider.DocumentFile;
2021-03-13 00:40:59 +01:00
2024-01-03 16:24:21 +01:00
import eu.chainfire.libsuperuser.Shell;
2021-02-16 13:42:49 +01:00
public class Miscellaneous extends Service
2021-03-29 16:36:21 +02:00
{
2021-02-16 20:24:12 +01:00
protected static String writeableFolderStringCache = null;
2023-01-16 23:44:28 +01:00
public static Context startupContext;
2021-02-16 13:42:49 +01:00
public static final String lineSeparator = System.getProperty("line.separator");
2024-01-02 16:36:10 +01:00
public static String downloadURL(String url, String username, String password, String method, Map<String, String> httpParams)
2021-02-16 13:42:49 +01:00
{
HttpClient httpclient = new DefaultHttpClient();
StringBuilder responseBody = new StringBuilder();
boolean errorFound = false;
try
{
try
{
URL urlObject = new URL(url);
HttpURLConnection connection;
if(url.toLowerCase().contains("https"))
{
connection = (HttpsURLConnection) urlObject.openConnection();
}
else
connection = (HttpURLConnection) urlObject.openConnection();
// Add http simple authentication if specified
if(username != null && password != null)
{
String encodedCredentials = Base64.encodeToString(new String(username + ":" + password).getBytes(), Base64.DEFAULT);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty ("Authorization", "Basic " + encodedCredentials);
}
2023-12-11 22:50:07 +01:00
else if(method.equals(ActivityManageActionTriggerUrl.methodPost))
connection.setRequestMethod("POST");
2024-01-03 16:24:21 +01:00
if(httpParams.size() > 0)
{
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
List<NameValuePair> paramPairs = new ArrayList<NameValuePair>();
for(String key : httpParams.keySet())
paramPairs.add(new BasicNameValuePair(key, httpParams.get(key)));
OutputStream os = connection.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
writer.write(getQuery(paramPairs));
writer.flush();
writer.close();
}
2021-02-16 13:42:49 +01:00
InputStream content = (InputStream)connection.getInputStream();
BufferedReader in = new BufferedReader (new InputStreamReader (content));
String line;
while ((line = in.readLine()) != null)
responseBody.append(line + Miscellaneous.lineSeparator);
}
catch(Exception e)
{
Miscellaneous.logEvent("e", "HTTP error", Log.getStackTraceString(e), 3);
errorFound = true;
}
}
finally
{
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
httpclient.getConnectionManager().shutdown();
if(errorFound)
return "httpError";
else
return responseBody.toString();
}
}
2024-01-03 16:24:21 +01:00
private static String getQuery(List<NameValuePair> params) throws UnsupportedEncodingException
{
StringBuilder result = new StringBuilder();
boolean first = true;
for (NameValuePair pair : params)
{
if (first)
first = false;
else
result.append("&");
result.append(URLEncoder.encode(pair.getName(), "UTF-8"));
result.append("=");
result.append(URLEncoder.encode(pair.getValue(), "UTF-8"));
}
return result.toString();
}
2021-02-16 13:42:49 +01:00
2024-01-06 17:25:27 +01:00
public static String downloadUrlWithoutCertificateChecking(String url, String username, String password, String method, Map<String, String> httpParams)
2021-02-16 13:42:49 +01:00
{
2023-12-12 23:40:12 +01:00
try
2021-02-16 13:42:49 +01:00
{
HttpParams params = new BasicHttpParams();
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpClient httpclient = new DefaultHttpClient(params);
httpclient = Actions.getInsecureSslClient(httpclient);
2023-12-11 22:50:07 +01:00
2023-12-12 23:40:12 +01:00
HttpRequestBase httpRequest;
if(
method.equals(ActivityManageActionTriggerUrl.methodPost)
||
(username != null && password != null)
2024-01-03 16:24:21 +01:00
||
httpParams.size() > 0
2023-12-12 23:40:12 +01:00
)
httpRequest = new HttpPost(url);
else
httpRequest = new HttpGet(url);
2021-02-16 13:42:49 +01:00
// Add http simple authentication if specified
if(username != null && password != null)
{
String encodedCredentials = Base64.encodeToString(new String(username + ":" + password).getBytes(), Base64.DEFAULT);
2023-12-11 22:50:07 +01:00
httpRequest.addHeader("Authorization", "Basic " + encodedCredentials);
2021-02-16 13:42:49 +01:00
}
2024-01-03 16:24:21 +01:00
if(httpParams.size() > 0)
{
List<NameValuePair> paramPairs = new ArrayList<NameValuePair>();
for(String key : httpParams.keySet())
paramPairs.add(new BasicNameValuePair(key, httpParams.get(key)));
((HttpPost)httpRequest).setEntity(new UrlEncodedFormEntity(paramPairs, "UTF-8"));
}
2021-02-16 13:42:49 +01:00
2023-12-11 22:50:07 +01:00
HttpResponse response = httpclient.execute(httpRequest);
2021-02-16 13:42:49 +01:00
HttpEntity entity = response.getEntity();
if (entity != null)
{
// System.out.println(EntityUtils.toString(entity));
return EntityUtils.toString(entity);
}
}
catch(Exception e)
{
Miscellaneous.logEvent("e", "HTTP error", Log.getStackTraceString(e), 3);
2023-12-12 23:40:12 +01:00
return "httpError";
2021-02-16 13:42:49 +01:00
}
// finally
// {
// // When HttpClient instance is no longer needed,
// // shut down the connection manager to ensure
// // immediate deallocation of all system resources
// httpclient.getConnectionManager().shutdown();
// return responseBody.toString();
// }
return null;
}
2022-01-05 18:06:26 +01:00
public static int boolToInt(boolean input)
{
if(input)
return 1;
else
return 0;
}
@Override
2021-02-16 13:42:49 +01:00
public IBinder onBind(Intent arg0)
{
return null;
}
public static void logEvent(String type, String header, String description, int logLevel)
{
try
{
header = getAnyContext().getResources().getString(R.string.app_name);
}
catch(NullPointerException e)
{
header = "Automation";
}
if(type.equals("e"))
Log.e(header, description);
if(type.equals("w"))
Log.w(header, description);
if(type.equals("i"))
Log.i(header, description);
if(Settings.writeLogFile && Settings.logLevel >= logLevel)
{
writeToLogFile(type, header, description);
2023-01-16 23:44:28 +01:00
if (!logCleanerRunning && Math.random() < 0.1) // tidy up with 10% probability
2021-02-16 13:42:49 +01:00
{
rotateLogFile(getLogFile());
}
}
}
protected static boolean logCleanerRunning = false;
protected static void rotateLogFile(File logFile)
{
logCleanerRunning = true;
long maxSizeInBytes = (long)Settings.logFileMaxSize * 1024 * 1024;
if(logFile.exists() && logFile.length() > (maxSizeInBytes))
{
Miscellaneous.logEvent("i", "Logfile", "Cleaning up log file.", 3);
File archivedLogFile = new File(getWriteableFolder() + "/" + logFileName + "-old");
logFile.renameTo(archivedLogFile);
Miscellaneous.logEvent("i", "Logfile", "Cleaning up log file finished. Old log renamed to " + archivedLogFile.getAbsolutePath(), 3);
}
logCleanerRunning = false;
}
protected static boolean testFolder(String folderPath)
{
File folder = new File(folderPath + "/" + Settings.folderName);
final String testFileName = "AutomationTestFile.txt";
try
{
if(folder.exists() || folder.mkdirs())
{
XmlFileInterface.migrateFilesFromRootToFolder(folderPath, folder.getAbsolutePath());
File testFile = new File(folder + "/" + testFileName);
if(!testFile.exists())
testFile.createNewFile();
if(testFile.canRead() && testFile.canWrite())
{
testFile.delete();
writeableFolderStringCache = testFile.getParent();
Miscellaneous.logEvent("i", "File", "Test of " + folder.getAbsolutePath() + " succeeded.", 3);
return true;
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
Miscellaneous.logEvent("w", "File", "Test of " + folder.getAbsolutePath() + " failed.", 3);
return false;
}
public static String getWriteableFolder()
{
if(writeableFolderStringCache == null)
{
2021-03-12 22:38:52 +01:00
// Use the app-specific folder as new default.
writeableFolderStringCache = Miscellaneous.getAnyContext().getFilesDir().getAbsolutePath();
2021-02-16 13:42:49 +01:00
2021-03-12 22:38:52 +01:00
File newConfigFile = new File(writeableFolderStringCache + "/" + XmlFileInterface.settingsFileName);
2021-02-16 13:42:49 +01:00
2021-03-12 22:38:52 +01:00
migration:
if (!newConfigFile.exists())
{
2021-05-16 19:51:22 +02:00
if (ActivityPermissions.havePermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Miscellaneous.getAnyContext()))
2021-02-16 13:42:49 +01:00
{
2021-03-12 22:38:52 +01:00
// We have the storage permission, probably because it's an old installation. Files should be migrated to app-specific folder.
String testPath = null;
File folder = null;
2021-03-12 22:38:52 +01:00
try
2021-02-16 13:42:49 +01:00
{
2021-03-12 22:38:52 +01:00
String[] foldersToTestArray = new String[]
{
Environment.getExternalStorageDirectory().getAbsolutePath(),
"/storage/emulated/0",
"/HWUserData",
"/mnt/sdcard"
};
for (String f : foldersToTestArray)
{
2021-03-13 00:40:59 +01:00
// if (testFolder(f))
// {
2021-03-12 22:38:52 +01:00
String pathToUse = f + "/" + Settings.folderName;
2021-02-16 13:42:49 +01:00
// Toast.makeText(getAnyContext(), "Using " + pathToUse + " to store settings and log.", Toast.LENGTH_LONG).show();
2021-03-12 22:38:52 +01:00
// Migrate existing files
File oldDirectory = new File(pathToUse);
File newDirectory = new File(writeableFolderStringCache);
File oldConfigFilePath = new File(pathToUse + "/" + XmlFileInterface.settingsFileName);
if (oldConfigFilePath.exists() && oldConfigFilePath.canWrite())
{
Miscellaneous.logEvent("i", "Path", "Found old path " + pathToUse + " for settings and logs. Migrating old files to new directory.", 2);
2021-03-13 00:40:59 +01:00
for (File fileToBeMoved : oldDirectory.listFiles())
{
File dstFile = new File(writeableFolderStringCache + "/" + fileToBeMoved.getName());
/*
For some stupid reason Android's file.moveTo can't move files between
mount points. That's why we have to copy it and delete the src if successful.
*/
if(copyFileUsingStream(fileToBeMoved, dstFile))
fileToBeMoved.delete();
}
2021-03-12 22:38:52 +01:00
String message = String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.filesHaveBeenMovedTo), newDirectory.getAbsolutePath());
2021-03-13 00:40:59 +01:00
Miscellaneous.writeStringToFile(oldDirectory.getAbsolutePath() + "/readme.txt", message);
2021-03-12 22:38:52 +01:00
break migration;
}
2021-03-13 00:40:59 +01:00
// }
}
2021-03-12 22:38:52 +01:00
} catch (Exception e)
{
Log.w("getWritableFolder", folder + " not writable.");
2021-02-16 13:42:49 +01:00
}
}
}
}
2021-03-12 22:38:52 +01:00
return writeableFolderStringCache;
2021-02-16 13:42:49 +01:00
}
protected final static String logFileName = "Automation_logfile.txt";
protected static File getLogFile()
{
File logFile = null;
logFile = new File(getWriteableFolder() + "/" + logFileName);
if(!logFile.exists())
{
Log.i("LogFile", "Creating new logfile: " + logFile.getAbsolutePath());
try
{
logFile.createNewFile();
}
catch(Exception e)
{
Log.e("LogFile", "Error writing logs to file: " + e.getMessage());
}
}
return logFile;
}
private static void writeToLogFile(String type, String header, String description)
{
try
{
FileWriter fileWriter = new FileWriter(getLogFile(), true);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
Date date = new Date();
bufferedWriter.write("\n" + date + ": " + type + " / " + header + " / " + description);
bufferedWriter.close();
// Log.i("LogFile", "Log entry written.");
}
catch(Exception e)
{
Log.e("LogFile", "Error writing logs to file: " + e.getMessage());
}
}
public static boolean isAndroidEmulator()
{
String TAG = "EmulatorTest";
String model = Build.MODEL;
// Miscellaneous.logEvent("i", TAG, "model=" + model);
String product = Build.PRODUCT;
// Miscellaneous.logEvent("i", TAG, "product=" + product);
boolean isEmulator = false;
if (product != null)
{
isEmulator = product.equals("sdk") || product.contains("_sdk") || product.contains("sdk_");
}
// Miscellaneous.logEvent("i", TAG, "isEmulator=" + isEmulator);
return isEmulator;
}
2021-03-28 20:33:44 +02:00
public static boolean compare(String direction, String needle, String haystack)
{
2021-08-31 18:53:26 +02:00
// If only one of needle or haystack is null
if(
(needle == null && haystack != null)
||
(needle != null && haystack == null)
)
return false;
2021-03-28 20:33:44 +02:00
switch(direction)
{
case Trigger.directionEquals:
2022-06-27 23:02:53 +02:00
if(Miscellaneous.isRegularExpression(needle))
return haystack.matches(needle);
else
return haystack.equalsIgnoreCase(needle);
2021-03-28 20:33:44 +02:00
case Trigger.directionNotEquals:
return !haystack.equalsIgnoreCase(needle);
case Trigger.directionContains:
return haystack.toLowerCase().contains(needle.toLowerCase());
2022-05-29 13:57:47 +02:00
case Trigger.directionNotContains:
return !haystack.toLowerCase().contains(needle.toLowerCase());
2021-03-28 20:33:44 +02:00
case Trigger.directionStartsWith:
return haystack.toLowerCase().startsWith(needle.toLowerCase());
case Trigger.directionEndsWith:
return haystack.toLowerCase().endsWith(needle.toLowerCase());
default:
return false;
}
}
2021-02-16 13:42:49 +01:00
2022-05-26 18:47:30 +02:00
public static int compareTimes(TimeObject time1, TimeObject time2)
2021-02-16 13:42:49 +01:00
{
// Miscellaneous.logEvent("i", "TimeCompare", "To compare: " + time1.toString() + " / " + time2.toString());
if(time1.getHours() == time2.getHours() && time1.getMinutes() == time2.getMinutes())
{
// Miscellaneous.logEvent("i", "TimeCompare", "Times are equal.");
return 0;
}
if(time1.getHours() > time2.getHours())
{
// Miscellaneous.logEvent("i", "TimeCompare", "Time1 is bigger/later by hours.");
return -1;
}
if(time1.getHours() < time2.getHours())
{
// Miscellaneous.logEvent("i", "TimeCompare", "Time2 is bigger/later by hours.");
return 1;
}
if(time1.getHours() == time2.getHours())
{
if(time1.getMinutes() < time2.getMinutes())
{
// Miscellaneous.logEvent("i", "TimeCompare", "Hours are equal. Time2 is bigger/later by minutes.");
return 1;
}
if(time1.getMinutes() > time2.getMinutes())
{
// Miscellaneous.logEvent("i", "TimeCompare", "Hours are equal. Time1 is bigger/later by minutes.");
return -1;
}
}
Miscellaneous.logEvent("i", "TimeCompare", "Default return code. Shouldn't be here.", 5);
return 0;
}
public static String convertStreamToString(InputStream is)
{
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
public static Context getAnyContext()
{
Context returnContext;
returnContext = AutomationService.getInstance();
if(returnContext != null)
return returnContext;
returnContext = ActivityMainScreen.getActivityMainScreenInstance();
if(returnContext != null)
return returnContext;
returnContext = ActivityPermissions.getInstance().getApplicationContext();
if(returnContext != null)
return returnContext;
2023-01-16 23:44:28 +01:00
if(startupContext != null)
return startupContext;
2021-02-16 13:42:49 +01:00
return null;
}
2021-12-12 20:03:53 +01:00
public static boolean isDarkModeEnabled(Context context)
{
2021-12-18 02:43:04 +01:00
int mode = context.getResources().getConfiguration().uiMode;
switch(mode)
2021-12-12 20:03:53 +01:00
{
2021-12-18 13:29:47 +01:00
case 33:
2021-12-12 20:03:53 +01:00
case Configuration.UI_MODE_NIGHT_YES:
return true;
2021-12-18 13:29:47 +01:00
case 17:
2021-12-12 20:03:53 +01:00
case Configuration.UI_MODE_NIGHT_NO:
case Configuration.UI_MODE_NIGHT_UNDEFINED:
default:
return false;
}
}
2021-02-16 13:42:49 +01:00
@SuppressLint("NewApi")
public static String replaceVariablesInText(String source, Context context) throws Exception
{
// Replace variable with actual content
// Miscellaneous.logEvent("i", "Raw source", source);
if(source.contains("[uniqueid]"))
source = source.replace("[uniqueid]", Secure.getString(context.getContentResolver(), Secure.ANDROID_ID));
if(source.contains("[latitude]") | source.contains("[longitude]"))
{
if(LocationProvider.getLastKnownLocation() != null)
{
source = source.replace("[latitude]", String.valueOf(LocationProvider.getLastKnownLocation().getLatitude()));
source = source.replace("[longitude]", String.valueOf(LocationProvider.getLastKnownLocation().getLongitude()));
}
else
{
Miscellaneous.logEvent("w", "TriggerURL", context.getResources().getString(R.string.triggerUrlReplacementPositionError), 3);
}
}
if(source.contains("[phonenr]"))
{
String lastPhoneNr = PhoneStatusListener.getLastPhoneNumber();
if(lastPhoneNr != null && lastPhoneNr.length() > 0)
source = source.replace("[phonenr]", PhoneStatusListener.getLastPhoneNumber());
else
Miscellaneous.logEvent("w", "TriggerURL", context.getResources().getString(R.string.triggerUrlReplacementPositionError), 3);
}
if(source.contains("[serialnr]"))
2023-03-15 23:27:27 +01:00
{
if (Build.VERSION.SDK_INT > 8)
2021-02-16 13:42:49 +01:00
source = source.replace("[serialnr]", Secure.getString(context.getContentResolver(), Build.SERIAL));
else
source = source.replace("[serialnr]", "serialUnknown");
2023-03-15 23:27:27 +01:00
}
2021-02-16 13:42:49 +01:00
if(
2023-01-05 20:39:29 +01:00
source.contains("[d]") ||
source.contains("[m]") ||
source.contains("[Y]") ||
source.contains("[h]") ||
source.contains("[H]") ||
source.contains("[i]") ||
source.contains("[s]") ||
2021-02-16 13:42:49 +01:00
source.contains("[ms]")
)
{
Calendar cal = Calendar.getInstance();
2023-01-05 20:39:29 +01:00
if(source.contains("[d]"))
{
String result = String.valueOf(cal.get(Calendar.DAY_OF_MONTH));
if(result.length() < 2)
result = "0" + result;
source = source.replace("[d]", result);
}
if(source.contains("[m]"))
{
String result = String.valueOf(cal.get(Calendar.MONTH) +1);
if(result.length() < 2)
result = "0" + result;
source = source.replace("[m]", result);
}
if(source.contains("[Y]"))
{
source = source.replace("[Y]", String.valueOf(cal.get(Calendar.YEAR)));
}
if(source.contains("[h]"))
{
String result = String.valueOf(cal.get(Calendar.HOUR));
if(result.length() < 2)
result = "0" + result;
source = source.replace("[h]", result);
}
if(source.contains("[H]"))
{
String result = String.valueOf(cal.get(Calendar.HOUR_OF_DAY));
if(result.length() < 2)
result = "0" + result;
source = source.replace("[H]", result);
}
if(source.contains("[i]"))
{
String result = String.valueOf(cal.get(Calendar.MINUTE));
if(result.length() < 2)
result = "0" + result;
source = source.replace("[i]", result);
}
if(source.contains("[s]"))
{
String result = String.valueOf(cal.get(Calendar.SECOND));
if(result.length() < 2)
result = "0" + result;
source = source.replace("[s]", result);
2023-01-05 20:39:29 +01:00
}
if(source.contains("[ms]"))
{
source = source.replace("[ms]", String.valueOf(cal.get(Calendar.MILLISECOND)));
}
2021-02-16 13:42:49 +01:00
}
2021-05-11 22:49:41 +02:00
if(source.contains("[notificationTitle]"))
{
2022-01-11 16:04:04 +01:00
if(NotificationListener.getLastNotification() != null)
{
String notificationTitle = NotificationListener.getLastNotification().getTitle();
2021-05-11 22:49:41 +02:00
2022-01-11 16:04:04 +01:00
if (notificationTitle != null && notificationTitle.length() > 0)
2023-10-19 22:30:13 +02:00
source = source.replace("[notificationTitle]", escapeStringForUrl(notificationTitle));
2022-01-11 16:04:04 +01:00
else
{
source = source.replace("[notificationTitle]", "notificationTitle unknown");
Miscellaneous.logEvent("w", "Variable replacement", "notificationTitle was empty.", 3);
}
}
2021-05-11 22:49:41 +02:00
else
2021-09-14 17:56:45 +02:00
{
2022-01-11 16:04:04 +01:00
source = source.replace("[notificationTitle]", "notificationTitle unknown");
Miscellaneous.logEvent("w", "Variable replacement", "lastNotification was empty.", 3);
2021-09-14 17:56:45 +02:00
}
2021-05-11 22:49:41 +02:00
}
if(source.contains("[notificationText]"))
{
2022-01-11 16:04:04 +01:00
if(NotificationListener.getLastNotification() != null)
{
String notificationText = NotificationListener.getLastNotification().getText();
2021-05-11 22:49:41 +02:00
2022-01-11 16:04:04 +01:00
if (notificationText != null && notificationText.length() > 0)
2023-10-19 22:30:13 +02:00
source = source.replace("[notificationText]", escapeStringForUrl(notificationText));
2022-01-11 16:04:04 +01:00
else
{
source = source.replace("[notificationText]", "notificationText unknown");
Miscellaneous.logEvent("w", "Variable replacement", "notificationText was empty.", 3);
}
}
2021-05-11 22:49:41 +02:00
else
2021-09-14 17:56:45 +02:00
{
2022-01-11 16:04:04 +01:00
source = source.replace("[notificationText]", "notificationText unknown");
Miscellaneous.logEvent("w", "Variable replacement", "lastNotification was empty.", 3);
2021-09-14 17:56:45 +02:00
}
2021-05-11 22:49:41 +02:00
}
2023-03-15 23:27:27 +01:00
2024-01-06 11:49:49 +01:00
if(source.contains("[last_trigger_url_result]"))
{
try
{
source = source.replace("[last_trigger_url_result]", AutomationService.getInstance().getVariableMap().get("last_trigger_url_result"));
}
catch (Exception e)
{
Miscellaneous.logEvent("w", "Variable replacement", "Error replacing variable last_trigger_url_result.", 3);
}
}
if(source.contains("[last_run_executable_exit_code]"))
{
try
{
source = source.replace("[last_run_executable_exit_code]", AutomationService.getInstance().getVariableMap().get("last_run_executable_exit_code"));
}
catch (Exception e)
{
Miscellaneous.logEvent("w", "Variable replacement", "Error replacing variable last_run_executable_exit_code.", 3);
}
}
if(source.contains("[last_run_executable_output]"))
{
try
{
source = source.replace("[last_run_executable_output]", AutomationService.getInstance().getVariableMap().get("last_run_executable_output"));
}
catch (Exception e)
{
Miscellaneous.logEvent("w", "Variable replacement", "Error replacing variable last_run_executable_output.", 3);
}
}
if(source.contains("[last_calendar_title]"))
{
try
{
source = source.replace("[last_calendar_title]", CalendarReceiver.getLastTriggeringEvent().title);
}
catch (Exception e)
{
Miscellaneous.logEvent("w", "Variable replacement", "Error replacing variable last_calendar_title.", 3);
}
}
if(source.contains("[last_calendar_description]"))
{
try
{
source = source.replace("[last_calendar_description]", CalendarReceiver.getLastTriggeringEvent().description);
}
catch (Exception e)
{
Miscellaneous.logEvent("w", "Variable replacement", "Error replacing variable last_calendar_description.", 3);
}
}
if(source.contains("[last_calendar_location]"))
{
try
{
source = source.replace("[last_calendar_location]", CalendarReceiver.getLastTriggeringEvent().location);
}
catch (Exception e)
{
Miscellaneous.logEvent("w", "Variable replacement", "Error replacing variable last_calendar_location.", 3);
}
}
2023-03-15 23:27:27 +01:00
while(source.contains("[variable-"))
{
int pos1 = source.indexOf("[variable-");
int pos2 = source.indexOf("]", pos1);
int posA = pos1 + "[variable-".length();
int posB = source.indexOf("]", posA);
String variableName = source.substring(posA, posB);
String replacement;
if(AutomationService.getInstance().variableMap.containsKey(variableName))
replacement = AutomationService.getInstance().variableMap.get(variableName);
else
replacement = "unknownVariable";
2023-10-19 22:30:13 +02:00
source = source.substring(0, pos1) + escapeStringForUrl(replacement) + source.substring(pos2 +1);
2023-03-15 23:27:27 +01:00
}
2021-02-16 13:42:49 +01:00
// Miscellaneous.logEvent("i", "URL after replace", source);
return source;
}
/**
* Write a log entry and exit the application, so the crash is actually visible.
* Might even cause the activity to be automatically restarted by the OS.
*/
public static UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread thread, Throwable ex)
{
Miscellaneous.logEvent("e", "UncaughtException", Log.getStackTraceString(ex), 1);
System.exit(0);
}
};
public static AlertDialog messageBox(String title, String message, Context context)
{
AlertDialog.Builder alertDialog = new AlertDialog.Builder(context);
alertDialog.setTitle(title);
alertDialog.setMessage(message);
2023-12-29 19:43:50 +01:00
alertDialog.setPositiveButton(context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener()
2021-02-16 13:42:49 +01:00
{
public void onClick(DialogInterface dialog, int whichButton)
{
dialog.dismiss();
}
});
return alertDialog.create();
}
2021-09-24 19:15:41 +02:00
private boolean haveNetworkConnection()
{
boolean haveConnectedWifi = false;
boolean haveConnectedMobile = false;
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo[] netInfo = cm.getAllNetworkInfo();
for (NetworkInfo ni : netInfo) {
if (ni.getTypeName().equalsIgnoreCase("WIFI"))
if (ni.isConnected())
haveConnectedWifi = true;
if (ni.getTypeName().equalsIgnoreCase("MOBILE"))
if (ni.isConnected())
haveConnectedMobile = true;
}
return haveConnectedWifi || haveConnectedMobile;
}
2021-02-16 13:42:49 +01:00
/**
* Checks if the device is rooted.
*
* @return <code>true</code> if the device is rooted, <code>false</code> otherwise.
*/
2022-06-02 17:41:41 +02:00
public static boolean isPhoneRooted()
{
try
{
2024-01-03 16:24:21 +01:00
return Shell.SU.available();
}
catch(Exception e)
{
// get from build info
String buildTags = Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys"))
2022-06-02 17:41:41 +02:00
{
return true;
}
2021-02-16 13:42:49 +01:00
2024-01-03 16:24:21 +01:00
// check if /system/app/Superuser.apk is present
try
{
File file = new File("/system/app/Superuser.apk");
if (file.exists())
{
return true;
}
}
catch (Exception e1)
{
// ignore
}
// try executing commands
return canExecuteCommand("/system/xbin/which su")
||
canExecuteCommand("/system/bin/which su")
||
canExecuteCommand("which su");
}
2022-06-02 17:41:41 +02:00
}
2021-02-16 13:42:49 +01:00
// executes a command on the system
private static boolean canExecuteCommand(String command)
{
boolean executedSuccesfully;
try
{
Runtime.getRuntime().exec(command);
executedSuccesfully = true;
}
catch (Exception e)
{
executedSuccesfully = false;
}
return executedSuccesfully;
}
2022-06-27 22:42:55 +02:00
public static boolean isNumericDecimal(String strNum)
{
if (strNum == null)
{
return false;
}
try
{
double d = Double.parseDouble(strNum);
}
catch (NumberFormatException nfe)
{
return false;
}
return true;
}
2021-02-16 13:42:49 +01:00
2022-06-27 22:42:55 +02:00
public static boolean isNumeric(String str)
{
return str.matches("-?\\d+(\\.\\d+)?"); //match a number with optional '-' and decimal.
}
2021-02-16 13:42:49 +01:00
/**
* Disables the SSL certificate checking for new instances of {@link HttpsURLConnection} This has been created to
* aid testing on a local box, not for use on production.
*/
2022-06-02 17:41:41 +02:00
private static void disableSSLCertificateChecking()
{
try
2024-01-06 17:25:27 +01:00
{
SSLSocketFactory ssf = null;
2021-02-16 13:42:49 +01:00
2024-01-06 17:25:27 +01:00
try
2021-02-16 13:42:49 +01:00
{
2024-01-06 17:25:27 +01:00
SSLContext ctx = SSLContext.getInstance("TLS");
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);
// return new DefaultHttpClient(ccm, client.getParams());
2021-02-16 13:42:49 +01:00
}
2024-01-06 17:25:27 +01:00
catch (Exception ex)
2021-02-16 13:42:49 +01:00
{
2024-01-06 17:25:27 +01:00
ex.printStackTrace();
// return null;
2021-02-16 13:42:49 +01:00
}
2024-01-06 17:25:27 +01:00
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, getInsecureTrustManager(), new java.security.SecureRandom());
// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// HttpsURLConnection.setDefaultSSLSocketFactory(ssf);
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(getInsecureHostnameVerifier());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
}
catch (KeyManagementException e)
{
Miscellaneous.logEvent("e", "SSL", Log.getStackTraceString(e), 4);
}
catch (NoSuchAlgorithmException e)
{
Miscellaneous.logEvent("e", "SSL", Log.getStackTraceString(e), 4);
}
2021-02-16 13:42:49 +01:00
}
public static TrustManager[] getInsecureTrustManager()
{
TrustManager[] trustAllCerts =
new TrustManager[]
{
new X509TrustManager()
{
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
{
// Not implemented
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
{
// Not implemented
}
}
};
return trustAllCerts;
}
public static HostnameVerifier getInsecureHostnameVerifier()
{
HostnameVerifier allHostsValid = new HostnameVerifier()
{
public boolean verify(String hostname, SSLSession session)
{
return true;
}
};
return allHostsValid;
}
2021-03-17 21:42:01 +01:00
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
2022-01-10 19:32:44 +01:00
public static void createDismissibleNotificationWithDelay(long delay, String title, String textToDisplay, int notificationId, String notificationChannelId, PendingIntent pendingIntent)
2021-03-17 21:42:01 +01:00
{
/*
Now what's this about?
From SDK 27 onwards you can only fire 1 notification per second:
https://developer.android.com/about/versions/oreo/android-8.1?hl=bn#notify
There are some situations where the service is just being started - resulting in a notification. But we have
additional need to inform the user about something and want to create another notification. That's why we have
to delay it for a moment, but don't want to hold off the main threat.
*/
class AsyncTaskCreateNotification extends AsyncTask<Void, Void, Void>
{
@Override
protected Void doInBackground(Void... voids)
{
setDefaultBehaviour(this);
try
{
Thread.sleep(delay);
}
catch(Exception e)
{}
2022-01-10 19:32:44 +01:00
createDismissibleNotification(title, textToDisplay, notificationId, true, notificationChannelId, pendingIntent);
2021-03-17 21:42:01 +01:00
return null;
}
}
AsyncTaskCreateNotification astCn = new AsyncTaskCreateNotification();
astCn.execute(null, null);
}
private static void setDefaultBehaviour(AsyncTask asyncTask)
{
// without this line debugger will - for some reason - skip all breakpoints in this class
if(android.os.Debug.isDebuggerConnected())
android.os.Debug.waitForDebugger();
// Thread.setDefaultUncaughtExceptionHandler(Miscellaneous.getUncaughtExceptionHandler(activityMainRef, true));
}
2021-02-16 13:42:49 +01:00
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
2022-01-10 19:32:44 +01:00
public static void createDismissibleNotification(String title, String textToDisplay, int notificationId, boolean vibrate, String notificationChannelId, PendingIntent pendingIntent)
2021-02-16 13:42:49 +01:00
{
2021-03-17 21:42:01 +01:00
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
2022-01-10 19:32:44 +01:00
createDismissibleNotificationSdk26(title, textToDisplay, notificationId, vibrate, notificationChannelId, pendingIntent);
2021-03-17 21:42:01 +01:00
return;
}
2021-02-16 13:42:49 +01:00
NotificationManager mNotificationManager = (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE);
2022-01-10 19:32:44 +01:00
NotificationCompat.Builder dismissibleNotificationBuilder = createDismissibleNotificationBuilder(vibrate, notificationChannelId, pendingIntent);
2022-01-09 22:42:47 +01:00
if(title == null)
2022-01-10 19:32:44 +01:00
dismissibleNotificationBuilder.setContentTitle(AutomationService.getInstance().getResources().getString(R.string.app_name));
2022-01-09 22:42:47 +01:00
else
2022-01-10 19:32:44 +01:00
dismissibleNotificationBuilder.setContentTitle(title);
dismissibleNotificationBuilder.setContentText(textToDisplay);
dismissibleNotificationBuilder.setContentIntent(pendingIntent);
dismissibleNotificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay));
dismissibleNotificationBuilder.setAutoCancel(true);
if(notificationChannelId.equals(AutomationService.NOTIFICATION_CHANNEL_ID_RULES))
dismissibleNotificationBuilder.setSmallIcon(R.drawable.info);
Notification dismissibleNotification = dismissibleNotificationBuilder.build();
mNotificationManager.notify(notificationId, dismissibleNotification);
2021-02-16 13:42:49 +01:00
}
2022-01-10 19:32:44 +01:00
@RequiresApi(api = Build.VERSION_CODES.O)
static NotificationChannel findExistingChannel(List<NotificationChannel> channels, String channelId)
{
for(NotificationChannel c : channels)
{
if(c.getId().equals(channelId))
return c;
}
return null;
}
@RequiresApi(api = Build.VERSION_CODES.O)
static NotificationChannel getNotificationChannel(String channelId)
{
NotificationManager nm = (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE);
List<NotificationChannel> channels = nm.getNotificationChannels();
2022-01-17 20:09:46 +01:00
if(!Settings.hasBeenDone(Settings.constNotificationChannelCleanupApk118) && BuildConfig.VERSION_CODE < 120)
2022-01-10 19:32:44 +01:00
{
2022-01-17 20:09:46 +01:00
// Perform a one-time cleanup of notification channels as they have been redesigned.
2022-01-10 19:32:44 +01:00
for(NotificationChannel c : channels)
nm.deleteNotificationChannel(c.getId());
2022-01-17 20:09:46 +01:00
Settings.considerDone(Settings.constNotificationChannelCleanupApk118);
Settings.writeSettings(Miscellaneous.getAnyContext());
2022-01-10 19:32:44 +01:00
}
NotificationChannel channel = findExistingChannel(channels, channelId);
if(channel == null)
{
switch (channelId)
{
case AutomationService.NOTIFICATION_CHANNEL_ID_SERVICE:
channel = new NotificationChannel(AutomationService.NOTIFICATION_CHANNEL_ID_SERVICE, AutomationService.NOTIFICATION_CHANNEL_NAME_SERVICE, NotificationManager.IMPORTANCE_LOW);
break;
case AutomationService.NOTIFICATION_CHANNEL_ID_FUNCTIONALITY:
channel = new NotificationChannel(AutomationService.NOTIFICATION_CHANNEL_ID_FUNCTIONALITY, AutomationService.NOTIFICATION_CHANNEL_NAME_FUNCTIONALITY, NotificationManager.IMPORTANCE_HIGH);
break;
case AutomationService.NOTIFICATION_CHANNEL_ID_RULES:
channel = new NotificationChannel(AutomationService.NOTIFICATION_CHANNEL_ID_RULES, AutomationService.NOTIFICATION_CHANNEL_NAME_RULES, NotificationManager.IMPORTANCE_HIGH);
break;
default:
break;
}
}
return channel;
}
static void createDismissibleNotificationSdk26(String title, String textToDisplay, int notificationId, boolean vibrate, String notificationChannelId, PendingIntent pendingIntent)
2021-02-16 13:42:49 +01:00
{
2021-03-17 21:42:01 +01:00
NotificationManager mNotificationManager = (NotificationManager) AutomationService.getInstance().getSystemService(Context.NOTIFICATION_SERVICE);
2021-02-16 13:42:49 +01:00
2021-03-17 21:42:01 +01:00
NotificationCompat.Builder builder;
2021-02-16 13:42:49 +01:00
2021-03-17 21:42:01 +01:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
2022-01-10 19:32:44 +01:00
NotificationChannel notificationChannel = getNotificationChannel(notificationChannelId);
// notificationChannel.setLightColor(Color.BLUE);
notificationChannel.enableVibration(vibrate);
try
{
Uri notificationSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// Uri notificationSound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE+ "://" +mContext.getPackageName()+"/"+R.raw.apple_ring));
// Ringtone r = RingtoneManager.getRingtone(Miscellaneous.getAnyContext(), notification);
AudioAttributes.Builder b = new AudioAttributes.Builder();
b.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN);
notificationChannel.setSound(notificationSound, b.build());
}
catch (Exception e)
{
Miscellaneous.logEvent("i", "Notification", Log.getStackTraceString(e), 2);
}
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
mNotificationManager.createNotificationChannel(notificationChannel);
builder = new NotificationCompat.Builder(AutomationService.getInstance(), notificationChannel.getId());
2021-03-17 21:42:01 +01:00
}
else
builder = new NotificationCompat.Builder(AutomationService.getInstance());
2021-02-16 13:42:49 +01:00
2021-03-17 21:42:01 +01:00
builder.setWhen(System.currentTimeMillis());
builder.setContentIntent(pendingIntent);
2022-01-09 22:42:47 +01:00
if(title == null)
builder.setContentTitle(AutomationService.getInstance().getResources().getString(R.string.app_name));
else
builder.setContentTitle(title);
2021-03-17 21:42:01 +01:00
builder.setOnlyAlertOnce(true);
2022-01-10 19:32:44 +01:00
if(Settings.showIconWhenServiceIsRunning && notificationChannelId.equals(AutomationService.NOTIFICATION_CHANNEL_ID_SERVICE))
2023-10-19 22:30:13 +02:00
{
if(BuildConfig.FLAVOR.equals(AutomationService.flavor_name_googleplay))
builder.setSmallIcon(R.drawable.crane);
else
builder.setSmallIcon(R.drawable.ic_launcher);
}
2022-01-10 19:32:44 +01:00
else if(!notificationChannelId.equals(AutomationService.NOTIFICATION_CHANNEL_ID_SERVICE))
builder.setSmallIcon(R.drawable.info);
2021-03-17 21:42:01 +01:00
builder.setContentText(textToDisplay);
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay));
NotificationManager notificationManager = (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, builder.build());
// Intent notifyIntent = new Intent(context, notification.class);
// notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//
// pendingIntent.getIntentSender().g
//
// PendingIntent pendingIntent = PendingIntent.getActivities(context, 0,
// new Intent[]{notifyIntent}, PendingIntent.FLAG_UPDATE_CURRENT);
//
// Notification notification = new Notification.Builder(Miscellaneous.getAnyContext())
// .setSmallIcon(android.R.drawable.ic_dialog_info)
// .setContentTitle("Automation")
// .setContentText(textToDisplay)
// .setAutoCancel(true)
// .setContentIntent(pendingIntent)
// .build();
// notification.defaults |= Notification.DEFAULT_SOUND;
// NotificationManager notificationManager =
// (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE);
// notificationManager.notify(1, notification);
}
2021-02-16 13:42:49 +01:00
2022-01-10 19:32:44 +01:00
protected static NotificationCompat.Builder createDismissibleNotificationBuilder(boolean vibrate, String notificationChannelId, PendingIntent myPendingIntent)
2021-02-16 13:42:49 +01:00
{
NotificationManager mNotificationManager = (NotificationManager) AutomationService.getInstance().getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
2022-01-10 19:32:44 +01:00
NotificationChannel notificationChannel = getNotificationChannel(notificationChannelId);
// notificationChannel.setLightColor(Color.BLUE);
notificationChannel.enableVibration(vibrate);
// notificationChannel.setSound(null, null);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
mNotificationManager.createNotificationChannel(notificationChannel);
builder = new NotificationCompat.Builder(AutomationService.getInstance(), notificationChannelId);
2021-02-16 13:42:49 +01:00
}
else
builder = new NotificationCompat.Builder(AutomationService.getInstance());
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
builder.setCategory(Notification.CATEGORY_SERVICE);
2021-11-03 15:15:06 +01:00
builder.setAutoCancel(true);
2021-02-16 13:42:49 +01:00
builder.setWhen(System.currentTimeMillis());
builder.setContentIntent(myPendingIntent);
builder.setContentTitle(AutomationService.getInstance().getResources().getString(R.string.app_name));
// builder.setOnlyAlertOnce(true);
builder.setSmallIcon(R.drawable.priority);
// builder.setContentText(textToDisplay);
// builder.setSmallIcon(icon);
// builder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay));
return builder;
}
2021-03-15 23:06:37 +01:00
public static String explode(String glue, ArrayList<String> arrayList)
2021-02-16 13:42:49 +01:00
{
2021-03-15 23:06:37 +01:00
if(arrayList != null)
{
StringBuilder builder = new StringBuilder();
for (String s : arrayList)
builder.append(s + glue);
if (builder.length() > 0)
builder.delete(builder.length() - glue.length(), builder.length());
2021-02-16 13:42:49 +01:00
2021-03-15 23:06:37 +01:00
return builder.toString();
}
else
return "";
2021-02-16 13:42:49 +01:00
}
2022-01-04 17:51:27 +01:00
public static String explode(String glue, String[] inputArray)
{
if(inputArray != null)
{
StringBuilder builder = new StringBuilder();
for (String s : inputArray)
builder.append(s + glue);
if (builder.length() > 0)
builder.delete(builder.length() - glue.length(), builder.length());
return builder.toString();
}
else
return "";
}
2021-02-16 13:42:49 +01:00
public static boolean isGooglePlayInstalled(Context context)
{
// return false;
PackageManager pm = context.getPackageManager();
boolean app_installed = false;
try
{
PackageInfo info = pm.getPackageInfo("com.android.vending", PackageManager.GET_ACTIVITIES);
String label = (String) info.applicationInfo.loadLabel(pm);
app_installed = (label != null && !label.equals("Market"));
}
catch (PackageManager.NameNotFoundException e)
{
app_installed = false;
}
return app_installed;
}
public static double round(double value, int places)
{
2022-02-02 18:06:37 +01:00
if (places < 0)
throw new IllegalArgumentException();
2021-02-16 13:42:49 +01:00
BigDecimal bd = new BigDecimal(Double.toString(value));
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}
public static String getRealPathFromURI(Context context, Uri contentUri)
{
Cursor cursor = null;
try
{
2022-02-02 18:06:37 +01:00
String[] proj = { MediaStore.Images.Media.DATA, MediaStore.Audio.Media.DATA };
2021-02-16 13:42:49 +01:00
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
catch (Exception e)
{
Miscellaneous.logEvent("e", "Uri", "getRealPathFromURI Exception : " + Log.getStackTraceString(e), 1);
return null;
}
finally
{
if (cursor != null)
{
cursor.close();
}
}
}
2021-02-16 20:24:12 +01:00
2022-02-02 18:06:37 +01:00
public static String getRealPathFromURI2(final Context context, final Uri uri)
{
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri))
{
// ExternalStorageProvider
if (isExternalStorageDocument(uri))
{
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type))
{
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri))
{
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri))
{
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type))
{
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
else if ("video".equals(type))
{
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}
else if ("audio".equals(type))
{
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme()))
{
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme()))
{
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs)
{
Cursor cursor = null;
final String column = "_data";
final String[] projection = { column };
try
{
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst())
{
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
}
finally
{
if (cursor != null)
cursor.close();
}
return null;
}
public static boolean isExternalStorageDocument(Uri uri)
{
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
public static boolean isDownloadsDocument(Uri uri)
{
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri)
{
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
2021-02-16 20:24:12 +01:00
public static Method getClassMethodReflective(String className, String methodName)
{
2021-02-16 23:38:38 +01:00
Class foundClass = null;
2021-02-16 20:24:12 +01:00
try
{
2021-02-16 23:38:38 +01:00
foundClass = Class.forName(className);
for(Method m : foundClass.getDeclaredMethods())
2021-02-16 20:24:12 +01:00
{
2021-02-16 23:38:38 +01:00
if(m.getName().equalsIgnoreCase(methodName))
2021-02-16 20:24:12 +01:00
{
return m;
}
}
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
return null;
}
2021-02-16 23:38:38 +01:00
public static Object runMethodReflective(String className, String methodName, Object[] params)
{
Method m = getClassMethodReflective(className, methodName);
Object result = null;
try
{
if(params == null)
result = m.invoke((Object[]) null);
else
result = m.invoke(null, params);
}
catch (IllegalAccessException e)
{
2021-12-12 20:03:53 +01:00
Miscellaneous.logEvent("w", "runMethodReflective", Log.getStackTraceString(e),5 );
2021-02-16 23:38:38 +01:00
}
catch (InvocationTargetException e)
{
2021-12-12 20:03:53 +01:00
Miscellaneous.logEvent("w", "runMethodReflective", Log.getStackTraceString(e),5 );
2021-02-16 23:38:38 +01:00
}
return result;
}
2021-02-20 02:04:53 +01:00
2022-10-03 20:16:56 +02:00
public static boolean restrictedFeaturesConfiguredFdroid()
2021-02-20 02:30:06 +01:00
{
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection))
{
try
{
Class testClass = Class.forName(ActivityManageRule.activityDetectionClassPath);
}
catch (ClassNotFoundException e)
{
return true;
}
}
return false;
}
2022-10-03 20:16:56 +02:00
public static boolean restrictedFeaturesConfiguredGoogle()
{
if(Rule.isAnyRuleUsing(Action.Action_Enum.startPhoneCall) || Rule.isAnyRuleUsing(Action.Action_Enum.stopPhoneCall))
{
if(BuildConfig.FLAVOR.equals(AutomationService.flavor_name_googleplay))
return true;
}
return false;
}
2021-02-20 02:04:53 +01:00
public static Element getXmlTree(String inputString) throws SAXException, IOException, ParserConfigurationException
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// Create a Document from a file or stream
/*
StringBuilder xmlStringBuilder = new StringBuilder();
xmlStringBuilder.append("<?xml version="1.0"?> <class> </class>");
ByteArrayInputStream input = new ByteArrayInputStream(xmlStringBuilder.toString().getBytes("UTF-8"));
*/
// Document doc = builder.parse(input);
Document doc = builder.parse(new InputSource(new StringReader(inputString)));
Element rootElement = doc.getDocumentElement();
return rootElement;
/*
// Examine attributes
//returns specific attribute
root.getAttribute("attributeName");
//returns a Map (table) of names/values
root.getAttributes();
// Examine sub-elements
//returns a list of subelements of specified name
root.getElementsByTagName("subelementName");
//returns a list of all child nodes
root.getChildNodes();
*/
}
public static Calendar calendarFromLong(long input)
{
Calendar returnValue = Calendar.getInstance();
returnValue.setTimeInMillis(input);
return returnValue;
}
public static boolean writeStringToFile(String filename, String input)
{
try
{
FileWriter myWriter = new FileWriter(filename);
myWriter.write(input);
myWriter.close();
return true;
}
catch (IOException e)
{
Miscellaneous.logEvent("e", "Error writing to file", Log.getStackTraceString(e), 3);
return false;
}
}
public static String readFileToString(String fileName)
{
try
{
StringBuilder result = new StringBuilder();
File myObj = new File(fileName);
Scanner myReader = new Scanner(myObj);
while (myReader.hasNextLine())
{
String data = myReader.nextLine();
result.append(data);
}
myReader.close();
return result.toString();
}
catch (FileNotFoundException e)
{
Miscellaneous.logEvent("e", "Error reading file " + fileName, Log.getStackTraceString(e), 3);
return null;
}
}
2021-03-13 00:40:59 +01:00
public static boolean copyFileUsingStream(File source, File dest) throws IOException
{
boolean returnValue = false;
InputStream is = null;
OutputStream os = null;
try
{
is = new FileInputStream(source);
os = new FileOutputStream(dest);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0)
{
os.write(buffer, 0, length);
}
returnValue = true;
}
finally
{
is.close();
os.close();
}
return returnValue;
}
2021-03-18 20:00:19 +01:00
public static boolean copyDocumentFileToFile(DocumentFile src, File dst)
{
InputStream in = null;
OutputStream out = null;
String error = null;
try
{
in = Miscellaneous.getAnyContext().getContentResolver().openInputStream(src.getUri());
out = new FileOutputStream(dst);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
// write the output file (You have now copied the file)
out.flush();
out.close();
return true;
}
catch (FileNotFoundException fnfe1)
{
error = fnfe1.getMessage();
}
catch (Exception e)
{
error = e.getMessage();
}
return false;
// return error;
}
public static boolean copyFileToDocumentFile(File src, DocumentFile dst)
{
InputStream in = null;
OutputStream out = null;
String error = null;
try
{
in = new FileInputStream(src);
out = Miscellaneous.getAnyContext().getContentResolver().openOutputStream(dst.getUri());
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
// write the output file (You have now copied the file)
out.flush();
out.close();
return true;
}
catch (FileNotFoundException fnfe1)
{
error = fnfe1.getMessage();
}
catch (Exception e)
{
error = e.getMessage();
}
return false;
// return error;
}
/*public static String copyDocumentFile(String inputPath, String inputFile, Uri treeUri)
2021-05-10 19:56:54 +02:00
{
InputStream in = null;
OutputStream out = null;
String error = null;
DocumentFile pickedDir = DocumentFile.fromTreeUri(getActivity(), treeUri);
String extension = inputFile.substring(inputFile.lastIndexOf(".")+1,inputFile.length());
try
{
2021-05-10 19:56:54 +02:00
DocumentFile newFile = pickedDir.createFile("audio/"+extension, inputFile);
out = getActivity().getContentResolver().openOutputStream(newFile.getUri());
in = new FileInputStream(inputPath + inputFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
{
2021-05-10 19:56:54 +02:00
out.write(buffer, 0, read);
}
in.close();
// write the output file (You have now copied the file)
out.flush();
out.close();
}
catch (FileNotFoundException fnfe1)
{
2021-05-10 19:56:54 +02:00
error = fnfe1.getMessage();
}
catch (Exception e)
{
2021-05-10 19:56:54 +02:00
error = e.getMessage();
}
2021-05-10 19:56:54 +02:00
return error;
}*/
2021-05-10 19:56:54 +02:00
2021-03-20 02:44:27 +01:00
public static boolean googleToBlameForLocation(boolean checkExistingRules)
2021-03-18 20:00:19 +01:00
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
2022-06-01 22:36:30 +02:00
if (BuildConfig.FLAVOR.equalsIgnoreCase(AutomationService.flavor_name_googleplay))
2021-03-18 20:00:19 +01:00
{
2021-03-20 02:44:27 +01:00
if(checkExistingRules)
2021-03-18 20:00:19 +01:00
{
2021-03-20 02:44:27 +01:00
if (Rule.isAnyRuleUsing(Trigger.Trigger_Enum.pointOfInterest))
{
return true;
}
2021-03-18 20:00:19 +01:00
}
2021-03-20 02:44:27 +01:00
else
return true;
2021-03-18 20:00:19 +01:00
}
}
return false;
}
2021-03-29 16:36:21 +02:00
public static void zip(String[] _files, String zipFileName)
{
int BUFFER = 2048;
try
{
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream(zipFileName);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
dest));
byte data[] = new byte[BUFFER];
for (int i = 0; i < _files.length; i++)
{
Log.v("Compress", "Adding: " + _files[i]);
FileInputStream fi = new FileInputStream(_files[i]);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(_files[i].substring(_files[i].lastIndexOf("/") + 1));
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1)
{
out.write(data, 0, count);
}
origin.close();
}
out.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void unzip(String _zipFile, String _targetLocation)
{
int BUFFER = 2048;
try
{
FileInputStream fin = new FileInputStream(_zipFile);
ZipInputStream zin = new ZipInputStream(fin);
ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null)
{
//create dir if required while unzipping
if (ze.isDirectory())
{
// dirChecker(ze.getName());
}
else
{
FileOutputStream fout = new FileOutputStream(_targetLocation + ze.getName());
for (int c = zin.read(); c != -1; c = zin.read())
{
fout.write(c);
}
zin.closeEntry();
fout.close();
}
}
zin.close();
}
catch (Exception e)
{
System.out.println(e);
}
}
public static void sendEmail(Context context, String targetAddress, String subject, String message, Uri fileAttachment)
{
try
{
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("plain/text");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{targetAddress});
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
if (fileAttachment != null)
{
emailIntent.putExtra(Intent.EXTRA_STREAM, fileAttachment);
}
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
context.startActivity(Intent.createChooser(emailIntent, "Sending email..."));
}
catch (Throwable t)
{
Toast.makeText(context, "Request failed try again: "+ t.toString(), Toast.LENGTH_LONG).show();
}
}
2021-04-13 20:00:36 +02:00
public static boolean doesActivityExist(Intent intent, Context context)
{
return intent.resolveActivityInfo(context.getPackageManager(), 0) != null;
}
2021-05-15 19:55:48 +02:00
public static boolean isRegularExpression(String regex)
{
try
{
"compareString".matches(regex); //will cause expection if no valid regex
return true;
}
catch(java.util.regex.PatternSyntaxException e)
{
}
return false;
}
public static boolean comparePhoneNumbers(String number1, String number2)
{
2022-06-27 23:02:53 +02:00
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.S)
2021-05-15 19:55:48 +02:00
{
TelephonyManager tm = (TelephonyManager)Miscellaneous.getAnyContext().getSystemService(Context.TELEPHONY_SERVICE);
return PhoneNumberUtils.areSamePhoneNumber(number1, number2, tm.getNetworkCountryIso());
}
2022-06-27 23:02:53 +02:00
else
2021-05-15 19:55:48 +02:00
return PhoneNumberUtils.compare(number1, number2);
}
2021-11-08 20:13:11 +01:00
public static String formatDate(Date input)
{
DateFormat sdf = null;
SimpleDateFormat fallBackFormatter = new SimpleDateFormat(Settings.dateFormat);
if(sdf == null && Settings.dateFormat != null)
sdf = new SimpleDateFormat(Settings.dateFormat);
String formattedDate;
if(sdf != null)
formattedDate = sdf.format(input);
else
formattedDate = fallBackFormatter.format(input);
return formattedDate;
}
2022-01-10 19:57:55 +01:00
public static boolean arraySearch(String[] haystack, String needle, boolean caseSensitive, boolean matchFullLine)
{
if(matchFullLine)
{
if(caseSensitive)
{
for (String s : haystack)
{
if (s.equals(needle))
return true;
}
}
else
{
for (String s : haystack)
{
if (s.toLowerCase().equals(needle.toLowerCase()))
return true;
}
}
}
else
{
if(caseSensitive)
{
for (String s : haystack)
{
if (s.contains(needle))
return true;
}
}
else
{
for (String s : haystack)
{
if (s.toLowerCase().contains(needle.toLowerCase()))
return true;
}
}
}
return false;
}
public static boolean arraySearch(ArrayList<String> requestList, String needle, boolean caseSensitive, boolean matchFullLine)
{
return arraySearch(requestList.toArray(new String[requestList.size()]), needle, caseSensitive, matchFullLine);
}
2022-01-27 11:34:04 +01:00
/**
* Get ISO 3166-1 alpha-2 country code for this device (or null if not available)
* @param context Context reference to get the TelephonyManager instance from
* @return country code or null
*/
2022-05-10 12:42:14 +02:00
public static String getUserCountry(Context context)
{
2022-01-27 11:34:04 +01:00
try
{
final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final String simCountry = tm.getSimCountryIso();
if (simCountry != null && simCountry.length() == 2)
{ // SIM country code is available
return simCountry.toLowerCase(Locale.US);
}
else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA)
{ // device is not 3G (would be unreliable)
String networkCountry = tm.getNetworkCountryIso();
if (networkCountry != null && networkCountry.length() == 2)
{ // network country code is available
return networkCountry.toLowerCase(Locale.US);
}
}
}
catch (SecurityException se)
{
return "unknown";
}
catch (Exception e)
{ }
return null;
}
2022-02-02 18:06:37 +01:00
public static String checksumSha(String filepath) throws IOException
{
try
{
MessageDigest md = null;
md = MessageDigest.getInstance("SHA-256");
// file hashing with DigestInputStream
try (DigestInputStream dis = new DigestInputStream(new FileInputStream(filepath), md))
{
while (dis.read() != -1)
; //empty loop to clear the data
md = dis.getMessageDigest();
}
// bytes to hex
StringBuilder result = new StringBuilder();
for (byte b : md.digest())
{
result.append(String.format("%02x", b));
}
return result.toString();
}
catch (NoSuchAlgorithmException e)
{
Miscellaneous.logEvent("e", "shaChecksum", Log.getStackTraceString(e), 2);
}
return null;
}
2022-07-17 23:40:17 +02:00
public static int getTargetSDK(Context context)
{
return context.getApplicationContext().getApplicationInfo().targetSdkVersion;
}
2023-02-26 18:12:47 +01:00
public static void setDisplayLanguage(Context context)
{
if(!Settings.displayLanguage.equals(Settings.default_displayLanguage))
{
2023-06-08 19:43:41 +02:00
Locale myLocale;
if(Settings.displayLanguage.contains("_"))
{
String[] parts = Settings.displayLanguage.split("_");
myLocale = new Locale(parts[0], parts[1]);
}
else
myLocale = new Locale(Settings.displayLanguage);
2023-02-26 18:12:47 +01:00
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = myLocale;
res.updateConfiguration(conf, dm);
//Intent refresh = new Intent(this, AndroidLocalize.class);
//finish();
//startActivity(refresh);
}
}
2023-10-19 22:30:13 +02:00
public static String escapeStringForUrl(String input)
{
String output;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
try
{
output = URLEncoder.encode(input, String.valueOf(StandardCharsets.UTF_8));
}
catch (UnsupportedEncodingException e)
{
Miscellaneous.logEvent("e", "URLEncoder", "Error encoding string for URL. Leaving as it is. Error details: " + Log.getStackTraceString(e), 3);
output = input;
}
}
else
{
output = Uri.encode(input);
}
return output;
}
2024-01-03 16:24:21 +01:00
public static String getCallingMethodName()
{
StackTraceElement callingFrame = Thread.currentThread().getStackTrace()[4];
return callingFrame.getMethodName();
}
2021-02-16 13:42:49 +01:00
}