1691 lines
66 KiB
Java
1691 lines
66 KiB
Java
package com.eveningoutpost.dexdrip.Models;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.annotation.TargetApi;
|
|
import android.app.Activity;
|
|
import android.app.ActivityManager;
|
|
import android.app.AlarmManager;
|
|
import android.app.NotificationManager;
|
|
import android.app.PendingIntent;
|
|
import android.bluetooth.BluetoothAdapter;
|
|
import android.bluetooth.BluetoothDevice;
|
|
import android.bluetooth.BluetoothGatt;
|
|
import android.bluetooth.BluetoothManager;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.Signature;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Point;
|
|
import android.media.AudioManager;
|
|
import android.media.MediaPlayer;
|
|
import android.media.RingtoneManager;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.NetworkInfo;
|
|
import android.net.Uri;
|
|
import android.net.wifi.WifiInfo;
|
|
import android.net.wifi.WifiManager;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.PowerManager;
|
|
import android.os.SystemClock;
|
|
import android.provider.Settings;
|
|
import android.support.v4.app.NotificationCompat;
|
|
import android.support.v7.app.AlertDialog;
|
|
import android.support.v7.app.AppCompatActivity;
|
|
import android.support.v7.view.ContextThemeWrapper;
|
|
import android.text.InputType;
|
|
import android.text.method.DigitsKeyListener;
|
|
import android.util.Base64;
|
|
import android.util.Log;
|
|
import android.view.Display;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.widget.Toast;
|
|
|
|
import com.activeandroid.ActiveAndroid;
|
|
import com.eveningoutpost.dexdrip.Home;
|
|
import com.eveningoutpost.dexdrip.R;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
|
import com.eveningoutpost.dexdrip.UtilityModels.XdripNotificationCompat;
|
|
import com.eveningoutpost.dexdrip.utils.BestGZIPOutputStream;
|
|
import com.eveningoutpost.dexdrip.utils.CipherUtils;
|
|
import com.eveningoutpost.dexdrip.xdrip;
|
|
import com.google.common.primitives.Bytes;
|
|
import com.google.common.primitives.UnsignedInts;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.net.URLEncoder;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.Charset;
|
|
import java.text.DecimalFormat;
|
|
import java.text.DecimalFormatSymbols;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.GregorianCalendar;
|
|
import java.util.HashMap;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.SortedSet;
|
|
import java.util.TreeSet;
|
|
import java.util.zip.CRC32;
|
|
import java.util.zip.Checksum;
|
|
import java.util.zip.Deflater;
|
|
import java.util.zip.GZIPInputStream;
|
|
import java.util.zip.Inflater;
|
|
|
|
import static android.bluetooth.BluetoothDevice.PAIRING_VARIANT_PIN;
|
|
import static android.content.Context.ALARM_SERVICE;
|
|
import static com.eveningoutpost.dexdrip.stats.StatsActivity.SHOW_STATISTICS_PRINT_COLOR;
|
|
|
|
/**
|
|
* Created by jamorham on 06/01/16.
|
|
* <p>
|
|
* lazy helper class for utilities
|
|
*/
|
|
public class JoH {
|
|
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
|
private final static String TAG = "jamorham JoH";
|
|
private final static int PAIRING_VARIANT_PASSKEY = 1; // hidden in api
|
|
private final static boolean debug_wakelocks = false;
|
|
|
|
private static double benchmark_time = 0;
|
|
private static Map<String, Double> benchmarks = new HashMap<String, Double>();
|
|
private static final Map<String, Long> rateLimits = new HashMap<>();
|
|
|
|
public static boolean buggy_samsung = false; // flag set when we detect samsung devices which do not perform to android specifications
|
|
|
|
// quick string conversion with leading zero
|
|
public static String qs0(double x, int digits) {
|
|
final String qs = qs(x, digits);
|
|
return qs.startsWith(".") ? "0" + qs : qs;
|
|
}
|
|
|
|
// qs = quick string conversion of double for printing
|
|
public static String qs(double x) {
|
|
return qs(x, 2);
|
|
}
|
|
|
|
// singletons to avoid repeated allocation
|
|
private static DecimalFormatSymbols dfs;
|
|
private static DecimalFormat df;
|
|
public static String qs(double x, int digits) {
|
|
|
|
if (digits == -1) {
|
|
digits = 0;
|
|
if (((int) x != x)) {
|
|
digits++;
|
|
if ((((int) x * 10) / 10 != x)) {
|
|
digits++;
|
|
if ((((int) x * 100) / 100 != x)) digits++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dfs == null) {
|
|
final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols();
|
|
local_dfs.setDecimalSeparator('.');
|
|
dfs = local_dfs; // avoid race condition
|
|
}
|
|
|
|
final DecimalFormat this_df;
|
|
// use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe
|
|
if (Thread.currentThread().getId() == 1) {
|
|
if (df == null) {
|
|
final DecimalFormat local_df = new DecimalFormat("#", dfs);
|
|
local_df.setMinimumIntegerDigits(1);
|
|
df = local_df; // avoid race condition
|
|
}
|
|
this_df = df;
|
|
} else {
|
|
this_df = new DecimalFormat("#", dfs);
|
|
}
|
|
|
|
this_df.setMaximumFractionDigits(digits);
|
|
return this_df.format(x);
|
|
}
|
|
|
|
public static double ts() {
|
|
return new Date().getTime();
|
|
}
|
|
|
|
public static long tsl() {
|
|
return System.currentTimeMillis();
|
|
}
|
|
|
|
public static long uptime() {
|
|
return SystemClock.uptimeMillis();
|
|
}
|
|
|
|
public static boolean upForAtLeastMins(int mins) {
|
|
return uptime() > Constants.MINUTE_IN_MS * mins;
|
|
}
|
|
|
|
public static long msSince(long when) {
|
|
return (tsl() - when);
|
|
}
|
|
|
|
public static long msTill(long when) {
|
|
return (when - tsl());
|
|
}
|
|
|
|
public static long absMsSince(long when) {
|
|
return Math.abs(tsl() - when);
|
|
}
|
|
|
|
public static String bytesToHex(byte[] bytes) {
|
|
if (bytes == null) return "<empty>";
|
|
final char[] hexChars = new char[bytes.length * 2];
|
|
for (int j = 0; j < bytes.length; j++) {
|
|
final int v = bytes[j] & 0xFF;
|
|
hexChars[j * 2] = hexArray[v >>> 4];
|
|
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
|
}
|
|
return new String(hexChars);
|
|
}
|
|
|
|
public static byte[] tolerantHexStringToByteArray(String str) {
|
|
return hexStringToByteArray(str.toUpperCase().replaceAll("[^A-F0-9]",""));
|
|
}
|
|
|
|
public static byte[] hexStringToByteArray(String str) {
|
|
try {
|
|
str = str.toUpperCase().trim();
|
|
if (str.length() == 0) return null;
|
|
final int len = str.length();
|
|
byte[] data = new byte[len / 2];
|
|
for (int i = 0; i < len; i += 2) {
|
|
data[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
|
|
}
|
|
return data;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception processing hexString: " + e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static String macFormat(final String unformatted) {
|
|
if (unformatted == null) return null;
|
|
return unformatted.replaceAll("[^a-fA-F0-9]","").replaceAll("(.{2})", "$1:").substring(0,17);
|
|
}
|
|
|
|
public static <K, V extends Comparable<? super V>> SortedSet<Map.Entry<K, V>> mapSortedByValue(Map<K, V> map, boolean descending) {
|
|
final SortedSet<Map.Entry<K, V>> sortedSet = new TreeSet<>((value1, value2) -> {
|
|
int result = descending ? value2.getValue().compareTo(value1.getValue())
|
|
: value1.getValue().compareTo(value2.getValue());
|
|
return result != 0 ? result : 1;
|
|
});
|
|
sortedSet.addAll(map.entrySet());
|
|
return sortedSet;
|
|
}
|
|
|
|
|
|
public static String compressString(String source) {
|
|
try {
|
|
|
|
Deflater deflater = new Deflater();
|
|
deflater.setInput(source.getBytes(Charset.forName("UTF-8")));
|
|
deflater.finish();
|
|
|
|
byte[] buf = new byte[source.length() + 256];
|
|
int count = deflater.deflate(buf);
|
|
// check count
|
|
deflater.end();
|
|
return Base64.encodeToString(buf, 0, count, Base64.NO_WRAP);
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static byte[] compressStringToBytes(String string) {
|
|
try {
|
|
ByteArrayOutputStream output = new ByteArrayOutputStream(string.length());
|
|
BestGZIPOutputStream gzipped_data = new BestGZIPOutputStream(output);
|
|
gzipped_data.write(string.getBytes(Charset.forName("UTF-8")));
|
|
gzipped_data.close();
|
|
byte[] compressed = output.toByteArray();
|
|
output.close();
|
|
return compressed;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception in compress: " + e.toString());
|
|
return new byte[0];
|
|
}
|
|
}
|
|
|
|
public static byte[] compressBytesforPayload(byte[] bytes) {
|
|
return compressBytesToBytes(Bytes.concat(bytes, bchecksum(bytes)));
|
|
}
|
|
|
|
public static byte[] compressBytesToBytes(byte[] bytes) {
|
|
try {
|
|
ByteArrayOutputStream output = new ByteArrayOutputStream(bytes.length);
|
|
BestGZIPOutputStream gzipped_data = new BestGZIPOutputStream(output);
|
|
gzipped_data.write(bytes);
|
|
gzipped_data.close();
|
|
byte[] compressed = output.toByteArray();
|
|
output.close();
|
|
return compressed;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception in compress: " + e.toString());
|
|
return new byte[0];
|
|
}
|
|
}
|
|
|
|
public static byte[] decompressBytesToBytes(byte[] bytes) {
|
|
try {
|
|
Log.d(TAG, "Decompressing bytes size: " + bytes.length);
|
|
byte[] buffer = new byte[8192];
|
|
int bytes_read;
|
|
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
|
|
ByteArrayOutputStream output = new ByteArrayOutputStream(bytes.length);
|
|
GZIPInputStream gzipped_data = new GZIPInputStream(input, buffer.length);
|
|
while ((bytes_read = gzipped_data.read(buffer)) != -1) {
|
|
output.write(buffer, 0, bytes_read);
|
|
}
|
|
gzipped_data.close();
|
|
input.close();
|
|
// output.close();
|
|
return output.toByteArray();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception in decompress: " + e.toString());
|
|
return new byte[0];
|
|
}
|
|
}
|
|
|
|
|
|
public static String uncompressString(String input) {
|
|
try {
|
|
byte[] bytes = Base64.decode(input, Base64.NO_WRAP);
|
|
Inflater inflater = new Inflater();
|
|
inflater.setInput(bytes);
|
|
inflater.finished();
|
|
|
|
byte[] buf = new byte[10000]; // max packet size because not using stream
|
|
int count = inflater.inflate(buf);
|
|
inflater.end();
|
|
Log.d(TAG, "Inflated bytes: " + count);
|
|
return new String(buf, 0, count, "UTF-8");
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Got exception uncompressing string");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static String base64encode(String input) {
|
|
try {
|
|
return new String(Base64.encode(input.getBytes("UTF-8"), Base64.NO_WRAP), "UTF-8");
|
|
} catch (UnsupportedEncodingException e) {
|
|
Log.e(TAG, "Got unsupported encoding: " + e);
|
|
return "encode-error";
|
|
}
|
|
}
|
|
|
|
public static String base64decode(String input) {
|
|
try {
|
|
return new String(Base64.decode(input.getBytes("UTF-8"), Base64.NO_WRAP), "UTF-8");
|
|
} catch (UnsupportedEncodingException | IllegalArgumentException e) {
|
|
Log.e(TAG, "Got unsupported encoding: " + e);
|
|
return "decode-error";
|
|
}
|
|
}
|
|
|
|
|
|
public static String base64encodeBytes(byte[] input) {
|
|
try {
|
|
return new String(Base64.encode(input, Base64.NO_WRAP), "UTF-8");
|
|
} catch (UnsupportedEncodingException e) {
|
|
Log.e(TAG, "Got unsupported encoding: " + e);
|
|
return "encode-error";
|
|
}
|
|
}
|
|
|
|
public static byte[] base64decodeBytes(String input) {
|
|
try {
|
|
return Base64.decode(input.getBytes("UTF-8"), Base64.NO_WRAP);
|
|
} catch (UnsupportedEncodingException | IllegalArgumentException e) {
|
|
Log.e(TAG, "Got unsupported encoding: " + e);
|
|
return new byte[0];
|
|
}
|
|
}
|
|
|
|
|
|
public static String ucFirst(String input) {
|
|
return input.substring(0, 1).toUpperCase() + input.substring(1).toLowerCase();
|
|
}
|
|
|
|
public static boolean isSamsung() {
|
|
return Build.MANUFACTURER.toLowerCase().contains("samsung");
|
|
}
|
|
|
|
private static final String BUGGY_SAMSUNG_ENABLED = "buggy-samsung-enabled";
|
|
public static void persistentBuggySamsungCheck() {
|
|
if (!buggy_samsung) {
|
|
if (JoH.isSamsung() && PersistentStore.getLong(BUGGY_SAMSUNG_ENABLED) > 4) {
|
|
buggy_samsung = true;
|
|
UserError.Log.d(TAG,"Enabling buggy samsung mode due to historical pattern");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void setBuggySamsungEnabled() {
|
|
if (!buggy_samsung) {
|
|
JoH.buggy_samsung = true;
|
|
PersistentStore.incrementLong(BUGGY_SAMSUNG_ENABLED);
|
|
}
|
|
}
|
|
|
|
|
|
public static class DecimalKeyListener extends DigitsKeyListener {
|
|
private final char[] acceptedCharacters =
|
|
new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
new DecimalFormat().getDecimalFormatSymbols().getDecimalSeparator()};
|
|
|
|
@Override
|
|
protected char[] getAcceptedChars() {
|
|
return acceptedCharacters;
|
|
}
|
|
|
|
public int getInputType() {
|
|
return InputType.TYPE_CLASS_NUMBER;
|
|
}
|
|
|
|
}
|
|
|
|
public static String backTrace() {
|
|
return backTrace(1);
|
|
}
|
|
|
|
public static String backTrace(int depth) {
|
|
try {
|
|
StackTraceElement stack = new Exception().getStackTrace()[2 + depth];
|
|
StackTraceElement stackb = new Exception().getStackTrace()[3 + depth];
|
|
String[] stackclassa = stack.getClassName().split("\\.");
|
|
String[] stackbclassa = stackb.getClassName().split("\\.");
|
|
|
|
return stackbclassa[stackbclassa.length - 1] + "::" + stackb.getMethodName()
|
|
+ " -> " + stackclassa[stackclassa.length - 1] + "::" + stack.getMethodName();
|
|
} catch (Exception e) {
|
|
return "unknown backtrace: " + e.toString();
|
|
}
|
|
}
|
|
|
|
public static String backTraceShort(int depth) {
|
|
try {
|
|
final StackTraceElement stackb = new Exception().getStackTrace()[3 + depth];
|
|
return stackb.getMethodName();
|
|
} catch (Exception e) {
|
|
return "unknown backtrace: " + e.toString();
|
|
}
|
|
}
|
|
|
|
public static void benchmark(String name) {
|
|
if (name == null) {
|
|
if (benchmark_time == 0) {
|
|
benchmark_time = ts();
|
|
} else {
|
|
Log.e(TAG, "Cannot start a benchmark as one is already running - cancelling");
|
|
benchmark_time = 0;
|
|
}
|
|
} else {
|
|
if (benchmark_time == 0) {
|
|
Log.e(TAG, "Benchmark: " + name + " no benchmark set!");
|
|
} else {
|
|
Log.i(TAG, "Benchmark: " + name + " " + (ts() - benchmark_time) + " ms");
|
|
benchmark_time = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void dumpBundle(Bundle bundle, String tag) {
|
|
if (bundle != null) {
|
|
for (String key : bundle.keySet()) {
|
|
Object value = bundle.get(key);
|
|
if (value != null) {
|
|
UserError.Log.d(tag, String.format("%s %s (%s)", key,
|
|
value.toString(), value.getClass().getName()));
|
|
}
|
|
}
|
|
} else {
|
|
UserError.Log.d(tag, "Bundle is empty");
|
|
}
|
|
}
|
|
|
|
|
|
// compare stored byte array hashes
|
|
public static synchronized boolean differentBytes(String name, byte[] bytes) {
|
|
final String id = "differentBytes-" + name;
|
|
final String last_hash = PersistentStore.getString(id);
|
|
final String this_hash = CipherUtils.getSHA256(bytes);
|
|
if (this_hash.equals(last_hash)) return false;
|
|
PersistentStore.setString(id, this_hash);
|
|
return true;
|
|
}
|
|
|
|
public static synchronized void clearRatelimit(final String name) {
|
|
if (PersistentStore.getLong(name) > 0) {
|
|
PersistentStore.setLong(name, 0);
|
|
}
|
|
if (rateLimits.containsKey(name)) {
|
|
rateLimits.remove(name);
|
|
}
|
|
}
|
|
|
|
// return true if below rate limit (persistent version)
|
|
public static synchronized boolean pratelimit(String name, int seconds) {
|
|
// check if over limit
|
|
final long time_now = JoH.tsl();
|
|
final long rate_time;
|
|
if (!rateLimits.containsKey(name)) {
|
|
rate_time = PersistentStore.getLong(name); // 0 if undef
|
|
} else {
|
|
rate_time = rateLimits.get(name);
|
|
}
|
|
if ((rate_time > 0) && (time_now - rate_time) < (seconds * 1000L)) {
|
|
Log.d(TAG, name + " rate limited: " + seconds + " seconds");
|
|
return false;
|
|
}
|
|
// not over limit
|
|
rateLimits.put(name, time_now);
|
|
PersistentStore.setLong(name, time_now);
|
|
return true;
|
|
}
|
|
|
|
// return true if below rate limit
|
|
public static synchronized boolean ratelimit(String name, int seconds) {
|
|
// check if over limit
|
|
if ((rateLimits.containsKey(name)) && (JoH.tsl() - rateLimits.get(name) < (seconds * 1000L))) {
|
|
Log.d(TAG, name + " rate limited: " + seconds + " seconds");
|
|
return false;
|
|
}
|
|
// not over limit
|
|
rateLimits.put(name, JoH.tsl());
|
|
return true;
|
|
}
|
|
|
|
// return true if below rate limit
|
|
public static synchronized boolean quietratelimit(String name, int seconds) {
|
|
// check if over limit
|
|
if ((rateLimits.containsKey(name)) && (JoH.tsl() - rateLimits.get(name) < (seconds * 1000))) {
|
|
return false;
|
|
}
|
|
// not over limit
|
|
rateLimits.put(name, JoH.tsl());
|
|
return true;
|
|
}
|
|
|
|
// return true if below rate limit
|
|
public static synchronized boolean ratelimitmilli(String name, int milliseconds) {
|
|
// check if over limit
|
|
if ((rateLimits.containsKey(name)) && (JoH.tsl() - rateLimits.get(name) < (milliseconds))) {
|
|
Log.d(TAG, name + " rate limited: " + milliseconds + " milliseconds");
|
|
return false;
|
|
}
|
|
// not over limit
|
|
rateLimits.put(name, JoH.tsl());
|
|
return true;
|
|
}
|
|
|
|
public static String getDeviceDetails() {
|
|
final String manufacturer = Build.MANUFACTURER.replace(" ", "_");
|
|
final String model = Build.MODEL.replace(" ", "_");
|
|
final String version = Integer.toString(Build.VERSION.SDK_INT) + " " + Build.VERSION.RELEASE + " " + Build.VERSION.INCREMENTAL;
|
|
return manufacturer + " " + model + " " + version;
|
|
}
|
|
|
|
public static String getVersionDetails() {
|
|
try {
|
|
return xdrip.getAppContext().getPackageManager().getPackageInfo(xdrip.getAppContext().getPackageName(), PackageManager.GET_META_DATA).versionName;
|
|
} catch (Exception e) {
|
|
return "Unknown version";
|
|
}
|
|
}
|
|
|
|
public static boolean isOldVersion(Context context) {
|
|
try {
|
|
final Signature[] pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
|
|
if (pinfo.length == 1) {
|
|
final Checksum s = new CRC32();
|
|
final byte[] ba = pinfo[0].toByteArray();
|
|
s.update(ba, 0, ba.length);
|
|
if (s.getValue() == 2009579833) return true;
|
|
}
|
|
} catch (Exception e) {
|
|
Log.d(TAG, "exception: " + e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean getWifiSleepPolicyNever() {
|
|
try {
|
|
int policy = Settings.Global.getInt(xdrip.getAppContext().getContentResolver(), android.provider.Settings.Global.WIFI_SLEEP_POLICY);
|
|
Log.d(TAG, "Current WifiPolicy: " + ((policy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) ? "Never" : Integer.toString(policy)) + " " + Settings.Global.WIFI_SLEEP_POLICY_DEFAULT + " " + Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED);
|
|
return (policy == Settings.Global.WIFI_SLEEP_POLICY_NEVER);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception during global settings policy");
|
|
return true; // we don't know anything
|
|
}
|
|
}
|
|
|
|
public static void benchmark_method_start() {
|
|
benchmarks.put(backTrace(0), ts());
|
|
}
|
|
|
|
public static void benchmark_method_end() {
|
|
String name = backTrace(0);
|
|
try {
|
|
|
|
double timing = ts() - benchmarks.get(name);
|
|
Log.i(TAG, "Benchmark: " + name + " " + timing + "ms");
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Benchmark: " + name + " no benchmark set!");
|
|
}
|
|
}
|
|
|
|
public static void fixActionBar(AppCompatActivity context) {
|
|
try {
|
|
context.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
|
context.getSupportActionBar().setIcon(R.drawable.ic_launcher);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Got exception with supportactionbar: " + e.toString());
|
|
|
|
}
|
|
}
|
|
|
|
public static HashMap<String, Object> JsonStringtoMap(String json) {
|
|
return new Gson().fromJson(json, new TypeToken<HashMap<String, Object>>() {
|
|
}.getType());
|
|
}
|
|
|
|
private static Gson gson_instance;
|
|
public static Gson defaultGsonInstance() {
|
|
if (gson_instance == null) {
|
|
gson_instance = new GsonBuilder()
|
|
.excludeFieldsWithoutExposeAnnotation()
|
|
//.registerTypeAdapter(Date.class, new DateTypeAdapter())
|
|
// .serializeSpecialFloatingPointValues()
|
|
.create();
|
|
}
|
|
return gson_instance;
|
|
}
|
|
|
|
public static String hourMinuteString() {
|
|
// Date date = new Date();
|
|
// SimpleDateFormat sd = new SimpleDateFormat("HH:mm");
|
|
// return sd.format(date);
|
|
return hourMinuteString(JoH.tsl());
|
|
}
|
|
|
|
public static String hourMinuteString(long timestamp) {
|
|
return android.text.format.DateFormat.format("kk:mm", timestamp).toString();
|
|
}
|
|
|
|
public static String dateTimeText(long timestamp) {
|
|
return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString();
|
|
}
|
|
|
|
public static String dateText(long timestamp) {
|
|
return android.text.format.DateFormat.format("yyyy-MM-dd", timestamp).toString();
|
|
}
|
|
|
|
public static long getTimeZoneOffsetMs() {
|
|
return new GregorianCalendar().getTimeZone().getRawOffset();
|
|
}
|
|
|
|
public static String niceTimeSince(long t) {
|
|
return niceTimeScalar(msSince(t));
|
|
}
|
|
|
|
public static String niceTimeTill(long t) {
|
|
return niceTimeScalar(-msSince(t));
|
|
}
|
|
|
|
// temporary
|
|
public static String niceTimeScalar(long t) {
|
|
String unit = xdrip.getAppContext().getString(R.string.unit_second);
|
|
t = t / 1000;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_seconds);
|
|
if (t > 59) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_minute);
|
|
t = t / 60;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_minutes);
|
|
if (t > 59) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_hour);
|
|
t = t / 60;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_hours);
|
|
if (t > 24) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_day);
|
|
t = t / 24;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_days);
|
|
if (t > 28) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_week);
|
|
t = t / 7;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_weeks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character
|
|
return qs((double) t, 0) + " " + unit;
|
|
}
|
|
|
|
public static String niceTimeScalar(double t, int digits) {
|
|
String unit = xdrip.getAppContext().getString(R.string.unit_second);
|
|
t = t / 1000;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_seconds);
|
|
if (t > 59) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_minute);
|
|
t = t / 60;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_minutes);
|
|
if (t > 59) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_hour);
|
|
t = t / 60;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_hours);
|
|
if (t > 24) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_day);
|
|
t = t / 24;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_days);
|
|
if (t > 28) {
|
|
unit = xdrip.getAppContext().getString(R.string.unit_week);
|
|
t = t / 7;
|
|
if (t != 1) unit = xdrip.getAppContext().getString(R.string.unit_weeks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character
|
|
return qs( t, digits) + " " + unit;
|
|
}
|
|
|
|
|
|
public static String niceTimeScalarNatural(long t) {
|
|
if (t > 3000000) t = t + 10000; // round up by 10 seconds if nearly an hour
|
|
if ((t > Constants.DAY_IN_MS) && (t < Constants.WEEK_IN_MS * 2)) {
|
|
final SimpleDateFormat df = new SimpleDateFormat("EEEE", Locale.getDefault());
|
|
final String day = df.format(new Date(JoH.tsl() + t));
|
|
return ((t > Constants.WEEK_IN_MS) ? "next " : "") + day;
|
|
} else {
|
|
return niceTimeScalar(t);
|
|
}
|
|
}
|
|
|
|
public static String niceTimeScalarRedux(long t) {
|
|
return niceTimeScalar(t).replaceFirst("^1 ", "");
|
|
}
|
|
|
|
public static String niceTimeScalarShort(long t) {
|
|
return niceTimeScalar(t).replaceFirst("([A-z]).*$", "$1");
|
|
}
|
|
|
|
public static String niceTimeScalarShortWithDecimalHours(long t) {
|
|
if (t > Constants.HOUR_IN_MS) {
|
|
return niceTimeScalar(t,1).replaceFirst("([A-z]).*$", "$1");
|
|
} else {
|
|
return niceTimeScalar(t).replaceFirst("([A-z]).*$", "$1");
|
|
}
|
|
}
|
|
|
|
|
|
public static double tolerantParseDouble(String str) throws NumberFormatException {
|
|
return Double.parseDouble(str.replace(",", "."));
|
|
}
|
|
|
|
public static double tolerantParseDouble(final String str, final double def) {
|
|
if (str == null) return def;
|
|
try {
|
|
return Double.parseDouble(str.replace(",", "."));
|
|
} catch (NumberFormatException e) {
|
|
return def;
|
|
}
|
|
}
|
|
|
|
public static int tolerantParseInt(final String str, final int def) {
|
|
if (str == null) return def;
|
|
try {
|
|
return Integer.parseInt(str);
|
|
} catch (NumberFormatException e) {
|
|
return def;
|
|
}
|
|
}
|
|
|
|
public static long tolerantParseLong(final String str, final long def) {
|
|
if (str == null) return def;
|
|
try {
|
|
return Long.parseLong(str);
|
|
} catch (NumberFormatException e) {
|
|
return def;
|
|
}
|
|
}
|
|
|
|
|
|
public static String getRFC822String(long timestamp) {
|
|
final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
|
return dateFormat.format(new Date(timestamp));
|
|
}
|
|
|
|
public static PowerManager.WakeLock getWakeLock(final String name, int millis) {
|
|
final PowerManager pm = (PowerManager) xdrip.getAppContext().getSystemService(Context.POWER_SERVICE);
|
|
final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
|
|
wl.acquire(millis);
|
|
if (debug_wakelocks) Log.d(TAG, "getWakeLock: " + name + " " + wl.toString());
|
|
return wl;
|
|
}
|
|
|
|
public static void releaseWakeLock(PowerManager.WakeLock wl) {
|
|
if (debug_wakelocks) Log.d(TAG, "releaseWakeLock: " + wl.toString());
|
|
if (wl == null) return;
|
|
if (wl.isHeld()) {
|
|
try {
|
|
wl.release();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error releasing wakelock: " + e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static PowerManager.WakeLock fullWakeLock(final String name, long millis) {
|
|
final PowerManager pm = (PowerManager) xdrip.getAppContext().getSystemService(Context.POWER_SERVICE);
|
|
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, name);
|
|
wl.acquire(millis);
|
|
if (debug_wakelocks) Log.d(TAG, "fullWakeLock: " + name + " " + wl.toString());
|
|
return wl;
|
|
}
|
|
|
|
public static void fullDatabaseReset() {
|
|
try {
|
|
clearCache();
|
|
ActiveAndroid.dispose();
|
|
ActiveAndroid.initialize(xdrip.getAppContext());
|
|
} catch (Exception e) {
|
|
Log.e(TAG,"Error restarting active android db");
|
|
}
|
|
}
|
|
|
|
public static void clearCache() {
|
|
try {
|
|
ActiveAndroid.clearCache();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error clearing active android cache: " + e);
|
|
}
|
|
}
|
|
|
|
public static boolean isLANConnected() {
|
|
final ConnectivityManager cm =
|
|
(ConnectivityManager) xdrip.getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
final NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
|
final boolean isConnected = activeNetwork != null &&
|
|
activeNetwork.isConnected();
|
|
return isConnected && ((activeNetwork.getType() == ConnectivityManager.TYPE_WIFI)
|
|
|| (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET)
|
|
|| (activeNetwork.getType() == ConnectivityManager.TYPE_BLUETOOTH));
|
|
}
|
|
|
|
public static boolean isMobileDataOrEthernetConnected() {
|
|
final ConnectivityManager cm =
|
|
(ConnectivityManager) xdrip.getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
|
final boolean isConnected = activeNetwork != null &&
|
|
activeNetwork.isConnected();
|
|
return isConnected && ((activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) || (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET));
|
|
}
|
|
|
|
public static boolean isAnyNetworkConnected() {
|
|
final ConnectivityManager cm =
|
|
(ConnectivityManager) xdrip.getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
|
return activeNetwork != null &&
|
|
activeNetwork.isConnected();
|
|
}
|
|
|
|
public static boolean isScreenOn() {
|
|
final PowerManager pm = (PowerManager) xdrip.getAppContext().getSystemService(Context.POWER_SERVICE);
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
return pm.isInteractive();
|
|
} else {
|
|
return pm.isScreenOn();
|
|
}
|
|
}
|
|
|
|
public static boolean isOngoingCall() {
|
|
try {
|
|
AudioManager manager = (AudioManager) xdrip.getAppContext().getSystemService(Context.AUDIO_SERVICE);
|
|
return (manager.getMode() == AudioManager.MODE_IN_CALL);
|
|
// possibly should have MODE_IN_COMMUNICATION as well
|
|
} catch (Exception e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static String getWifiSSID() {
|
|
try {
|
|
final WifiManager wifi_manager = (WifiManager) xdrip.getAppContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
|
if (wifi_manager.isWifiEnabled()) {
|
|
final WifiInfo wifiInfo = wifi_manager.getConnectionInfo();
|
|
if (wifiInfo != null) {
|
|
final NetworkInfo.DetailedState wifi_state = WifiInfo.getDetailedStateOf(wifiInfo.getSupplicantState());
|
|
if (wifi_state == NetworkInfo.DetailedState.CONNECTED
|
|
|| wifi_state == NetworkInfo.DetailedState.OBTAINING_IPADDR
|
|
|| wifi_state == NetworkInfo.DetailedState.CAPTIVE_PORTAL_CHECK) {
|
|
String ssid = wifiInfo.getSSID();
|
|
if (ssid.equals("<unknown ssid>")) return null; // WifiSsid.NONE;
|
|
if (ssid.charAt(0) == '"') ssid = ssid.substring(1);
|
|
if (ssid.charAt(ssid.length() - 1) == '"')
|
|
ssid = ssid.substring(0, ssid.length() - 1);
|
|
return ssid;
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Got exception in getWifiSSID: " + e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static boolean getWifiFuzzyMatch(String local, String remote) {
|
|
if ((local == null) || (remote == null) || (local.length() == 0) || (remote.length() == 0))
|
|
return false;
|
|
final int slen = Math.min(local.length(), remote.length());
|
|
final int llen = Math.max(local.length(), remote.length());
|
|
int matched = 0;
|
|
for (int i = 0; i < slen; i++) {
|
|
if (local.charAt(i) == (remote.charAt(i))) matched++;
|
|
}
|
|
boolean result = false;
|
|
if (matched == slen) result = true; // shorter string is substring
|
|
final double quota = (double) matched / (double) llen;
|
|
final int dmatch = llen - matched;
|
|
if (slen > 2) {
|
|
if (dmatch < 3) result = true;
|
|
if (quota > 0.80) result = true;
|
|
}
|
|
//Log.d(TAG, "l:" + local + " r:" + remote + " slen:" + slen + " llen:" + llen + " matched:" + matched + " q:" + JoH.qs(quota, 2) + " dm:" + dmatch + " RESULT: " + result);
|
|
return result;
|
|
}
|
|
|
|
public static boolean runOnUiThread(Runnable theRunnable) {
|
|
final Handler mainHandler = new Handler(xdrip.getAppContext().getMainLooper());
|
|
return mainHandler.post(theRunnable);
|
|
}
|
|
|
|
public static boolean runOnUiThreadDelayed(Runnable theRunnable, long delay) {
|
|
final Handler mainHandler = new Handler(xdrip.getAppContext().getMainLooper());
|
|
return mainHandler.postDelayed(theRunnable, delay);
|
|
}
|
|
|
|
public static void removeUiThreadRunnable(Runnable theRunnable) {
|
|
final Handler mainHandler = new Handler(xdrip.getAppContext().getMainLooper());
|
|
mainHandler.removeCallbacks(theRunnable);
|
|
}
|
|
|
|
public static void hardReset() {
|
|
try {
|
|
android.os.Process.killProcess(android.os.Process.myPid());
|
|
} catch (Exception e) {
|
|
// not much to do
|
|
}
|
|
}
|
|
|
|
public static void static_toast(final Context context, final String msg, final int length) {
|
|
try {
|
|
if (!runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
Toast.makeText(context, msg, length).show();
|
|
Log.i(TAG, "Displaying toast using fallback");
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception processing runnable toast ui thread: " + e);
|
|
Home.toaststatic(msg);
|
|
}
|
|
}
|
|
})) {
|
|
Log.e(TAG, "Couldn't display toast via ui thread: " + msg);
|
|
Home.toaststatic(msg);
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Couldn't display toast due to exception: " + msg + " e: " + e.toString());
|
|
Home.toaststatic(msg);
|
|
}
|
|
}
|
|
|
|
public static void static_toast_long(final String msg) {
|
|
static_toast(xdrip.getAppContext(), msg, Toast.LENGTH_LONG);
|
|
}
|
|
|
|
public static void static_toast_short(final String msg) {
|
|
static_toast(xdrip.getAppContext(), msg, Toast.LENGTH_SHORT);
|
|
}
|
|
|
|
public static void static_toast_long(Context context, final String msg) {
|
|
static_toast(context, msg, Toast.LENGTH_LONG);
|
|
}
|
|
|
|
public static void show_ok_dialog(final Activity activity, final String title, final String message, final Runnable runnable) {
|
|
runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.AppTheme));
|
|
builder.setTitle(title);
|
|
builder.setMessage(message);
|
|
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
try {
|
|
dialog.dismiss();
|
|
} catch (Exception e) {
|
|
//
|
|
}
|
|
if (runnable != null) {
|
|
runOnUiThreadDelayed(runnable, 10);
|
|
}
|
|
}
|
|
});
|
|
|
|
builder.create().show();
|
|
} catch (Exception e) {
|
|
Log.wtf(TAG, "show_dialog exception: " + e);
|
|
static_toast_long(message);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public static synchronized void playResourceAudio(int id) {
|
|
playSoundUri(getResourceURI(id));
|
|
}
|
|
|
|
public static String getResourceURI(int id) {
|
|
return ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + xdrip.getAppContext().getPackageName() + "/" + id;
|
|
}
|
|
|
|
public static synchronized MediaPlayer playSoundUri(String soundUri) {
|
|
try {
|
|
JoH.getWakeLock("joh-playsound", 10000);
|
|
final MediaPlayer player = MediaPlayer.create(xdrip.getAppContext(), Uri.parse(soundUri));
|
|
player.setLooping(false);
|
|
player.start();
|
|
return player;
|
|
} catch (Exception e) {
|
|
Log.wtf(TAG, "Failed to play audio: " + soundUri + " exception:" + e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static boolean validateMacAddress(final String mac) {
|
|
return mac != null && mac.length() == 17 && mac.matches("([\\da-fA-F]{1,2}(?:\\:|$)){6}");
|
|
}
|
|
|
|
public static String urlEncode(String source) {
|
|
try {
|
|
return URLEncoder.encode(source, "UTF-8");
|
|
} catch (Exception e) {
|
|
return "encoding-exception";
|
|
}
|
|
}
|
|
|
|
public static Object cloneObject(Object obj) {
|
|
try {
|
|
Object clone = obj.getClass().newInstance();
|
|
for (Field field : obj.getClass().getDeclaredFields()) {
|
|
field.setAccessible(true);
|
|
field.set(clone, field.get(obj));
|
|
}
|
|
return clone;
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static void stopService(Class c) {
|
|
xdrip.getAppContext().stopService(new Intent(xdrip.getAppContext(), c));
|
|
}
|
|
|
|
public static void startService(Class c) {
|
|
xdrip.getAppContext().startService(new Intent(xdrip.getAppContext(), c));
|
|
}
|
|
|
|
public static void startService(final Class c, final String... args) {
|
|
startService(c, null, args);
|
|
}
|
|
|
|
public static void startService(final Class c, final byte[] bytes, final String... args) {
|
|
final Intent intent = new Intent(xdrip.getAppContext(), c);
|
|
if (bytes != null) {
|
|
intent.putExtra("bytes_payload", bytes);
|
|
}
|
|
if (args.length % 2 == 1) {
|
|
throw new RuntimeException("Odd number of args for JoH.startService");
|
|
}
|
|
for (int i = 0; i < args.length; i += 2) {
|
|
intent.putExtra(args[i], args[i + 1]);
|
|
}
|
|
xdrip.getAppContext().startService(intent);
|
|
}
|
|
|
|
|
|
public static void startActivity(Class c) {
|
|
xdrip.getAppContext().startActivity(getStartActivityIntent(c));
|
|
}
|
|
|
|
|
|
public static Intent getStartActivityIntent(Class c) {
|
|
return new Intent(xdrip.getAppContext(), c).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
}
|
|
|
|
|
|
public static void goFullScreen(boolean fullScreen, View decorView) {
|
|
|
|
if (fullScreen) {
|
|
if (Build.VERSION.SDK_INT >= 19) {
|
|
decorView.setSystemUiVisibility(
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
|
} else {
|
|
decorView.setSystemUiVisibility(
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_FULLSCREEN);
|
|
}
|
|
} else {
|
|
decorView.setSystemUiVisibility(0); // TODO will this need revisiting in later android vers?
|
|
}
|
|
}
|
|
|
|
|
|
public static Bitmap screenShot(View view, String annotation) {
|
|
|
|
if (view == null) {
|
|
static_toast_long("View is null in screenshot!");
|
|
return null;
|
|
}
|
|
final int width = view.getWidth();
|
|
final int height = view.getHeight();
|
|
Log.d(TAG, "Screenshot called: " + width + "," + height);
|
|
final Bitmap bitmap = Bitmap.createBitmap(width,
|
|
height, Bitmap.Config.ARGB_8888);
|
|
|
|
final Canvas canvas = new Canvas(bitmap);
|
|
if (Pref.getBooleanDefaultFalse(SHOW_STATISTICS_PRINT_COLOR)) {
|
|
Paint paint = new Paint();
|
|
paint.setColor(Color.WHITE);
|
|
paint.setStyle(Paint.Style.FILL);
|
|
canvas.drawRect(0, 0, width, height, paint);
|
|
}
|
|
|
|
|
|
view.destroyDrawingCache();
|
|
view.layout(0, 0, width, height);
|
|
view.draw(canvas);
|
|
|
|
if (annotation != null) {
|
|
final int offset = (annotation != null) ? 40 : 0;
|
|
final Bitmap bitmapf = Bitmap.createBitmap(width,
|
|
height + offset, Bitmap.Config.ARGB_8888);
|
|
final Canvas canvasf = new Canvas(bitmapf);
|
|
|
|
Paint paint = new Paint();
|
|
if (Pref.getBooleanDefaultFalse(SHOW_STATISTICS_PRINT_COLOR)) {
|
|
paint.setColor(Color.WHITE);
|
|
paint.setStyle(Paint.Style.FILL);
|
|
canvasf.drawRect(0, 0, width, offset, paint);
|
|
paint.setColor(Color.BLACK);
|
|
} else {
|
|
paint.setColor(Color.GRAY);
|
|
}
|
|
paint.setTextSize(20);
|
|
// paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
|
|
canvasf.drawBitmap(bitmap, 0, offset, paint);
|
|
canvasf.drawText(annotation, 50, (offset / 2) + 5, paint);
|
|
bitmap.recycle();
|
|
return bitmapf;
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
public static Bitmap screenShot2(View view) {
|
|
Log.d(TAG, "Screenshot2 called: " + view.getWidth() + "," + view.getHeight());
|
|
view.setDrawingCacheEnabled(true);
|
|
view.buildDrawingCache(true);
|
|
final Bitmap bitmap = view.getDrawingCache(true);
|
|
return bitmap;
|
|
}
|
|
|
|
|
|
public static void bitmapToFile(Bitmap bitmap, String path, String fileName) {
|
|
|
|
if (bitmap == null) return;
|
|
Log.d(TAG, "bitmapToFile: " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
File dir = new File(path);
|
|
if (!dir.exists())
|
|
dir.mkdirs();
|
|
final File file = new File(path, fileName);
|
|
try {
|
|
FileOutputStream output = new FileOutputStream(file);
|
|
final boolean result = bitmap.compress(Bitmap.CompressFormat.PNG, 80, output);
|
|
output.flush();
|
|
output.close();
|
|
Log.d(TAG, "Bitmap compress result: " + result);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Got exception writing bitmap to file: " + e);
|
|
}
|
|
}
|
|
|
|
public static void shareImage(Context context, File file) {
|
|
Uri uri = Uri.fromFile(file);
|
|
final Intent intent = new Intent();
|
|
intent.setAction(Intent.ACTION_SEND);
|
|
intent.setType("image/*");
|
|
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "");
|
|
intent.putExtra(android.content.Intent.EXTRA_TEXT, "");
|
|
intent.putExtra(Intent.EXTRA_STREAM, uri);
|
|
try {
|
|
context.startActivity(Intent.createChooser(intent, "Share"));
|
|
} catch (ActivityNotFoundException e) {
|
|
static_toast_long("No suitable app to show an image!");
|
|
}
|
|
}
|
|
|
|
public static void cancelAlarm(Context context, PendingIntent serviceIntent) {
|
|
// do we want a try catch block here?
|
|
final AlarmManager alarm = (AlarmManager) context.getSystemService(ALARM_SERVICE);
|
|
if (serviceIntent != null) {
|
|
Log.d(TAG, "Cancelling alarm " + serviceIntent.getCreatorPackage());
|
|
alarm.cancel(serviceIntent);
|
|
} else {
|
|
Log.d(TAG, "Cancelling alarm: serviceIntent is null");
|
|
}
|
|
}
|
|
|
|
public static long wakeUpIntent(Context context, long delayMs, PendingIntent pendingIntent) {
|
|
final long wakeTime = JoH.tsl() + delayMs;
|
|
if (pendingIntent != null) {
|
|
Log.d(TAG, "Scheduling wakeup intent: " + dateTimeText(wakeTime));
|
|
final AlarmManager alarm = (AlarmManager) context.getSystemService(ALARM_SERVICE);
|
|
try {
|
|
alarm.cancel(pendingIntent);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Exception cancelling alarm in wakeUpIntent: " + e);
|
|
}
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
if (buggy_samsung && Pref.getBoolean("allow_samsung_workaround", true)) {
|
|
alarm.setAlarmClock(new AlarmManager.AlarmClockInfo(wakeTime, null), pendingIntent);
|
|
} else {
|
|
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
|
|
}
|
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
alarm.setExact(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
|
|
} else
|
|
alarm.set(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
|
|
} else {
|
|
Log.e(TAG, "wakeUpIntent - pending intent was null!");
|
|
}
|
|
return wakeTime;
|
|
}
|
|
|
|
public static void scheduleNotification(Context context, String title, String body, int delaySeconds, int notification_id) {
|
|
final Intent notificationIntent = new Intent(context, Home.class).putExtra(Home.SHOW_NOTIFICATION, title).putExtra("notification_body", body).putExtra("notification_id", notification_id);
|
|
final PendingIntent pendingIntent = PendingIntent.getActivity(context, notification_id, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
Log.d(TAG, "Scheduling notification: " + title + " / " + body);
|
|
wakeUpIntent(context, delaySeconds * 1000, pendingIntent);
|
|
}
|
|
|
|
public static void cancelNotification(int notificationId) {
|
|
try {
|
|
final NotificationManager mNotifyMgr = (NotificationManager) xdrip.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
|
mNotifyMgr.cancel(notificationId);
|
|
} catch (Exception e) {
|
|
//
|
|
}
|
|
}
|
|
|
|
public static void showNotification(String title, String content, PendingIntent intent, int notificationId, boolean sound, boolean vibrate, boolean onetime) {
|
|
showNotification(title, content, intent, notificationId, sound, vibrate, null, null);
|
|
}
|
|
|
|
public static void showNotification(String title, String content, PendingIntent intent, int notificationId, boolean sound, boolean vibrate, PendingIntent deleteIntent, Uri sound_uri) {
|
|
showNotification(title, content, intent, notificationId, null, sound, vibrate, deleteIntent, sound_uri, null);
|
|
}
|
|
|
|
public static void showNotification(String title, String content, PendingIntent intent, int notificationId, boolean sound, boolean vibrate, PendingIntent deleteIntent, Uri sound_uri, String bigmsg) {
|
|
showNotification(title, content, intent, notificationId, null, sound, vibrate, deleteIntent, sound_uri, bigmsg);
|
|
}
|
|
|
|
public static void showNotification(String title, String content, PendingIntent intent, int notificationId, String channelId, boolean sound, boolean vibrate, PendingIntent deleteIntent, Uri sound_uri, String bigmsg) {
|
|
final NotificationCompat.Builder mBuilder = notificationBuilder(title, content, intent, channelId);
|
|
final long[] vibratePattern = {0, 1000, 300, 1000, 300, 1000};
|
|
if (vibrate) mBuilder.setVibrate(vibratePattern);
|
|
if (deleteIntent != null) mBuilder.setDeleteIntent(deleteIntent);
|
|
mBuilder.setLights(0xff00ff00, 300, 1000);
|
|
if (sound) {
|
|
Uri soundUri = (sound_uri != null) ? sound_uri : RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
|
mBuilder.setSound(soundUri);
|
|
}
|
|
|
|
if (bigmsg != null) {
|
|
mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigmsg));
|
|
}
|
|
|
|
final NotificationManager mNotifyMgr = (NotificationManager) xdrip.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
|
// if (!onetime) mNotifyMgr.cancel(notificationId);
|
|
|
|
mNotifyMgr.notify(notificationId, XdripNotificationCompat.build(mBuilder));
|
|
}
|
|
|
|
private static NotificationCompat.Builder notificationBuilder(String title, String content, PendingIntent intent, String channelId) {
|
|
return new NotificationCompat.Builder(xdrip.getAppContext(), channelId)
|
|
.setSmallIcon(R.drawable.ic_action_communication_invert_colors_on)
|
|
.setContentTitle(title)
|
|
.setContentText(content)
|
|
.setContentIntent(intent);
|
|
}
|
|
|
|
|
|
public static void releaseOrientation(Activity activity) {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
@SuppressLint("NewApi")
|
|
public static void lockOrientation(Activity activity) {
|
|
Display display = activity.getWindowManager().getDefaultDisplay();
|
|
int rotation = display.getRotation();
|
|
int height;
|
|
int width;
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR2) {
|
|
height = display.getHeight();
|
|
width = display.getWidth();
|
|
} else {
|
|
Point size = new Point();
|
|
display.getSize(size);
|
|
height = size.y;
|
|
width = size.x;
|
|
}
|
|
switch (rotation) {
|
|
case Surface.ROTATION_90:
|
|
if (width > height)
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
else
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
|
|
break;
|
|
case Surface.ROTATION_180:
|
|
if (height > width)
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
|
|
else
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
|
|
break;
|
|
case Surface.ROTATION_270:
|
|
if (width > height)
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
|
|
else
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
break;
|
|
default:
|
|
if (height > width)
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
else
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
}
|
|
}
|
|
|
|
public static boolean areWeRunningOnAndroidWear() {
|
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH
|
|
&& (xdrip.getAppContext().getResources().getConfiguration().uiMode
|
|
& Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH;
|
|
}
|
|
|
|
public static boolean isAirplaneModeEnabled(Context context) {
|
|
return Settings.Global.getInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
|
|
}
|
|
|
|
|
|
public static byte[] convertPinToBytes(String pin) {
|
|
if (pin == null) {
|
|
return null;
|
|
}
|
|
byte[] pinBytes;
|
|
try {
|
|
pinBytes = pin.getBytes("UTF-8");
|
|
} catch (UnsupportedEncodingException uee) {
|
|
Log.e(TAG, "UTF-8 not supported?!?"); // this should not happen
|
|
return null;
|
|
}
|
|
if (pinBytes.length <= 0 || pinBytes.length > 16) {
|
|
return null;
|
|
}
|
|
return pinBytes;
|
|
}
|
|
|
|
public static boolean doPairingRequest(Context context, BroadcastReceiver broadcastReceiver, Intent intent, String mBluetoothDeviceAddress) {
|
|
return doPairingRequest(context, broadcastReceiver, intent, mBluetoothDeviceAddress, null);
|
|
}
|
|
|
|
@TargetApi(19)
|
|
public static boolean doPairingRequest(Context context, BroadcastReceiver broadcastReceiver, Intent intent, final String mBluetoothDeviceAddress, final String pinHint) {
|
|
if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) {
|
|
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
|
if (device != null) {
|
|
int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
|
|
if ((mBluetoothDeviceAddress != null) && (device.getAddress().equals(mBluetoothDeviceAddress))) {
|
|
|
|
if (type == PAIRING_VARIANT_PASSKEY && pinHint != null) {
|
|
return false;
|
|
}
|
|
|
|
if ((type == PAIRING_VARIANT_PIN) && (pinHint != null)) {
|
|
device.setPin(convertPinToBytes(pinHint));
|
|
Log.d(TAG, "Setting pairing pin to " + pinHint);
|
|
broadcastReceiver.abortBroadcast();
|
|
}
|
|
try {
|
|
UserError.Log.e(TAG, "Pairing type: " + type);
|
|
if (type != PAIRING_VARIANT_PIN && type != PAIRING_VARIANT_PASSKEY) {
|
|
device.setPairingConfirmation(true);
|
|
JoH.static_toast_short("xDrip Pairing");
|
|
broadcastReceiver.abortBroadcast();
|
|
} else {
|
|
Log.d(TAG, "Attempting to passthrough PIN pairing");
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, "Could not set pairing confirmation due to exception: " + e);
|
|
if (JoH.ratelimit("failed pair confirmation", 200)) {
|
|
// BluetoothDevice.PAIRING_VARIANT_CONSENT)
|
|
if (type == 3) {
|
|
JoH.static_toast_long("Please confirm the bluetooth pairing request");
|
|
return false;
|
|
} else {
|
|
JoH.static_toast_long("Failed to pair, may need to do it via Android Settings");
|
|
device.createBond(); // for what it is worth
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
UserError.Log.e(TAG, "Received pairing request for not our device: " + device.getAddress());
|
|
}
|
|
} else {
|
|
UserError.Log.e(TAG, "Device was null in pairing receiver");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static String getLocalBluetoothName() {
|
|
try {
|
|
final String name = BluetoothAdapter.getDefaultAdapter().getName();
|
|
if (name == null) return "";
|
|
return name;
|
|
} catch (Exception e) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public static boolean refreshDeviceCache(String thisTAG, BluetoothGatt gatt){
|
|
try {
|
|
final Method method = gatt.getClass().getMethod("refresh", new Class[0]);
|
|
if (method != null) {
|
|
return (Boolean) method.invoke(gatt, new Object[0]);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
Log.e(thisTAG, "An exception occured while refreshing gatt device cache: "+e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean createSpecialBond(final String thisTAG, final BluetoothDevice device){
|
|
try {
|
|
Log.e(thisTAG,"Attempting special bond");
|
|
Class[] argTypes = new Class[] { int.class };
|
|
final Method method = device.getClass().getMethod("createBond", argTypes);
|
|
if (method != null) {
|
|
return (Boolean) method.invoke(device, 2);
|
|
} else {
|
|
Log.e(thisTAG,"CANNOT FIND SPECIAL BOND METHOD!!");
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
Log.e(thisTAG, "An exception occured while creating special bond: "+e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
public synchronized static void setBluetoothEnabled(Context context, boolean state) {
|
|
try {
|
|
|
|
if (isAirplaneModeEnabled(context)) {
|
|
UserError.Log.e(TAG, "Not setting bluetooth to state: " + state + " due to airplane mode being enabled");
|
|
return;
|
|
}
|
|
|
|
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
|
|
final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
|
|
if (bluetoothManager == null) {
|
|
UserError.Log.e(TAG, "Couldn't get bluetooth in setBluetoothEnabled");
|
|
return;
|
|
}
|
|
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter(); // local scope only
|
|
if (mBluetoothAdapter == null) {
|
|
UserError.Log.e(TAG, "Couldn't get bluetooth adapter in setBluetoothEnabled");
|
|
return;
|
|
}
|
|
try {
|
|
if (state) {
|
|
UserError.Log.i(TAG, "Setting bluetooth enabled");
|
|
mBluetoothAdapter.enable();
|
|
} else {
|
|
UserError.Log.i(TAG, "Setting bluetooth disabled");
|
|
mBluetoothAdapter.disable();
|
|
|
|
}
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, "Exception when enabling/disabling bluetooth: " + e);
|
|
}
|
|
} else {
|
|
UserError.Log.e(TAG, "Bluetooth low energy not supported");
|
|
}
|
|
} finally {
|
|
//
|
|
}
|
|
}
|
|
|
|
public static void niceRestartBluetooth(Context context) {
|
|
if (!isOngoingCall()) {
|
|
if (ratelimit("joh-restart-bluetooth", 600)) {
|
|
restartBluetooth(context);
|
|
}
|
|
}
|
|
}
|
|
|
|
public synchronized static void restartBluetooth(final Context context) {
|
|
restartBluetooth(context, 0);
|
|
}
|
|
|
|
public synchronized static void restartBluetooth(final Context context, final int startInMs) {
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
final PowerManager.WakeLock wl = getWakeLock("restart-bluetooth", 60000);
|
|
Log.d(TAG, "Restarting bluetooth");
|
|
try {
|
|
if (startInMs > 0) {
|
|
try {
|
|
Thread.sleep(startInMs);
|
|
} catch (InterruptedException e) {
|
|
Log.d(TAG, "Got interrupted waiting to start resetBluetooth");
|
|
}
|
|
}
|
|
setBluetoothEnabled(context, false);
|
|
try {
|
|
Thread.sleep(6000);
|
|
} catch (InterruptedException e) {
|
|
Log.d(TAG, "Got interrupted in resetBluetooth");
|
|
}
|
|
setBluetoothEnabled(context, true);
|
|
} finally {
|
|
releaseWakeLock(wl);
|
|
}
|
|
}
|
|
}.start();
|
|
}
|
|
|
|
|
|
public static synchronized void unBond(String transmitterMAC) {
|
|
|
|
UserError.Log.d(TAG, "unBond() start");
|
|
if (transmitterMAC == null) return;
|
|
try {
|
|
final BluetoothAdapter mBluetoothAdapter = ((BluetoothManager) xdrip.getAppContext().getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
|
|
|
|
final Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
|
|
if (pairedDevices.size() > 0) {
|
|
for (BluetoothDevice device : pairedDevices) {
|
|
if (device.getAddress() != null) {
|
|
if (device.getAddress().equals(transmitterMAC)) {
|
|
try {
|
|
UserError.Log.e(TAG, "removingBond: " + transmitterMAC);
|
|
Method m = device.getClass().getMethod("removeBond", (Class[]) null);
|
|
m.invoke(device, (Object[]) null);
|
|
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
UserError.Log.e(TAG, "Exception during unbond! " + transmitterMAC, e);
|
|
}
|
|
UserError.Log.d(TAG, "unBond() finished");
|
|
}
|
|
|
|
|
|
public static Map<String, String> bundleToMap(Bundle bundle) {
|
|
final HashMap<String, String> map = new HashMap<>();
|
|
for (String key : bundle.keySet()) {
|
|
Object value = bundle.get(key);
|
|
if (value != null) {
|
|
map.put(key, value.toString());
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
public static void threadSleep(long millis) {
|
|
try {
|
|
Thread.sleep(millis);
|
|
} catch (InterruptedException e) {
|
|
//
|
|
}
|
|
}
|
|
|
|
public static ByteBuffer bArrayAsBuffer(byte[] bytes) {
|
|
final ByteBuffer bb = ByteBuffer.allocate(bytes.length);
|
|
bb.put(bytes);
|
|
return bb;
|
|
}
|
|
|
|
public static long checksum(byte[] bytes) {
|
|
if (bytes == null) return 0;
|
|
final CRC32 crc = new CRC32();
|
|
crc.update(bytes);
|
|
return crc.getValue();
|
|
}
|
|
|
|
|
|
|
|
public static byte[] bchecksum(byte[] bytes) {
|
|
final long c = checksum(bytes);
|
|
final byte[] buf = new byte[4];
|
|
ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).putInt((int) c);
|
|
return buf;
|
|
}
|
|
|
|
public static boolean checkChecksum(byte[] bytes) {
|
|
if ((bytes == null) || (bytes.length < 4)) return false;
|
|
final CRC32 crc = new CRC32();
|
|
crc.update(bytes, 0, bytes.length - 4);
|
|
final long buffer_crc = UnsignedInts.toLong(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(bytes.length - 4));
|
|
return buffer_crc == crc.getValue();
|
|
}
|
|
|
|
public static int parseIntWithDefault(String number, int radix, int defaultVal) {
|
|
try {
|
|
return Integer.parseInt(number, radix);
|
|
} catch (NumberFormatException e) {
|
|
Log.e(TAG, "Error parsing integer number = " + number + " radix = " + radix);
|
|
return defaultVal;
|
|
}
|
|
}
|
|
|
|
public static double roundDouble(final double value, int places) {
|
|
if (places < 0) throw new IllegalArgumentException("Invalid decimal places");
|
|
BigDecimal bd = new BigDecimal(value);
|
|
bd = bd.setScale(places, RoundingMode.HALF_UP);
|
|
return bd.doubleValue();
|
|
}
|
|
|
|
public static float roundFloat(final float value, int places) {
|
|
if (places < 0) throw new IllegalArgumentException("Invalid decimal places");
|
|
BigDecimal bd = new BigDecimal(value);
|
|
bd = bd.setScale(places, RoundingMode.HALF_UP);
|
|
return bd.floatValue();
|
|
}
|
|
|
|
public static boolean isServiceRunningInForeground(Class<?> serviceClass) {
|
|
final ActivityManager manager = (ActivityManager) xdrip.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);
|
|
try {
|
|
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
|
if (serviceClass.getName().equals(service.service.getClassName())) {
|
|
return service.foreground;
|
|
}
|
|
}
|
|
return false;
|
|
} catch (NullPointerException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static boolean emptyString(final String str) {
|
|
return str == null || str.length() == 0;
|
|
}
|
|
|
|
|
|
}
|