FairEmail/app/src/main/java/eu/faircode/email/Log.java

3861 lines
206 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import static androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION;
import android.Manifest;
import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
import android.app.Dialog;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.UriPermission;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteFullException;
import android.graphics.Point;
import android.graphics.Typeface;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.DeadSystemException;
import android.os.Debug;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.OperationCanceledException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.TransactionTooLargeException;
import android.os.ext.SdkExtensions;
import android.provider.MediaStore;
import android.provider.Settings;
import android.security.NetworkSecurityPolicy;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.util.Printer;
import android.view.Display;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.emoji2.text.EmojiCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.WorkQuery;
import com.bugsnag.android.BreadcrumbType;
import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.Client;
import com.bugsnag.android.ErrorTypes;
import com.bugsnag.android.Event;
import com.bugsnag.android.OnErrorCallback;
import com.bugsnag.android.OnSessionCallback;
import com.bugsnag.android.Session;
import com.bugsnag.android.Severity;
import com.sun.mail.iap.BadCommandException;
import com.sun.mail.iap.ConnectionException;
import com.sun.mail.iap.ProtocolException;
import com.sun.mail.util.FolderClosedIOException;
import com.sun.mail.util.MailConnectException;
import net.openid.appauth.AuthState;
import net.openid.appauth.TokenResponse;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileStore;
import java.nio.file.FileSystems;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertPathValidatorException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.FolderClosedException;
import javax.mail.MessageRemovedException;
import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.StoreClosedException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class Log {
private static Context ctx;
private static final long MAX_LOG_SIZE = 8 * 1024 * 1024L;
private static final int MAX_CRASH_REPORTS = (BuildConfig.TEST_RELEASE ? 50 : 5);
private static final long MIN_FILE_SIZE = 1024 * 1024L;
private static final long MIN_ZIP_SIZE = 2 * 1024 * 1024L;
private static final String TAG = "fairemail";
// https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
// https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html
private static final List<String> NETWORK_PROPS = Collections.unmodifiableList(Arrays.asList(
"java.net.preferIPv4Stack",
"java.net.preferIPv6Addresses",
"http.proxyHost",
"http.proxyPort",
"http.nonProxyHosts",
"https.proxyHost",
"https.proxyPort",
//"ftp.proxyHost",
//"ftp.proxyPort",
//"ftp.nonProxyHosts",
"socksProxyHost",
"socksProxyPort",
"socksProxyVersion",
"java.net.socks.username",
//"java.net.socks.password",
"http.agent",
"http.keepalive",
"http.maxConnections",
"http.maxRedirects",
"http.auth.digest.validateServer",
"http.auth.digest.validateProxy",
"http.auth.digest.cnonceRepeat",
"http.auth.ntlm.domain",
"jdk.https.negotiate.cbt",
"networkaddress.cache.ttl",
"networkaddress.cache.negative.ttl"
));
static final String TOKEN_REFRESH_REQUIRED =
"Token refresh required. Is there a VPN based app running?";
static {
System.loadLibrary("fairemail");
}
public static native int jni_safe_log(int prio, String tag, String msg);
public static native long[] jni_safe_runtime_stats();
public static int d(String msg) {
return d(TAG, msg);
}
public static int d(String tag, String msg) {
org.tinylog.Logger.tag(tag).debug(msg);
return 0;
}
public static int i(String msg) {
return i(TAG, msg);
}
public static int i(String tag, String msg) {
org.tinylog.Logger.tag(tag).info(msg);
return 0;
}
public static int w(String msg) {
org.tinylog.Logger.tag(TAG).warn(msg);
return 0;
}
public static int e(String msg) {
if (BuildConfig.BETA_RELEASE)
try {
ThrowableWrapper ex = new ThrowableWrapper();
ex.setMessage(msg);
Bugsnag.notify(ex, new OnErrorCallback() {
@Override
public boolean onError(@NonNull Event event) {
event.setSeverity(Severity.ERROR);
return true;
}
});
} catch (Throwable ex) {
Log.i(ex);
}
org.tinylog.Logger.tag(TAG).error(msg);
return 0;
}
public static int i(Throwable ex) {
org.tinylog.Logger.tag(TAG).info(ex);
return 0;
}
public static int w(Throwable ex) {
if (BuildConfig.BETA_RELEASE)
try {
final StackTraceElement[] ste = new Throwable().getStackTrace();
Bugsnag.notify(ex, new OnErrorCallback() {
@Override
public boolean onError(@NonNull Event event) {
event.setSeverity(Severity.INFO);
if (ste.length > 1)
event.addMetadata("extra", "caller", ste[1].toString());
return true;
}
});
} catch (Throwable ex1) {
Log.i(ex1);
}
org.tinylog.Logger.tag(TAG).warn(ex);
return 0;
}
public static int e(Throwable ex) {
if (BuildConfig.BETA_RELEASE)
try {
final StackTraceElement[] ste = new Throwable().getStackTrace();
Bugsnag.notify(ex, new OnErrorCallback() {
@Override
public boolean onError(@NonNull Event event) {
event.setSeverity(Severity.WARNING);
if (ste.length > 1)
event.addMetadata("extra", "caller", ste[1].toString());
return true;
}
});
} catch (Throwable ex1) {
Log.i(ex1);
}
org.tinylog.Logger.tag(TAG).error(ex);
return 0;
}
public static int i(String prefix, Throwable ex) {
org.tinylog.Logger.tag(TAG).info(ex, prefix);
return 0;
}
public static int w(String prefix, Throwable ex) {
if (BuildConfig.BETA_RELEASE)
try {
Bugsnag.notify(ex, new OnErrorCallback() {
@Override
public boolean onError(@NonNull Event event) {
event.setSeverity(Severity.INFO);
return true;
}
});
} catch (Throwable ex1) {
Log.i(ex1);
}
org.tinylog.Logger.tag(TAG).warn(ex, prefix);
return 0;
}
public static int e(String prefix, Throwable ex) {
if (BuildConfig.BETA_RELEASE)
try {
Bugsnag.notify(ex, new OnErrorCallback() {
@Override
public boolean onError(@NonNull Event event) {
event.setSeverity(Severity.WARNING);
return true;
}
});
} catch (Throwable ex1) {
Log.i(ex1);
}
org.tinylog.Logger.tag(TAG).error(ex, prefix);
return 0;
}
public static void persist(String message) {
if (ctx == null)
org.tinylog.Logger.tag(TAG).error(message);
else
EntityLog.log(ctx, message);
}
public static void persist(EntityLog.Type type, String message) {
if (ctx == null)
org.tinylog.Logger.tag(TAG).error(type.name() + " " + message);
else
EntityLog.log(ctx, type, message);
}
static void setCrashReporting(boolean enabled) {
try {
if (enabled)
Bugsnag.startSession();
} catch (Throwable ex) {
Log.i(ex);
}
}
public static void breadcrumb(String name, Bundle args) {
Map<String, String> crumb = new HashMap<>();
for (String key : args.keySet()) {
Object value = args.get(key);
if (value instanceof Boolean)
crumb.put(key, Boolean.toString((Boolean) value));
else if (value instanceof Integer)
crumb.put(key, Integer.toString((Integer) value));
else if (value instanceof Long)
crumb.put(key, Long.toString((Long) value));
else if (value instanceof Float)
crumb.put(key, Float.toString((Float) value));
else if (value instanceof Double)
crumb.put(key, Double.toString((Double) value));
else if (value instanceof String || value instanceof Spanned) {
String v = value.toString();
if (v.length() > 50)
v = v.substring(0, 50) + "...";
crumb.put(key, v);
} else if (value == null)
crumb.put(key, "<null>");
else
crumb.put(key, "<" + value.getClass().getName() + ">");
}
breadcrumb(name, crumb);
}
public static void breadcrumb(String name, String key, String value) {
Map<String, String> crumb = new HashMap<>();
crumb.put(key, value);
breadcrumb(name, crumb);
}
public static void breadcrumb(String name, Map<String, String> crumb) {
try {
crumb.put("free", Integer.toString(Log.getFreeMemMb()));
StringBuilder sb = new StringBuilder();
sb.append("Breadcrumb ").append(name);
Map<String, Object> ocrumb = new HashMap<>();
for (String key : crumb.keySet()) {
String val = crumb.get(key);
sb.append(' ').append(key).append('=').append(val);
ocrumb.put(key, val);
}
Log.i(sb.toString());
Bugsnag.leaveBreadcrumb(name, ocrumb, BreadcrumbType.LOG);
} catch (Throwable ex) {
Log.e(ex);
}
}
static void setup(Context context) {
ctx = context;
setupBugsnag(context);
}
private static void setupBugsnag(final Context context) {
try {
Log.i("Configuring Bugsnag");
// https://docs.bugsnag.com/platforms/android/sdk/
com.bugsnag.android.Configuration config =
new com.bugsnag.android.Configuration("9d2d57476a0614974449a3ec33f2604a");
config.setTelemetry(Collections.emptySet());
if (BuildConfig.DEBUG)
config.setReleaseStage("Debug");
else
config.setReleaseStage(getReleaseType(context));
config.setAutoTrackSessions(false);
ErrorTypes etypes = new ErrorTypes();
etypes.setAnrs(BuildConfig.DEBUG);
etypes.setNdkCrashes(false);
config.setEnabledErrorTypes(etypes);
config.setMaxBreadcrumbs(BuildConfig.PLAY_STORE_RELEASE ? 250 : 500);
Set<String> ignore = new HashSet<>();
ignore.add("com.sun.mail.util.MailConnectException");
ignore.add("android.accounts.AuthenticatorException");
ignore.add("android.accounts.OperationCanceledException");
ignore.add("android.app.RemoteServiceException");
ignore.add("java.lang.NoClassDefFoundError");
ignore.add("java.lang.UnsatisfiedLinkError");
ignore.add("java.nio.charset.MalformedInputException");
ignore.add("java.net.ConnectException");
ignore.add("java.net.SocketException");
ignore.add("java.net.SocketTimeoutException");
ignore.add("java.net.UnknownHostException");
ignore.add("javax.mail.AuthenticationFailedException");
ignore.add("javax.mail.internet.AddressException");
ignore.add("javax.mail.internet.ParseException");
ignore.add("javax.mail.MessageRemovedException");
ignore.add("javax.mail.FolderNotFoundException");
ignore.add("javax.mail.ReadOnlyFolderException");
ignore.add("javax.mail.FolderClosedException");
ignore.add("com.sun.mail.util.FolderClosedIOException");
ignore.add("javax.mail.StoreClosedException");
ignore.add("org.xmlpull.v1.XmlPullParserException");
config.setDiscardClasses(ignore);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
ActivityManager am = Helper.getSystemService(context, ActivityManager.class);
String no_internet = context.getString(R.string.title_no_internet);
String installer = Helper.getInstallerName(context);
config.addMetadata("extra", "revision", BuildConfig.REVISION);
config.addMetadata("extra", "installer", installer == null ? "-" : installer);
config.addMetadata("extra", "installed", new Date(Helper.getInstallTime(context)).toString());
config.addMetadata("extra", "fingerprint", Helper.hasValidFingerprint(context));
config.addMetadata("extra", "memory_class", am.getMemoryClass());
config.addMetadata("extra", "memory_class_large", am.getLargeMemoryClass());
config.addMetadata("extra", "build_host", Build.HOST);
config.addMetadata("extra", "build_time", new Date(Build.TIME));
config.addOnSession(new OnSessionCallback() {
@Override
public boolean onSession(@NonNull Session session) {
// opt-in
return prefs.getBoolean("crash_reports", false) || BuildConfig.TEST_RELEASE;
}
});
config.addOnError(new OnErrorCallback() {
@Override
public boolean onError(@NonNull Event event) {
// opt-in
boolean crash_reports = prefs.getBoolean("crash_reports", false);
if (!crash_reports && !BuildConfig.TEST_RELEASE)
return false;
Throwable ex = event.getOriginalError();
boolean should = shouldNotify(ex);
if (should) {
event.addMetadata("extra", "pid", Integer.toString(android.os.Process.myPid()));
event.addMetadata("extra", "thread", Thread.currentThread().getName() + ":" + Thread.currentThread().getId());
event.addMetadata("extra", "memory_free", getFreeMemMb());
event.addMetadata("extra", "memory_available", getAvailableMb());
event.addMetadata("extra", "native_allocated", Debug.getNativeHeapAllocatedSize() / 1024L / 1024L);
event.addMetadata("extra", "native_size", Debug.getNativeHeapSize() / 1024L / 1024L);
event.addMetadata("extra", "classifier_size", MessageClassifier.getSize(context));
Boolean ignoringOptimizations = Helper.isIgnoringOptimizations(context);
event.addMetadata("extra", "optimizing", (ignoringOptimizations != null && !ignoringOptimizations));
String theme = prefs.getString("theme", "blue_orange_system");
event.addMetadata("extra", "theme", theme);
event.addMetadata("extra", "package", BuildConfig.APPLICATION_ID);
event.addMetadata("extra", "locale", Locale.getDefault().toString());
}
return should;
}
private boolean shouldNotify(Throwable ex) {
if (ex instanceof MessagingException &&
(ex.getCause() instanceof IOException ||
ex.getCause() instanceof ProtocolException))
// IOException includes SocketException, SocketTimeoutException
// ProtocolException includes ConnectionException
return false;
if (ex instanceof MessagingException &&
("connection failure".equals(ex.getMessage()) ||
"failed to create new store connection".equals(ex.getMessage()) ||
"Failed to fetch headers".equals(ex.getMessage()) ||
"Failed to load IMAP envelope".equals(ex.getMessage()) ||
"Unable to load BODYSTRUCTURE".equals(ex.getMessage())))
return false;
if (ex instanceof IllegalStateException &&
(no_internet.equals(ex.getMessage()) ||
TOKEN_REFRESH_REQUIRED.equals(ex.getMessage()) ||
"Not connected".equals(ex.getMessage()) ||
"This operation is not allowed on a closed folder".equals(ex.getMessage())))
return false;
if (ex instanceof FileNotFoundException &&
ex.getMessage() != null &&
(ex.getMessage().startsWith("Download image failed") ||
ex.getMessage().startsWith("http://") ||
ex.getMessage().startsWith("https://") ||
ex.getMessage().startsWith("content://")))
return false;
if (ex instanceof IOException &&
ex.getCause() instanceof MessageRemovedException)
return false;
if (ex instanceof IOException &&
ex.getMessage() != null &&
(ex.getMessage().startsWith("HTTP status=") ||
"NetworkError".equals(ex.getMessage()) || // account manager
"Resetting to invalid mark".equals(ex.getMessage()) ||
"Mark has been invalidated.".equals(ex.getMessage())))
return false;
if (ex instanceof SSLPeerUnverifiedException ||
ex instanceof EmailService.UntrustedException)
return false;
if (ex instanceof SSLHandshakeException &&
ex.getCause() instanceof CertPathValidatorException)
return false; // checkUpdate!
if (ex instanceof RuntimeException &&
"Illegal meta data value: the child service doesn't exist".equals(ex.getMessage()))
return false;
if (isDead(ex))
return false;
// Rate limit
int count = prefs.getInt("crash_report_count", 0) + 1;
prefs.edit().putInt("crash_report_count", count).apply();
return (count <= MAX_CRASH_REPORTS);
}
});
Bugsnag.start(context, config);
Client client = Bugsnag.getClient();
String uuid = prefs.getString("uuid", null);
if (uuid == null) {
uuid = UUID.randomUUID().toString();
prefs.edit().putString("uuid", uuid).apply();
}
Log.i("uuid=" + uuid);
client.setUser(uuid, null, null);
if (prefs.getBoolean("crash_reports", false) || BuildConfig.TEST_RELEASE)
Bugsnag.startSession();
} catch (Throwable ex) {
Log.e(ex);
/*
java.lang.AssertionError: No NameTypeIndex match for SHORT_DAYLIGHT
at android.icu.impl.TimeZoneNamesImpl$ZNames.getNameTypeIndex(TimeZoneNamesImpl.java:724)
at android.icu.impl.TimeZoneNamesImpl$ZNames.getName(TimeZoneNamesImpl.java:790)
at android.icu.impl.TimeZoneNamesImpl.getTimeZoneDisplayName(TimeZoneNamesImpl.java:183)
at android.icu.text.TimeZoneNames.getDisplayName(TimeZoneNames.java:261)
at java.util.TimeZone.getDisplayName(TimeZone.java:405)
at java.util.Date.toString(Date.java:1066)
*/
}
}
static @NonNull String getReleaseType(Context context) {
if (Helper.hasValidFingerprint(context)) {
if (BuildConfig.PLAY_STORE_RELEASE) {
String installer = Helper.getInstallerName(context);
String type = "Play Store";
if (installer != null && !Helper.PLAY_PACKAGE_NAME.equals(installer))
type += " (" + installer + ")";
return type;
} else if (BuildConfig.FDROID_RELEASE)
return "Reproducible";
else if (BuildConfig.AMAZON_RELEASE)
return "Amazon";
else
return "GitHub";
} else if (Helper.isSignedByFDroid(context))
return "F-Droid";
else {
if (BuildConfig.APPLICATION_ID.startsWith("eu.faircode.email"))
return "Other";
else
return "Clone";
}
}
static void logExtras(Intent intent) {
if (intent != null)
logBundle(intent.getExtras());
}
static void logBundle(Bundle data) {
for (String extra : getExtras(data))
i(extra);
}
static List<String> getExtras(Bundle data) {
List<String> result = new ArrayList<>();
if (data == null)
return result;
try {
Set<String> keys = data.keySet();
for (String key : keys) {
Object v = data.get(key);
Object value = v;
int length = -1;
if (v != null && v.getClass().isArray()) {
length = Array.getLength(v);
String[] elements = new String[Math.min(length, 10)];
for (int i = 0; i < elements.length; i++) {
Object element = Array.get(v, i);
if (element instanceof Long)
elements[i] = element + " (0x" + Long.toHexString((Long) element) + ")";
else if (element instanceof Spanned)
elements[i] = "(span:" + Helper.getPrintableString(element.toString()) + ")";
else
elements[i] = (element == null ? "<null>" : Helper.getPrintableString(element.toString()));
}
value = TextUtils.join(",", elements);
if (length > 10)
value += ", ...";
value = "[" + value + "]";
} else if (v instanceof Long)
value = v + " (0x" + Long.toHexString((Long) v) + ")";
else if (v instanceof Spanned)
value = "(span:" + Helper.getPrintableString(v.toString()) + ")";
else if (v instanceof Bundle)
value = "{" + TextUtils.join(" ", getExtras((Bundle) v)) + "}";
result.add(key + "=" + value + (value == null ? "" :
" (" + v.getClass().getSimpleName() + (length < 0 ? "" : ":" + length) + ")"));
}
} catch (Throwable ex) {
// android.os.BadParcelableException: ClassNotFoundException when unmarshalling: ...
// java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1172374955, result=0, data=Intent { (has extras) }} to activity {eu.faircode.email/eu.faircode.email.ActivityCompose}: java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = com.lyrebirdstudio.imagecameralib.utils.ImageCameraLibReturnTypes)
// at android.app.ActivityThread.deliverResults(ActivityThread.java:4382)
// at android.app.ActivityThread.handleSendResult(ActivityThread.java:4426)
// at android.app.ActivityThread.-wrap20(Unknown)
// at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1685)
// at android.os.Handler.dispatchMessage(Handler.java:106)
// at android.os.Looper.loop(Looper.java:164)
// at android.app.ActivityThread.main(ActivityThread.java:6626)
// at java.lang.reflect.Method.invoke(Method.java:-2)
// at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
// at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)
//
// Caused by: java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = com.lyrebirdstudio.imagecameralib.utils.ImageCameraLibReturnTypes)
// at android.os.Parcel.readSerializable(Parcel.java:3019)
// at android.os.Parcel.readValue(Parcel.java:2805)
// at android.os.Parcel.readArrayMapInternal(Parcel.java:3123)
// at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:273)
// at android.os.BaseBundle.unparcel(BaseBundle.java:226)
// at android.os.BaseBundle.keySet(BaseBundle.java:520)
// at eu.faircode.email.Log.getExtras(Log:565)
// at eu.faircode.email.Log.logBundle(Log:555)
// at eu.faircode.email.Log.logExtras(Log:551)
// at eu.faircode.email.ActivityBase.onActivityResult(ActivityBase:376)
// at android.app.Activity.dispatchActivityResult(Activity.java:7305)
// at android.app.ActivityThread.deliverResults(ActivityThread.java:4378)
// at android.app.ActivityThread.handleSendResult(ActivityThread.java:4426)
// at android.app.ActivityThread.-wrap20(Unknown)
// at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1685)
// at android.os.Handler.dispatchMessage(Handler.java:106)
// at android.os.Looper.loop(Looper.java:164)
// at android.app.ActivityThread.main(ActivityThread.java:6626)
// at java.lang.reflect.Method.invoke(Method.java:-2)
// at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
// at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)
//
// Caused by: java.lang.ClassNotFoundException: com.lyrebirdstudio.imagecameralib.utils.ImageCameraLibReturnTypes
//
// Caused by: java.lang.ClassNotFoundException: Didn't find class "com.lyrebirdstudio.imagecameralib.utils.ImageCameraLibReturnTypes" on path: DexPathList[[zip file "/data/app/eu.faircode.email-b4dvFM1MrZ5iBeNXAXRJhQ==/base.apk"],nativeLibraryDirectories=[/data/app/eu.faircode.email-b4dvFM1MrZ5iBeNXAXRJhQ==/lib/arm, /data/app/eu.faircode.email-b4dvFM1MrZ5iBeNXAXRJhQ==/base.apk!/lib/armeabi-v7a, /system/lib, /system/vendor/lib]]
Log.e(ex);
}
return result;
}
static void logMemory(Context context, String message) {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
ActivityManager activityManager = Helper.getSystemService(context, ActivityManager.class);
activityManager.getMemoryInfo(mi);
int mb = Math.round(mi.availMem / 0x100000L);
int perc = Math.round(mi.availMem / (float) mi.totalMem * 100.0f);
Log.i(message + " " + mb + " MB" + " " + perc + " %");
}
static boolean isOwnFault(Throwable ex) {
if (!Helper.isSupportedDevice())
return false;
if (ex instanceof OutOfMemoryError ||
ex.getCause() instanceof OutOfMemoryError)
return false;
if (ex instanceof RemoteException)
return false;
if (ex instanceof UnsatisfiedLinkError ||
ex.getCause() instanceof UnsatisfiedLinkError)
/*
java.lang.UnsatisfiedLinkError: dlopen failed: couldn't map "/mnt/asec/eu.faircode.email-1/base.apk!/lib/arm64-v8a/libsqlite3x.so" segment 0: Permission denied
at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
at java.lang.System.loadLibrary(System.java:1657)
at io.requery.android.database.sqlite.SQLiteDatabase.<clinit>(SourceFile:91)
*/
return false;
if (ex instanceof InternalError &&
"Thread starting during runtime shutdown".equals(ex.getMessage()))
/*
java.lang.InternalError: Thread starting during runtime shutdown
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:1063)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:921)
at java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:989)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1131)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
*/
return false;
if ("android.app.RemoteServiceException".equals(ex.getClass().getName()))
/*
android.app.RemoteServiceException: Bad notification for startForeground: java.util.ConcurrentModificationException
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2204)
*/
return false;
if ("android.app.RemoteServiceException$CannotDeliverBroadcastException".equals(ex.getClass().getName()))
/*
android.app.RemoteServiceException$CannotDeliverBroadcastException: can't deliver broadcast
at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2180)
at android.app.ActivityThread.access$3000(ActivityThread.java:324)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2435)
at android.os.Handler.dispatchMessage(Handler.java:106)
*/
return false;
if ("android.app.RemoteServiceException$CannotPostForegroundServiceNotificationException".equals(ex.getClass().getName()))
/*
android.app.RemoteServiceException$CannotPostForegroundServiceNotificationException: Bad notification for startForeground
at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2219)
at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2505)
at android.os.Handler.dispatchMessage(Handler.java:106)
*/
return false;
if ("android.view.WindowManager$BadTokenException".equals(ex.getClass().getName()))
/*
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@e9084db is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:827)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4084)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1976)
*/
return false;
if (ex instanceof NoSuchMethodError)
/*
java.lang.NoSuchMethodError: No direct method ()V in class Landroid/security/IKeyChainService$Stub; or its super classes (declaration of 'android.security.IKeyChainService$Stub' appears in /system/framework/framework.jar!classes2.dex)
at com.android.keychain.KeyChainService$1.(KeyChainService.java:95)
at com.android.keychain.KeyChainService.(KeyChainService.java:95)
at java.lang.Class.newInstance(Native Method)
at android.app.AppComponentFactory.instantiateService(AppComponentFactory.java:103)
*/
return false;
if (ex instanceof IllegalStateException &&
"Drag shadow dimensions must be positive".equals(ex.getMessage()))
/*
Android 9 only
java.lang.IllegalStateException: Drag shadow dimensions must be positive
java.lang.IllegalStateException: Drag shadow dimensions must be positive
at android.view.View.startDragAndDrop(View.java:24027)
at android.widget.Editor.startDragAndDrop(Editor.java:1165)
at android.widget.Editor.performLongClick(Editor.java:1191)
at android.widget.TextView.performLongClick(TextView.java:11346)
at android.view.View.performLongClick(View.java:6653)
at android.view.View$CheckForLongPress.run(View.java:25855)
at android.os.Handler.handleCallback(Handler.java:873)
*/
return false;
if (ex instanceof IllegalStateException &&
"Results have already been set".equals(ex.getMessage()))
/*
Play billing?
java.lang.IllegalStateException: Results have already been set
at Gu.a(Unknown:8)
at Fq.a(Unknown:29)
at Fk.b(Unknown:17)
at Fk.a(Unknown:12)
at Fk.b(Unknown:5)
at Ex.a(Unknown:3)
at Ep.b(Unknown:9)
at Ep.a(Unknown:76)
at Ep.a(Unknown:16)
at GH.a(Unknown:2)
at Gz.a(Unknown:48)
at GC.handleMessage(Unknown:6)
at android.os.Handler.dispatchMessage(Handler.java:108)
at android.os.Looper.loop(Looper.java:166)
at android.os.HandlerThread.run(HandlerThread.java:65)
*/
return false;
if (ex instanceof IllegalArgumentException &&
ex.getCause() instanceof RemoteException)
/*
java.lang.IllegalArgumentException
at android.os.Parcel.createException(Parcel.java:1954)
at android.os.Parcel.readException(Parcel.java:1918)
at android.os.Parcel.readException(Parcel.java:1868)
at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:826)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:758)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3906)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: android.os.RemoteException: Remote stack trace:
at android.view.SurfaceControl.nativeCreate(Native Method)
at android.view.SurfaceControl.<init>(SurfaceControl.java:630)
at android.view.SurfaceControl.<init>(SurfaceControl.java:60)
at android.view.SurfaceControl$Builder.build(SurfaceControl.java:386)
at com.android.server.wm.WindowContainer.onParentSet(WindowContainer.java:184)
*/
return false;
if (ex instanceof IllegalArgumentException &&
ex.getMessage() != null &&
ex.getMessage().startsWith("Tmp detached view should be removed from RecyclerView before it can be recycled"))
/*
Android 9 only?
java.lang.IllegalArgumentException: Tmp detached view should be removed from RecyclerView before it can be recycled: ViewHolder{e3b70bd position=0 id=1, oldPos=-1, pLpos:-1 update tmpDetached no parent} androidx.recyclerview.widget.RecyclerView{f0fe5b1 VFED..... ......ID 0,0-641,456 #7f090293 app:id/rvAccount}, adapter:eu.faircode.email.AdapterNavAccount@9a6ea96, layout:androidx.recyclerview.widget.LinearLayoutManager@d8fc617, context:eu.faircode.email.ActivityView@82a6ec4
at androidx.recyclerview.widget.RecyclerView$Recycler.recycleViewHolderInternal(SourceFile:6435)
at androidx.recyclerview.widget.RecyclerView.removeAnimatingView(SourceFile:1456)
at androidx.recyclerview.widget.RecyclerView$ItemAnimatorRestoreListener.onAnimationFinished(SourceFile:12690)
at androidx.recyclerview.widget.RecyclerView$ItemAnimator.dispatchAnimationFinished(SourceFile:13190)
at androidx.recyclerview.widget.SimpleItemAnimator.dispatchChangeFinished(SourceFile:317)
at androidx.recyclerview.widget.DefaultItemAnimator$8.onAnimationEnd(SourceFile:391)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1122)
*/
return false;
if (ex instanceof IllegalStateException &&
ex.getMessage() != null &&
ex.getMessage().startsWith("Layout state should be one of"))
/*
Practically, this means OOM
java.lang.IllegalStateException: Layout state should be one of 100 but it is 10
at androidx.recyclerview.widget.RecyclerView$State.assertLayoutStep(SourceFile:44)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(SourceFile:4)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(SourceFile:125)
at androidx.recyclerview.widget.RecyclerView.consumePendingUpdateOperations(SourceFile:107)
at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(SourceFile:19)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1105)
at android.view.Choreographer.doCallbacks(Choreographer.java:896)
at android.view.Choreographer.doFrame(Choreographer.java:810)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1090)
*/
return false;
if (ex instanceof IllegalArgumentException &&
"page introduces incorrect tiling".equals(ex.getMessage()))
/*
java.lang.IllegalArgumentException: page introduces incorrect tiling
at androidx.paging.PagedStorage.insertPage(SourceFile:545)
at androidx.paging.PagedStorage.tryInsertPageAndTrim(SourceFile:504)
at androidx.paging.TiledPagedList$1.onPageResult(SourceFile:60)
at androidx.paging.DataSource$LoadCallbackHelper$1.run(SourceFile:324)
at android.os.Handler.handleCallback(Handler.java:789)
*/
return false;
if (ex instanceof IllegalArgumentException &&
"Can't interpolate between two incompatible pathData".equals(ex.getMessage()))
/*
java.lang.IllegalArgumentException: Can't interpolate between two incompatible pathData
at android.animation.AnimatorInflater$PathDataEvaluator.evaluate(AnimatorInflater.java:265)
at android.animation.AnimatorInflater$PathDataEvaluator.evaluate(AnimatorInflater.java:262)
at android.animation.KeyframeSet.getValue(KeyframeSet.java:210)
at android.animation.PropertyValuesHolder.calculateValue(PropertyValuesHolder.java:1018)
at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1341)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:986)
at android.animation.ValueAnimator.animateBasedOnTime(ValueAnimator.java:1258)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1306)
at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
at android.animation.AnimationHandler.-wrap2(AnimationHandler.java)
at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:925)
at android.view.Choreographer.doCallbacks(Choreographer.java:702)
at android.view.Choreographer.doFrame(Choreographer.java:635)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:913)
at android.os.Handler.handleCallback(Handler.java:751)
*/
return false;
if (ex instanceof IllegalMonitorStateException)
/*
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1959)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1142)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:849)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1092)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
*/
return false;
if (ex instanceof RuntimeException &&
ex.getCause() instanceof TransactionTooLargeException)
// Some Android versions (Samsung) send images as clip data
return false;
if (ex instanceof RuntimeException &&
ex.getMessage() != null &&
(ex.getMessage().startsWith("Could not get application info") ||
ex.getMessage().startsWith("Unable to create service") ||
ex.getMessage().startsWith("Unable to start service") ||
ex.getMessage().startsWith("Unable to resume activity") ||
ex.getMessage().startsWith("Failure delivering result")))
return false;
/*
java.lang.RuntimeException: Unable to unbind to service androidx.work.impl.background.systemjob.SystemJobService@291a412 with Intent { cmp=eu.faircode.email/androidx.work.impl.background.systemjob.SystemJobService }: java.lang.RuntimeException: android.os.DeadSystemException
at android.app.ActivityThread.handleUnbindService(ActivityThread.java:4352)
java.lang.RuntimeException: Could not get application info.
 at CH0.a(PG:11)
 at org.chromium.content.browser.ChildProcessLauncherHelperImpl.a(PG:34)
 at Fn2.run(PG:5)
 at android.os.Handler.handleCallback(Handler.java:874)
 at android.os.Handler.dispatchMessage(Handler.java:100)
 at android.os.Looper.loop(Looper.java:198)
 at android.os.HandlerThread.run(HandlerThread.java:65)
java.lang.RuntimeException: Unable to create service eu.faircode.email.ServiceSynchronize: java.lang.NullPointerException: Attempt to invoke interface method 'java.util.List android.os.IUserManager.getProfiles(int, boolean)' on a null object reference
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2739)
java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:autoFillAuth:, request=2162688, result=-1, data=Intent { (has extras) }} to activity {eu.faircode.email/eu.faircode.email.ActivitySetup}: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference
at android.app.ActivityThread.deliverResults(ActivityThread.java:4469)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4511)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1821)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6874)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:861)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference
at android.os.Parcel.createException(Parcel.java:1956)
at android.os.Parcel.readException(Parcel.java:1918)
at android.os.Parcel.readException(Parcel.java:1868)
at android.view.autofill.IAutoFillManager$Stub$Proxy.setAuthenticationResult(IAutoFillManager.java:729)
at android.view.autofill.AutofillManager.onAuthenticationResult(AutofillManager.java:1474)
at android.app.Activity.dispatchActivityResult(Activity.java:7497)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4462)
... 11 more
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.autofill.Session.setAuthenticationResultLocked(Session.java:1005)
at com.android.server.autofill.AutofillManagerServiceImpl.setAuthenticationResultLocked(AutofillManagerServiceImpl.java:325)
at com.android.server.autofill.AutofillManagerService$AutoFillManagerServiceStub.setAuthenticationResult(AutofillManagerService.java:863)
at android.view.autofill.IAutoFillManager$Stub.onTransact(IAutoFillManager.java:289)
at android.os.Binder.execTransact(Binder.java:731)
*/
if (ex instanceof RuntimeException &&
"InputChannel is not initialized.".equals(ex.getMessage()))
return false;
/*
java.lang.RuntimeException: InputChannel is not initialized.
at android.view.InputEventReceiver.nativeInit(Native Method)
at android.view.InputEventReceiver.<init>(InputEventReceiver.java:72)
at android.view.ViewRootImpl$WindowInputEventReceiver.<init>(ViewRootImpl.java:7612)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:957)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)
at android.widget.Toast$TN.handleShow(Toast.java:514)
at android.widget.Toast$TN$1.handleMessage(Toast.java:417)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7397)
*/
if (ex.getMessage() != null &&
(ex.getMessage().startsWith("Bad notification posted") ||
ex.getMessage().contains("ActivityRecord not found") ||
ex.getMessage().startsWith("Unable to create layer") ||
ex.getMessage().startsWith("Illegal meta data value") ||
ex.getMessage().startsWith("Context.startForegroundService") ||
ex.getMessage().startsWith("PARAGRAPH span must start at paragraph boundary")))
return false;
if (ex instanceof TimeoutException &&
ex.getMessage() != null &&
ex.getMessage().contains("finalize"))
return false;
if ("android.database.CursorWindowAllocationException".equals(ex.getClass().getName()))
/*
android.database.CursorWindowAllocationException: Could not allocate CursorWindow '/data/user/0/eu.faircode.email/no_backup/androidx.work.workdb' of size 2097152 due to error -12.
at android.database.CursorWindow.nativeCreate(Native Method)
at android.database.CursorWindow.<init>(CursorWindow.java:139)
at android.database.CursorWindow.<init>(CursorWindow.java:120)
at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:202)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:140)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:232)
at android.database.AbstractCursor.moveToNext(AbstractCursor.java:281)
at androidx.room.InvalidationTracker$1.checkUpdatedTable(SourceFile:417)
at androidx.room.InvalidationTracker$1.run(SourceFile:388)
at androidx.work.impl.utils.SerialExecutor$Task.run(SourceFile:91)
*/
return false;
if (ex instanceof RuntimeException &&
ex.getCause() != null &&
"android.database.CursorWindowAllocationException".equals(ex.getCause().getClass().getName()))
/*
java.lang.RuntimeException: Exception while computing database live data.
at androidx.room.RoomTrackingLiveData$1.run(SourceFile:10)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Caused by: io.requery.android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed.
at io.requery.android.database.CursorWindow.<init>(SourceFile:7)
at io.requery.android.database.CursorWindow.<init>(SourceFile:1)
at io.requery.android.database.AbstractWindowedCursor.clearOrCreateWindow(SourceFile:2)
at io.requery.android.database.sqlite.SQLiteCursor.fillWindow(SourceFile:1)
at io.requery.android.database.sqlite.SQLiteCursor.getCount(SourceFile:2)
at eu.faircode.email.DaoAttachment_Impl$14.call(SourceFile:16)
at eu.faircode.email.DaoAttachment_Impl$14.call(SourceFile:1)
at androidx.room.RoomTrackingLiveData$1.run(SourceFile:7)
*/
return false;
if (ex instanceof SQLiteFullException) // database or disk is full (code 13 SQLITE_FULL)
return false;
if ("android.util.SuperNotCalledException".equals(ex.getClass().getName()))
/*
android.util.SuperNotCalledException: Activity {eu.faircode.email/eu.faircode.email.ActivityView} did not call through to super.onResume()
at android.app.Activity.performResume(Activity.java:7304)
at android.app.ActivityThread.performNewIntents(ActivityThread.java:3165)
at android.app.ActivityThread.handleNewIntent(ActivityThread.java:3180)
at android.app.servertransaction.NewIntentItem.execute(NewIntentItem.java:49)
*/
return false;
if ("android.view.WindowManager$InvalidDisplayException".equals(ex.getClass().getName()))
/*
android.view.WindowManager$InvalidDisplayException: Unable to add window android.view.ViewRootImpl$W@d7b5a0b -- the specified display can not be found
at android.view.ViewRootImpl.setView(ViewRootImpl.java:854)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:1492)
at android.widget.PopupWindow.showAsDropDown(PopupWindow.java:1342)
at androidx.appcompat.widget.AppCompatPopupWindow.showAsDropDown(SourceFile:77)
at androidx.core.widget.PopupWindowCompat.showAsDropDown(SourceFile:69)
at androidx.appcompat.widget.ListPopupWindow.show(SourceFile:754)
at androidx.appcompat.view.menu.CascadingMenuPopup.showMenu(SourceFile:486)
at androidx.appcompat.view.menu.CascadingMenuPopup.show(SourceFile:265)
at androidx.appcompat.view.menu.MenuPopupHelper.showPopup(SourceFile:290)
at androidx.appcompat.view.menu.MenuPopupHelper.tryShow(SourceFile:177)
at androidx.appcompat.widget.ActionMenuPresenter$OpenOverflowRunnable.run(SourceFile:792)
*/
return false;
StackTraceElement[] stack = ex.getStackTrace();
if (ex instanceof IndexOutOfBoundsException &&
stack.length > 0 &&
"android.text.TextLine".equals(stack[0].getClassName()) &&
"measure".equals(stack[0].getMethodName()))
/*
java.lang.IndexOutOfBoundsException: offset(21) should be less than line limit(20)
at android.text.TextLine.measure(Unknown Source:233)
at android.text.Layout.getHorizontal(Unknown Source:104)
at android.text.Layout.getHorizontal(Unknown Source:4)
at android.text.Layout.getPrimaryHorizontal(Unknown Source:4)
at android.text.Layout.getPrimaryHorizontal(Unknown Source:1)
at android.widget.Editor$ActionPinnedPopupWindow.computeLocalPosition(Unknown Source:275)
at android.widget.Editor$PinnedPopupWindow.show(Unknown Source:15)
at android.widget.Editor$ActionPinnedPopupWindow.show(Unknown Source:3)
at android.widget.Editor$EmailAddPopupWindow.show(Unknown Source:92)
at android.widget.Editor$1.run(Unknown Source:6)
at android.os.Handler.handleCallback(Unknown Source:2)
*/
return false;
if (ex instanceof IllegalArgumentException &&
stack.length > 0 &&
"android.os.Parcel".equals(stack[0].getClassName()) &&
("createException".equals(stack[0].getMethodName()) ||
"readException".equals(stack[0].getMethodName())))
/*
java.lang.IllegalArgumentException
at android.os.Parcel.createException(Parcel.java:1954)
at android.os.Parcel.readException(Parcel.java:1918)
at android.os.Parcel.readException(Parcel.java:1868)
at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:826)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:758)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3906)
java.lang.NullPointerException: Attempt to invoke virtual method 'int com.android.server.job.controllers.JobStatus.getUid()' on a null object reference
at android.os.Parcel.readException(Parcel.java:1605)
at android.os.Parcel.readException(Parcel.java:1552)
at android.app.job.IJobCallback$Stub$Proxy.jobFinished(IJobCallback.java:167)
at android.app.job.JobService$JobHandler.handleMessage(JobService.java:147)
at android.os.Handler.dispatchMessage(Handler.java:102)
*/
return false;
if (ex instanceof NullPointerException &&
stack.length > 0 &&
"android.hardware.biometrics.BiometricPrompt".equals(stack[0].getClassName()))
/*
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.hardware.fingerprint.FingerprintManager.getErrorString(int, int)' on a null object reference
at android.hardware.biometrics.BiometricPrompt.lambda$sendError$0(BiometricPrompt.java:490)
at android.hardware.biometrics.-$$Lambda$BiometricPrompt$HqBGXtBUWNc-v8NoHYsj2gLfaRw.run(Unknown Source:6)
at android.os.Handler.handleCallback(Handler.java:873)
*/
return false;
if (ex instanceof NullPointerException &&
stack.length > 0 &&
"android.graphics.Rect".equals(stack[0].getClassName()) &&
"set".equals(stack[0].getMethodName()))
/*
java.lang.NullPointerException: Attempt to read from field 'int android.graphics.Rect.left' on a null object reference
at android.graphics.Rect.set(Rect.java:371)
at android.view.InsetsState.readFromParcel(InsetsState.java:453)
at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:1264)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:865)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4297)
*/
return false;
if (ex instanceof NullPointerException &&
stack.length > 0 &&
"android.app.ActivityThread".equals(stack[0].getClassName()) &&
"handleStopActivity".equals(stack[0].getMethodName()))
/*
Android: 6.0.1
java.lang.NullPointerException: Attempt to read from field 'android.app.Activity android.app.ActivityThread$ActivityClientRecord.activity' on a null object reference
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4766)
at android.app.ActivityThread.access$1400(ActivityThread.java:221)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1823)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7224)
*/
return false;
if (ex instanceof NullPointerException &&
ex.getCause() instanceof RemoteException)
/*
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean com.android.server.autofill.RemoteFillService$PendingRequest.cancel()' on a null object reference
at android.os.Parcel.createException(Parcel.java:1956)
at android.os.Parcel.readException(Parcel.java:1918)
at android.os.Parcel.readException(Parcel.java:1868)
at android.app.IActivityManager$Stub$Proxy.reportAssistContextExtras(IActivityManager.java:7079)
at android.app.ActivityThread.handleRequestAssistContextExtras(ActivityThread.java:3338)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1839)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6971)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:865)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.autofill.RemoteFillService.cancelCurrentRequest(RemoteFillService.java:177)
at com.android.server.autofill.Session.cancelCurrentRequestLocked(Session.java:465)
at com.android.server.autofill.Session.access$1000(Session.java:118)
at com.android.server.autofill.Session$1.onHandleAssistData(Session.java:322)
at com.android.server.am.ActivityManagerService.reportAssistContextExtras(ActivityManagerService.java:14510)
*/
return false;
if (ex instanceof IndexOutOfBoundsException &&
stack.length > 0 &&
"android.text.SpannableStringInternal".equals(stack[0].getClassName()) &&
"checkRange".equals(stack[0].getMethodName()))
/*
java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0
at android.text.SpannableStringInternal.checkRange(SpannableStringInternal.java:478)
at android.text.SpannableStringInternal.setSpan(SpannableStringInternal.java:189)
at android.text.SpannableStringInternal.setSpan(SpannableStringInternal.java:178)
at android.text.SpannableString.setSpan(SpannableString.java:60)
at android.text.Selection.setSelection(Selection.java:93)
at android.text.Selection.setSelection(Selection.java:77)
at android.widget.Editor$SelectionHandleView.updateSelection(Editor.java:5281)
at android.widget.Editor$HandleView.positionAtCursorOffset(Editor.java:4676)
at android.widget.Editor$SelectionHandleView.positionAtCursorOffset(Editor.java:5466)
at android.widget.Editor$SelectionHandleView.positionAndAdjustForCrossingHandles(Editor.java:5528)
at android.widget.Editor$SelectionHandleView.updatePosition(Editor.java:5458)
at android.widget.Editor$HandleView.onTouchEvent(Editor.java:4989)
at android.widget.Editor$SelectionHandleView.onTouchEvent(Editor.java:5472)
at android.view.View.dispatchTouchEvent(View.java:12545)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2741)
at android.widget.PopupWindow$PopupDecorView.dispatchTouchEvent(PopupWindow.java:2407)
at android.view.View.dispatchPointerEvent(View.java:12789)
*/
return false;
if (ex instanceof IndexOutOfBoundsException) {
for (StackTraceElement ste : stack)
if ("android.widget.NumberPicker$SetSelectionCommand".equals(ste.getClassName()) &&
"run".equals(ste.getMethodName()))
return false;
/*
java.lang.IndexOutOfBoundsException: setSpan (2 ... 2) ends beyond length 0
at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1265)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:684)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:677)
at android.text.Selection.setSelection(Selection.java:76)
at android.widget.TextView.semSetSelection(TextView.java:11550)
at android.widget.EditText.setSelection(EditText.java:118)
at android.widget.NumberPicker$SetSelectionCommand.run(NumberPicker.java:2246)
at android.os.Handler.handleCallback(Handler.java:751)
*/
}
if (ex instanceof IndexOutOfBoundsException) {
for (StackTraceElement ste : stack)
if ("android.graphics.Paint".equals(ste.getClassName()) &&
"getTextRunCursor".equals(ste.getMethodName()))
return false;
/*
Android 6.0.1
java.lang.IndexOutOfBoundsException
at android.graphics.Paint.getTextRunCursor(Paint.java:2160)
at android.graphics.Paint.getTextRunCursor(Paint.java:2112)
at android.widget.Editor.getNextCursorOffset(Editor.java:924)
at android.widget.Editor.access$4700(Editor.java:126)
at android.widget.Editor$SelectionEndHandleView.positionAndAdjustForCrossingHandles(Editor.java:4708)
at android.widget.Editor$SelectionEndHandleView.updatePosition(Editor.java:4692)
at android.widget.Editor$HandleView.onTouchEvent(Editor.java:4012)
at android.widget.Editor$SelectionEndHandleView.onTouchEvent(Editor.java:4726)
at android.view.View.dispatchTouchEvent(View.java:9377)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2554)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2255)
at android.widget.PopupWindow$PopupDecorView.dispatchTouchEvent(PopupWindow.java:2015)
at android.view.View.dispatchPointerEvent(View.java:9597)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4234)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4100)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3646)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3699)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3665)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3791)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3673)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3848)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3646)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3699)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3665)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3673)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3646)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5926)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5900)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5861)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6029)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:176)
at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:6000)
at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:6052)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:600)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:739)
*/
}
if (ex instanceof StringIndexOutOfBoundsException) {
for (StackTraceElement ste : stack)
if ("android.widget.Editor$SuggestionsPopupWindow".equals(ste.getClassName()) &&
"highlightTextDifferences".equals(ste.getMethodName()))
return false;
/*
Android 7.0 Samsung
java.lang.StringIndexOutOfBoundsException: length=175; regionStart=174; regionLength=7
at java.lang.String.substring(String.java:1931)
at android.widget.Editor$SuggestionsPopupWindow.highlightTextDifferences(Editor.java:4002)
at android.widget.Editor$SuggestionsPopupWindow.updateSuggestions(Editor.java:3933)
at android.widget.Editor$SuggestionsPopupWindow.show(Editor.java:3836)
at android.widget.Editor.replace(Editor.java:428)
at android.widget.Editor$3.run(Editor.java:2362)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6780)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1500)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1390)
*/
}
if (ex instanceof IllegalArgumentException &&
stack.length > 0 &&
"android.text.method.WordIterator".equals(stack[0].getClassName()) &&
"checkOffsetIsValid".equals(stack[0].getMethodName()))
/*
https://issuetracker.google.com/issues/37068143
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/marshmallow-release/core/java/android/text/method/WordIterator.java
java.lang.IllegalArgumentException: Invalid offset: -1. Valid range is [0, 1673]
at android.text.method.WordIterator.checkOffsetIsValid(WordIterator.java:380)
at android.text.method.WordIterator.isBoundary(WordIterator.java:101)
at android.widget.Editor$SelectionStartHandleView.positionAtCursorOffset(Editor.java:4287)
at android.widget.Editor$HandleView.updatePosition(Editor.java:3735)
at android.widget.Editor$PositionListener.onPreDraw(Editor.java:2512)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:944)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2412)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1321)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6763)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:894)
at android.view.Choreographer.doCallbacks(Choreographer.java:696)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:880)
at android.os.Handler.handleCallback(Handler.java:815)
*/
return false;
if (ex instanceof IllegalArgumentException && ex.getCause() != null) {
for (StackTraceElement ste : ex.getCause().getStackTrace())
if ("android.view.textclassifier.TextClassifierImpl".equals(ste.getClassName()) &&
"validateInput".equals(ste.getMethodName()))
return true;
/*
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:353)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
at java.util.concurrent.FutureTask.run(FutureTask.java:271)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.IllegalArgumentException
at com.android.internal.util.Preconditions.checkArgument(Preconditions.java:33)
at android.view.textclassifier.TextClassifierImpl.validateInput(TextClassifierImpl.java:484)
at android.view.textclassifier.TextClassifierImpl.classifyText(TextClassifierImpl.java:144)
at android.widget.SelectionActionModeHelper$TextClassificationHelper.classifyText(SelectionActionModeHelper.java:465)
at android.widget.SelectionActionModeHelper.-android_widget_SelectionActionModeHelper-mthref-1(SelectionActionModeHelper.java:83)
at android.widget.-$Lambda$tTszxdFZ0V9nXhnBpPsqeBMO0fw$5.$m$0(Unknown:4)
at android.widget.-$Lambda$tTszxdFZ0V9nXhnBpPsqeBMO0fw$5.get(Unknown)
at android.widget.SelectionActionModeHelper$TextClassificationAsyncTask.doInBackground(SelectionActionModeHelper.java:366)
at android.widget.SelectionActionModeHelper$TextClassificationAsyncTask.doInBackground(SelectionActionModeHelper.java:361)
at android.os.AsyncTask$2.call(AsyncTask.java:333)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
*/
}
if (ex instanceof NullPointerException &&
stack.length > 0 &&
"view.AccessibilityInteractionController".equals(stack[0].getClassName()) &&
"applyAppScaleAndMagnificationSpecIfNeeded".equals(stack[0].getMethodName()))
/*
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.RectF.scale(float)' on a null object reference
at android.view.AccessibilityInteractionController.applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityInteractionController.java:872)
at android.view.AccessibilityInteractionController.applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityInteractionController.java:796)
at android.view.AccessibilityInteractionController.updateInfosForViewportAndReturnFindNodeResult(AccessibilityInteractionController.java:924)
at android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread(AccessibilityInteractionController.java:345)
at android.view.AccessibilityInteractionController.access$400(AccessibilityInteractionController.java:75)
at android.view.AccessibilityInteractionController$PrivateHandler.handleMessage(AccessibilityInteractionController.java:1393)
at android.os.Handler.dispatchMessage(Handler.java:107)
*/
return false;
if (ex instanceof NullPointerException) {
for (StackTraceElement ste : stack)
if ("android.app.job.IJobCallback$Stub$Proxy".equals(ste.getClassName()) &&
"jobFinished".equals(ste.getMethodName()))
return false;
/*
java.lang.NullPointerException: Attempt to invoke virtual method 'int com.android.server.job.controllers.JobStatus.getUid()' on a null object reference
at android.os.Parcel.readException(Parcel.java:1605)
at android.os.Parcel.readException(Parcel.java:1552)
at android.app.job.IJobCallback$Stub$Proxy.jobFinished(IJobCallback.java:167)
at android.app.job.JobService$JobHandler.handleMessage(JobService.java:147)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5697)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:905)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:766)
*/
}
if (ex instanceof IllegalStateException &&
stack.length > 0 &&
"android.database.sqlite.SQLiteSession".equals(stack[0].getClassName()) &&
"throwIfNoTransaction".equals(stack[0].getMethodName()))
/*
java.lang.IllegalStateException: Cannot perform this operation because there is no current transaction.
at android.database.sqlite.SQLiteSession.throwIfNoTransaction(SQLiteSession.java:917)
at android.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:400)
at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:585)
at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.endTransaction(SourceFile:1)
at androidx.room.RoomDatabase.endTransaction(SourceFile:1)
at androidx.work.impl.WorkerWrapper.runWorker(SourceFile:66)
at androidx.work.impl.WorkerWrapper.run(SourceFile:3)
at androidx.work.impl.utils.SerialExecutor$Task.run(SourceFile:1)
*/
return false;
if (ex instanceof IllegalArgumentException &&
stack.length > 0 &&
"android.widget.SmartSelectSprite".equals(stack[0].getClassName()) &&
"startAnimation".equals(stack[0].getMethodName()))
/*
java.lang.IllegalArgumentException: Center point is not inside any of the rectangles!
at android.widget.SmartSelectSprite.startAnimation(SmartSelectSprite.java:392)
at android.widget.SelectionActionModeHelper.startSelectionActionModeWithSmartSelectAnimation(SelectionActionModeHelper.java:319)
at android.widget.SelectionActionModeHelper.lambda$l1f1_V5lw6noQxI_3u11qF753Iw(Unknown Source:0)
at android.widget.-$$Lambda$SelectionActionModeHelper$l1f1_V5lw6noQxI_3u11qF753Iw.accept(Unknown Source:4)
at android.widget.SelectionActionModeHelper$TextClassificationAsyncTask.onPostExecute(SelectionActionModeHelper.java:910)
at android.widget.SelectionActionModeHelper$TextClassificationAsyncTask.onPostExecute(SelectionActionModeHelper.java:864)
at android.os.AsyncTask.finish(AsyncTask.java:695)
at android.os.AsyncTask.access$600(AsyncTask.java:180)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
at android.os.Handler.dispatchMessage(Handler.java:106)
*/
return false;
if (ex instanceof InflateException)
/*
android.view.InflateException: Binary XML file line #7: Binary XML file line #7: Error inflating class <unknown>
Caused by: android.view.InflateException: Binary XML file line #7: Error inflating class <unknown>
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at android.view.LayoutInflater.createView(LayoutInflater.java:686)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:829)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:769)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:902)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:863)
at android.view.LayoutInflater.inflate(LayoutInflater.java:554)
at android.view.LayoutInflater.inflate(LayoutInflater.java:461)
*/
return false;
for (StackTraceElement ste : stack) {
String clazz = ste.getClassName();
if (clazz != null && clazz.startsWith("org.chromium."))
/*
android.content.res.Resources$NotFoundException:
at android.content.res.ResourcesImpl.getValue (ResourcesImpl.java:225)
at android.content.res.Resources.getInteger (Resources.java:1192)
at org.chromium.ui.base.DeviceFormFactor.a (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:105)
at y8.onCreateActionMode (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:744)
at px.onCreateActionMode (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:36)
at com.android.internal.policy.DecorView$ActionModeCallback2Wrapper.onCreateActionMode (DecorView.java:2722)
at com.android.internal.policy.DecorView.startActionMode (DecorView.java:926)
at com.android.internal.policy.DecorView.startActionModeForChild (DecorView.java:882)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.ViewGroup.startActionModeForChild (ViewGroup.java:1035)
at android.view.View.startActionMode (View.java:7654)
at org.chromium.content.browser.selection.SelectionPopupControllerImpl.B (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:31)
at uh0.a (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:1605)
at Kk0.i (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:259)
at B6.run (chromium-TrichromeWebViewGoogle6432.aab-stable-500512534:454)
at android.os.Handler.handleCallback (Handler.java:938)
*/
return false;
}
if (ex instanceof SecurityException &&
ex.getMessage() != null &&
ex.getMessage().contains("com.opera.browser"))
/*
java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.VIEW dat=https://tracking.dpd.de/... cmp=com.opera.browser/.leanplum.LeanplumCatchActivity (has extras) } from ProcessRecord{3d9efb1 6332:eu.faircode.email/u0a54} (pid=6332, uid=10054) not exported from uid 10113
at android.os.Parcel.readException(Parcel.java:1951)
at android.os.Parcel.readException(Parcel.java:1897)
at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:4430)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1610)
at android.app.ContextImpl.startActivity(ContextImpl.java:862)
at android.app.ContextImpl.startActivity(ContextImpl.java:839)
at android.view.textclassifier.TextClassification.lambda$-android_view_textclassifier_TextClassification_5020(TextClassification.java:166)
at android.view.textclassifier.-$Lambda$mxr44OLodDKdoE5ddAZvMdsFssQ.$m$0(Unknown Source:8)
at android.view.textclassifier.-$Lambda$mxr44OLodDKdoE5ddAZvMdsFssQ.onClick(Unknown Source:0)
at org.chromium.content.browser.selection.SelectionPopupControllerImpl.m(chromium-SystemWebViewGoogle.aab-stable-432415203:17)
at y5.onActionItemClicked(chromium-SystemWebViewGoogle.aab-stable-432415203:20)
at Bn.onActionItemClicked(chromium-SystemWebViewGoogle.aab-stable-432415203:1)
at com.android.internal.policy.DecorView$ActionModeCallback2Wrapper.onActionItemClicked(DecorView.java:2472)
at com.android.internal.view.FloatingActionMode$3.onMenuItemSelected(FloatingActionMode.java:101)
at com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:761)
at com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:167)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:908)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:898)
at com.android.internal.view.FloatingActionMode.lambda$-com_android_internal_view_FloatingActionMode_5176(FloatingActionMode.java:129)
at com.android.internal.view.-$Lambda$IoKM3AcgDw3Ok5aFi0zlym2p3IA.$m$0(Unknown Source:4)
at com.android.internal.view.-$Lambda$IoKM3AcgDw3Ok5aFi0zlym2p3IA.onMenuItemClick(Unknown Source:0)
at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup$2.onClick(FloatingToolbar.java:423)
at android.view.View.performClick(View.java:6320)
at android.view.View$PerformClick.run(View.java:25087)
*/
return false;
if (ex instanceof RuntimeException) {
for (StackTraceElement ste : stack)
if ("android.app.job.JobService$JobHandler".equals(ste.getClassName()) &&
"handleMessage".equals(ste.getMethodName()))
return false;
/*
java.lang.RuntimeException: java.lang.NullPointerException: Attempt to invoke virtual method 'int com.android.server.job.controllers.JobStatus.getUid()' on a null object reference
at android.app.job.JobService$JobHandler.handleMessage(JobService.java:139)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5546)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:682)
*/
}
if (stack.length > 0 &&
stack[0].getClassName() != null &&
stack[0].getClassName().startsWith("com.android.internal.widget.FloatingToolbar"))
/*
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.util.Size.getWidth()' on a null object reference
at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup$11.onMeasure(FloatingToolbar.java:1430)
at android.view.View.measure(View.java:25787)
at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup.measure(FloatingToolbar.java:1530)
at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup.layoutMainPanelItems(FloatingToolbar.java:1284)
at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup.layoutMenuItems(FloatingToolbar.java:554)
at com.android.internal.widget.FloatingToolbar.doShow(FloatingToolbar.java:283)
at com.android.internal.widget.FloatingToolbar.show(FloatingToolbar.java:221)
at com.android.internal.view.FloatingActionMode$FloatingToolbarVisibilityHelper.updateToolbarVisibility(FloatingActionMode.java:386)
at com.android.internal.view.FloatingActionMode$2.run(FloatingActionMode.java:75)
at android.os.Handler.handleCallback(Handler.java:938)
*/
return false;
if (isDead(ex))
return false;
if (BuildConfig.BETA_RELEASE)
return true;
while (ex != null) {
for (StackTraceElement ste : stack)
if (ste.getClassName().startsWith(BuildConfig.APPLICATION_ID))
return true;
ex = ex.getCause();
}
return false;
}
private static boolean isDead(Throwable ex) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
/*
java.lang.RuntimeException: Failure from system
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1327)
at android.app.ContextImpl.bindService(ContextImpl.java:1286)
at android.content.ContextWrapper.bindService(ContextWrapper.java:604)
at android.content.ContextWrapper.bindService(ContextWrapper.java:604)
at hq.run(PG:15)
at java.lang.Thread.run(Thread.java:818)
Caused by: android.os.DeadObjectException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:503)
at android.app.ActivityManagerProxy.bindService(ActivityManagerNative.java:3783)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1317)
*/
Throwable cause = ex;
while (cause != null) {
if (cause instanceof DeadObjectException)
return true;
cause = cause.getCause();
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Throwable cause = ex;
while (cause != null) {
if (cause instanceof DeadSystemException)
return true;
cause = cause.getCause();
}
}
return false;
}
static String formatThrowable(Throwable ex) {
return formatThrowable(ex, true);
}
static String formatThrowable(Throwable ex, boolean sanitize) {
return formatThrowable(ex, " ", sanitize);
}
static String formatThrowable(Throwable ex, String separator, boolean sanitize) {
if (ex == null)
return null;
if (sanitize) {
if (ex instanceof MessageRemovedException)
return null;
if (ex instanceof AuthenticationFailedException &&
ex.getCause() instanceof SocketException)
return null;
if (ex instanceof ProtocolException &&
ex.getCause() instanceof InterruptedException)
return null; // Interrupted waitIfIdle
if (ex instanceof MessagingException &&
("Not connected".equals(ex.getMessage()) || // POP3
"connection failure".equals(ex.getMessage()) ||
"failed to create new store connection".equals(ex.getMessage())))
return null;
if (ex instanceof MessagingException &&
ex.getCause() instanceof ConnectionException &&
ex.getCause().getMessage() != null &&
(ex.getCause().getMessage().contains("Read error") ||
ex.getCause().getMessage().contains("Write error") ||
ex.getCause().getMessage().contains("Unexpected end of ZLIB input stream") ||
ex.getCause().getMessage().contains("Socket is closed")))
return null;
// javax.mail.MessagingException: AU3 BAD User is authenticated but not connected.;
// nested exception is:
// com.sun.mail.iap.BadCommandException: AU3 BAD User is authenticated but not connected.
// javax.mail.MessagingException: AU3 BAD User is authenticated but not connected.;
// nested exception is:
// com.sun.mail.iap.BadCommandException: AU3 BAD User is authenticated but not connected.
// at com.sun.mail.imap.IMAPFolder.logoutAndThrow(SourceFile:1156)
// at com.sun.mail.imap.IMAPFolder.open(SourceFile:1063)
// at com.sun.mail.imap.IMAPFolder.open(SourceFile:977)
// at eu.faircode.email.ServiceSynchronize.monitorAccount(SourceFile:890)
// at eu.faircode.email.ServiceSynchronize.access$1500(SourceFile:85)
// at eu.faircode.email.ServiceSynchronize$7$1.run(SourceFile:627)
// at java.lang.Thread.run(Thread.java:764)
// Caused by: com.sun.mail.iap.BadCommandException: AU3 BAD User is authenticated but not connected.
// at com.sun.mail.iap.Protocol.handleResult(SourceFile:415)
// at com.sun.mail.imap.protocol.IMAPProtocol.select(SourceFile:1230)
// at com.sun.mail.imap.IMAPFolder.open(SourceFile:1034)
if (ex instanceof MessagingException &&
ex.getCause() instanceof BadCommandException &&
ex.getCause().getMessage() != null &&
ex.getCause().getMessage().contains("User is authenticated but not connected"))
return null;
//if (ex instanceof MessagingException &&
// ex.getMessage() != null &&
// ex.getMessage().startsWith("OAuth refresh"))
// return null;
if (ex instanceof IOException &&
ex.getCause() instanceof MessageRemovedException)
return null;
if (ex instanceof ConnectionException)
return null;
if (ex instanceof StoreClosedException ||
ex instanceof FolderClosedException ||
ex instanceof FolderClosedIOException ||
ex instanceof OperationCanceledException)
return null;
if (ex instanceof IllegalStateException &&
(TOKEN_REFRESH_REQUIRED.equals(ex.getMessage()) ||
"Not connected".equals(ex.getMessage()) ||
"This operation is not allowed on a closed folder".equals(ex.getMessage())))
return null;
}
if (ex instanceof MailConnectException &&
ex.getCause() instanceof SocketTimeoutException)
ex = new Throwable("No response received from email server", ex);
if (ex.getMessage() != null && ex.getMessage().contains("Read timed out"))
ex = new Throwable("No response received from email server", ex);
if (ex instanceof MessagingException &&
ex.getCause() instanceof UnknownHostException)
ex = new Throwable("Email server address lookup failed", ex);
StringBuilder sb = new StringBuilder();
if (BuildConfig.DEBUG)
sb.append(new ThrowableWrapper(ex).toSafeString());
else
sb.append(new ThrowableWrapper(ex).getSafeMessageOrName());
Throwable cause = ex.getCause();
while (cause != null) {
if (BuildConfig.DEBUG)
sb.append(separator).append(new ThrowableWrapper(cause).toSafeString());
else
sb.append(separator).append(new ThrowableWrapper(cause).getSafeMessageOrName());
cause = cause.getCause();
}
return sb.toString();
}
static void writeCrashLog(Context context, Throwable ex) {
File file = new File(context.getFilesDir(), "crash.log");
Log.w("Writing exception to " + file);
try (FileWriter out = new FileWriter(file, true)) {
out.write(BuildConfig.VERSION_NAME + BuildConfig.REVISION + " " + new Date() + "\r\n");
out.write(ex + "\r\n" + new ThrowableWrapper(ex).getSafeStackTraceString() + "\r\n");
} catch (IOException e) {
Log.e(e);
}
}
static EntityMessage getDebugInfo(Context context, String source, int title, Throwable ex, String log, Bundle args) throws IOException, JSONException {
StringBuilder sb = new StringBuilder();
sb.append(context.getString(title)).append("\n\n");
if (args != null) {
sb.append(args.getString("issue")).append('\n');
if (args.containsKey("account"))
sb.append('\n').append("Account: ").append(args.getString("account"));
if (args.containsKey("contact"))
sb.append('\n').append("Prior contact: ").append(args.getBoolean("contact"));
}
sb.append("\n\n");
sb.append(getAppInfo(context));
if (ex != null) {
ThrowableWrapper w = new ThrowableWrapper(ex);
sb.append(w.toSafeString()).append("\n").append(w.getSafeStackTraceString());
}
if (log != null)
sb.append(log);
String body = "<pre class=\"fairemail_debug_info\">" + TextUtils.htmlEncode(sb.toString()) + "</pre>";
EntityMessage draft;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<TupleIdentityEx> identities = db.identity().getComposableIdentities(null);
if (identities == null || identities.size() == 0)
throw new IllegalArgumentException(context.getString(R.string.title_no_composable));
EntityIdentity identity = identities.get(0);
EntityFolder drafts = db.folder().getFolderByType(identity.account, EntityFolder.DRAFTS);
if (drafts == null)
throw new IllegalArgumentException(context.getString(R.string.title_no_drafts));
draft = new EntityMessage();
draft.account = drafts.account;
draft.folder = drafts.id;
draft.identity = identity.id;
draft.msgid = EntityMessage.generateMessageId();
draft.thread = draft.msgid;
draft.from = new Address[]{new InternetAddress(identity.email, identity.name, StandardCharsets.UTF_8.name())};
draft.to = new Address[]{myAddress()};
draft.subject = context.getString(R.string.app_name) + " " + getVersionInfo(context) + " debug info - " + source;
draft.received = new Date().getTime();
draft.seen = true;
draft.ui_seen = true;
draft.id = db.message().insertMessage(draft);
File file = draft.getFile(context);
Helper.writeText(file, body); // TODO CASA
db.message().setMessageContent(draft.id, true, null, 0, null, null);
attachSettings(context, draft.id, 1);
attachAccounts(context, draft.id, 2);
attachNetworkInfo(context, draft.id, 3);
attachLog(context, draft.id, 4);
attachOperations(context, draft.id, 5);
attachTasks(context, draft.id, 6);
attachLogcat(context, draft.id, 7);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
attachNotificationInfo(context, draft.id, 8);
attachEnvironment(context, draft.id, 9);
//if (MessageClassifier.isEnabled(context))
// attachClassifierData(context, draft.id, 10);
EntityOperation.queue(context, draft, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "debuginfo");
return draft;
}
static void unexpectedError(Fragment fragment, Throwable ex) {
unexpectedError(fragment, ex, true);
}
static void unexpectedError(Fragment fragment, Throwable ex, boolean report) {
try {
unexpectedError(fragment.getParentFragmentManager(), ex, report);
} catch (Throwable exex) {
Log.w(exex);
/*
Exception java.lang.IllegalStateException:
at androidx.fragment.app.Fragment.getParentFragmentManager (Fragment.java:1107)
at eu.faircode.email.FragmentDialogForwardRaw.send (FragmentDialogForwardRaw.java:307)
at eu.faircode.email.FragmentDialogForwardRaw.access$200 (FragmentDialogForwardRaw.java:56)
at eu.faircode.email.FragmentDialogForwardRaw$4.onClick (FragmentDialogForwardRaw.java:239)
at androidx.appcompat.app.AlertController$ButtonHandler.handleMessage (AlertController.java:167)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loopOnce (Looper.java:210)
at android.os.Looper.loop (Looper.java:299)
at android.app.ActivityThread.main (ActivityThread.java:8168)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:556)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1037)
*/
}
}
static void unexpectedError(FragmentManager manager, Throwable ex) {
unexpectedError(manager, ex, true);
}
static void unexpectedError(FragmentManager manager, Throwable ex, boolean report) {
unexpectedError(manager, ex, report, null);
}
static void unexpectedError(FragmentManager manager, Throwable ex, int faq) {
unexpectedError(manager, ex, false, faq);
}
static void unexpectedError(FragmentManager manager, Throwable ex, boolean report, Integer faq) {
Log.e(ex);
if (ex instanceof OutOfMemoryError)
report = false;
Bundle args = new Bundle();
args.putSerializable("ex", ex);
args.putBoolean("report", report);
args.putInt("faq", faq == null ? 0 : faq);
FragmentDialogUnexpected fragment = new FragmentDialogUnexpected();
fragment.setArguments(args);
fragment.show(manager, "error:unexpected");
}
public static class FragmentDialogUnexpected extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
final Throwable ex = (Throwable) args.getSerializable("ex");
final boolean report = args.getBoolean("report", true);
final int faq = args.getInt("faq");
final Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View dview = inflater.inflate(R.layout.dialog_unexpected, null);
TextView tvError = dview.findViewById(R.id.tvError);
Button btnHelp = dview.findViewById(R.id.btnHelp);
String message = Log.formatThrowable(ex, false);
tvError.setText(message);
btnHelp.setVisibility(faq > 0 ? View.VISIBLE : View.GONE);
btnHelp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), faq);
}
});
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setView(dview)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.menu_faq, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = Helper.getSupportUri(context, "Unexpected:error");
if (!TextUtils.isEmpty(message))
uri = uri
.buildUpon()
.appendQueryParameter("message", "Unexpected: " + message)
.build();
Helper.view(context, uri, true);
}
});
if (report)
builder.setNeutralButton(R.string.title_report, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Dialog will be dismissed
final Context context = getContext();
new SimpleTask<Long>() {
@Override
protected Long onExecute(Context context, Bundle args) throws Throwable {
return Log.getDebugInfo(context, "report", R.string.title_unexpected_info_remark, ex, null, null).id;
}
@Override
protected void onExecuted(Bundle args, Long id) {
context.startActivity(new Intent(context, ActivityCompose.class)
.putExtra("action", "edit")
.putExtra("id", id));
}
@Override
protected void onException(Bundle args, Throwable ex) {
// Ignored
}
}.execute(getContext(), getActivity(), new Bundle(), "error:unexpected");
}
});
return builder.create();
}
}
private static String getVersionInfo(Context context) {
return String.format("%s%s/%d%s%s%s\r\n",
BuildConfig.VERSION_NAME,
BuildConfig.REVISION,
Helper.hasValidFingerprint(context) ? 1 : 3,
BuildConfig.PLAY_STORE_RELEASE ? "p" : "",
BuildConfig.DEBUG ? "d" : "",
ActivityBilling.isPro(context) ? "+" : "-");
}
private static StringBuilder getAppInfo(Context context) {
StringBuilder sb = new StringBuilder();
ContentResolver resolver = context.getContentResolver();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean main_log = prefs.getBoolean("main_log", true);
boolean protocol = prefs.getBoolean("protocol", false);
long last_cleanup = prefs.getLong("last_cleanup", 0);
PackageManager pm = context.getPackageManager();
// Get version info
sb.append(String.format("%s %s\r\n", context.getString(R.string.app_name), getVersionInfo(context)));
sb.append(String.format("Package: %s uid: %d\r\n",
BuildConfig.APPLICATION_ID, android.os.Process.myUid()));
sb.append(String.format("Android: %s (SDK device=%d target=%d)\r\n",
Build.VERSION.RELEASE, Build.VERSION.SDK_INT, Helper.getTargetSdk(context)));
String miui = Helper.getMIUIVersion();
Integer autostart = (miui == null ? null : Helper.getMIUIAutostart(context));
sb.append(String.format("MIUI: %s autostart: %s\r\n",
miui == null ? "-" : miui,
autostart == null ? "?" : Boolean.toString(autostart == 0)));
boolean reporting = prefs.getBoolean("crash_reports", false);
if (reporting || BuildConfig.TEST_RELEASE) {
String uuid = prefs.getString("uuid", null);
sb.append(String.format("UUID: %s\r\n", uuid == null ? "-" : uuid));
}
try {
ApplicationInfo app = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
String build_uuid = app.metaData.getString("com.bugsnag.android.BUILD_UUID");
sb.append(String.format("Build UUID: %s\r\n", build_uuid == null ? "-" : build_uuid));
} catch (PackageManager.NameNotFoundException ex) {
Log.e(ex);
}
String installer = Helper.getInstallerName(context);
sb.append(String.format("Release: %s\r\n", getReleaseType(context)));
sb.append(String.format("Play Store: %s\r\n", Helper.hasPlayStore(context)));
sb.append(String.format("Installer: %s\r\n", installer == null ? "-" : installer));
sb.append(String.format("Installed: %s\r\n", new Date(Helper.getInstallTime(context))));
sb.append(String.format("Updated: %s\r\n", new Date(Helper.getUpdateTime(context))));
sb.append(String.format("Last cleanup: %s\r\n", new Date(last_cleanup)));
sb.append(String.format("Now: %s\r\n", new Date()));
sb.append(String.format("Zone: %s\r\n", TimeZone.getDefault().getID()));
String language = prefs.getString("language", null);
sb.append(String.format("Locale: def=%s lang=%s\r\n",
Locale.getDefault(), language));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
sb.append(String.format("System: %s\r\n",
Resources.getSystem().getConfiguration().locale));
else {
LocaleList ll = Resources.getSystem().getConfiguration().getLocales();
for (int i = 0; i < ll.size(); i++)
sb.append(String.format("System: %s\r\n", ll.get(i)));
}
sb.append("\r\n");
String osVersion = null;
try {
osVersion = System.getProperty("os.version");
} catch (Throwable ex) {
Log.e(ex);
}
// Get device info
sb.append(String.format("Brand: %s\r\n", Build.BRAND));
sb.append(String.format("Manufacturer: %s\r\n", Build.MANUFACTURER));
sb.append(String.format("Model: %s\r\n", Build.MODEL));
sb.append(String.format("Product: %s\r\n", Build.PRODUCT));
sb.append(String.format("Device: %s\r\n", Build.DEVICE));
sb.append(String.format("Host: %s\r\n", Build.HOST));
sb.append(String.format("Time: %s\r\n", new Date(Build.TIME).toString()));
sb.append(String.format("Display: %s\r\n", Build.DISPLAY));
sb.append(String.format("Id: %s\r\n", Build.ID));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
sb.append(String.format("SoC: %s/%s\r\n", Build.SOC_MANUFACTURER, Build.SOC_MODEL));
sb.append(String.format("OS version: %s\r\n", osVersion));
sb.append("\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
// https://developer.android.com/reference/android/app/ApplicationExitInfo
boolean exits = false;
long from = new Date().getTime() - 30 * 24 * 3600 * 1000L;
ActivityManager am = Helper.getSystemService(context, ActivityManager.class);
List<ApplicationExitInfo> infos = am.getHistoricalProcessExitReasons(
context.getPackageName(), 0, 100);
for (ApplicationExitInfo info : infos)
if (info.getTimestamp() > from &&
info.getImportance() >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE) {
exits = true;
sb.append(String.format("%s: %s\r\n",
new Date(info.getTimestamp()),
Helper.getExitReason(info.getReason())));
}
if (!exits)
sb.append("No crashes\r\n");
sb.append("\r\n");
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
}
sb.append(String.format("Log main: %b protocol: %b build: %b test: %b\r\n",
main_log, protocol, BuildConfig.DEBUG, BuildConfig.TEST_RELEASE));
int[] contacts = ContactInfo.getStats();
sb.append(String.format("Contact lookup: %d cached: %d\r\n",
contacts[0], contacts[1]));
sb.append(String.format("Accessibility: %b\r\n", Helper.isAccessibilityEnabled(context)));
String charset = MimeUtility.getDefaultJavaCharset();
sb.append(String.format("Default charset: %s/%s\r\n", charset, MimeUtility.mimeCharset(charset)));
String emoji;
try {
if (EmojiCompat.isConfigured()) {
int emojiState = EmojiCompat.get().getLoadState();
switch (emojiState) {
case EmojiCompat.LOAD_STATE_LOADING:
emoji = "Loading";
break;
case EmojiCompat.LOAD_STATE_SUCCEEDED:
emoji = "Loaded";
break;
case EmojiCompat.LOAD_STATE_FAILED:
emoji = "Failed";
break;
case EmojiCompat.LOAD_STATE_DEFAULT:
emoji = "Not loaded";
break;
default:
emoji = "?" + emojiState;
}
} else
emoji = "Disabled";
} catch (Throwable ex) {
Log.e(ex);
emoji = ex.toString();
}
sb.append("Emoji: ").append(emoji).append("\r\n");
sb.append("Transliterate: ")
.append(TextHelper.canTransliterate())
.append("\r\n");
sb.append("Classifier: ")
.append(Helper.humanReadableByteCount(MessageClassifier.getSize(context)))
.append("\r\n");
sb.append("\r\n");
int cpus = Runtime.getRuntime().availableProcessors();
sb.append(String.format("Processors: %d\r\n", cpus));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
long running = SystemClock.uptimeMillis() - android.os.Process.getStartUptimeMillis();
long cpu = android.os.Process.getElapsedCpuTime();
int util = (int) (running == 0 ? 0 : 100 * cpu / running / cpus);
sb.append(String.format("Uptime: %s CPU: %s %d%%\r\n",
Helper.formatDuration(running), Helper.formatDuration(cpu), util));
}
Boolean largeHeap;
try {
ApplicationInfo info = pm.getApplicationInfo(context.getPackageName(), 0);
largeHeap = (info.flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
} catch (Throwable ex) {
largeHeap = null;
}
ActivityManager am = Helper.getSystemService(context, ActivityManager.class);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
sb.append(String.format("Memory class: %d/%d Large: %s MB Total: %s Low: %b\r\n",
am.getMemoryClass(), am.getLargeMemoryClass(),
largeHeap == null ? "?" : Boolean.toString(largeHeap),
Helper.humanReadableByteCount(mi.totalMem),
am.isLowRamDevice()));
long storage_available = Helper.getAvailableStorageSpace();
long storage_total = Helper.getTotalStorageSpace();
long storage_used = Helper.getSizeUsed(context.getFilesDir());
sb.append(String.format("Storage space: %s/%s App: %s\r\n",
Helper.humanReadableByteCount(storage_total - storage_available),
Helper.humanReadableByteCount(storage_total),
Helper.humanReadableByteCount(storage_used)));
long cache_used = Helper.getSizeUsed(context.getCacheDir());
long cache_quota = Helper.getCacheQuota(context);
sb.append(String.format("Cache space: %s/%s\r\n",
Helper.humanReadableByteCount(cache_used),
Helper.humanReadableByteCount(cache_quota)));
Runtime rt = Runtime.getRuntime();
long hused = (rt.totalMemory() - rt.freeMemory()) / 1024L / 1024L;
long hmax = rt.maxMemory() / 1024L / 1024L;
long nheap = Debug.getNativeHeapAllocatedSize() / 1024L / 1024L;
long nsize = Debug.getNativeHeapSize() / 1024 / 1024L;
sb.append(String.format("Heap usage: %d/%d MiB native: %d/%d MiB\r\n", hused, hmax, nheap, nsize));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
int ipc = IBinder.getSuggestedMaxIpcSizeBytes();
sb.append(String.format("IPC max: %s\r\n", Helper.humanReadableByteCount(ipc)));
}
sb.append("\r\n");
WindowManager wm = Helper.getSystemService(context, WindowManager.class);
Display display = wm.getDefaultDisplay();
Point dim = new Point();
display.getSize(dim);
float density = context.getResources().getDisplayMetrics().density;
sb.append(String.format("Density 1dp=%f\r\n", density));
sb.append(String.format("Resolution: %.2f x %.2f dp\r\n", dim.x / density, dim.y / density));
Configuration config = context.getResources().getConfiguration();
String size;
if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE))
size = "XLarge";
else if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
size = "Large";
else if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_NORMAL))
size = "Medium";
else if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_SMALL))
size = "Small";
else
size = "size=" + (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK);
String orientation;
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE)
orientation = "Landscape";
else if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
orientation = "Portrait";
else
orientation = "orientation=" + config.orientation;
sb.append(String.format("%s %s\r\n", size, orientation));
try {
float animation_scale = Settings.Global.getFloat(resolver,
Settings.Global.WINDOW_ANIMATION_SCALE, 0f);
sb.append(String.format("Animation scale: %f %s\r\n", animation_scale,
animation_scale == 1f ? "" : "!!!"));
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
int uiMode = context.getResources().getConfiguration().uiMode;
sb.append(String.format("UI mode: 0x"))
.append(Integer.toHexString(uiMode))
.append(" night=").append(Helper.isNight(context))
.append("\r\n");
String uiType = Helper.getUiModeType(context);
sb.append(String.format("UI type: %s %s\r\n", uiType,
"normal".equals(uiType) ? "" : "!!!"));
sb.append(String.format("Darken support: %b\r\n",
WebViewEx.isFeatureSupported(context, WebViewFeature.ALGORITHMIC_DARKENING)));
try {
PackageInfo pkg = WebViewCompat.getCurrentWebViewPackage(context);
sb.append(String.format("WebView %d/%s\r\n",
pkg == null ? -1 : pkg.versionCode,
pkg == null ? null : pkg.versionName));
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
sb.append("\r\n");
Boolean ignoring = Helper.isIgnoringOptimizations(context);
sb.append(String.format("Battery optimizations: %s %s\r\n",
ignoring == null ? null : Boolean.toString(!ignoring),
Boolean.FALSE.equals(ignoring) ? "!!!" : ""));
PowerManager power = Helper.getSystemService(context, PowerManager.class);
boolean psaving = power.isPowerSaveMode();
sb.append(String.format("Battery saving: %s %s\r\n", psaving, psaving ? "!!!" : ""));
sb.append(String.format("Charging: %b; level: %d\r\n",
Helper.isCharging(context), Helper.getBatteryLevel(context)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// https://developer.android.com/reference/android/app/usage/UsageStatsManager
UsageStatsManager usm = Helper.getSystemService(context, UsageStatsManager.class);
int bucket = usm.getAppStandbyBucket();
boolean inactive = usm.isAppInactive(BuildConfig.APPLICATION_ID);
sb.append(String.format("Standby bucket: %d-%b-%s %s\r\n",
bucket, inactive, Helper.getStandbyBucketName(bucket),
(bucket <= UsageStatsManager.STANDBY_BUCKET_ACTIVE && !inactive ? "" : "!!!")));
}
boolean canExact = AlarmManagerCompatEx.canScheduleExactAlarms(context);
boolean hasExact = AlarmManagerCompatEx.hasExactAlarms(context);
sb.append(String.format("ExactAlarms can=%b has=%b %s\r\n", canExact, hasExact,
canExact ? "" : "!!!"));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
boolean restricted = am.isBackgroundRestricted();
sb.append(String.format("Background restricted: %b %s\r\n", restricted,
restricted ? "!!!" : ""));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
boolean saving = ConnectionHelper.isDataSaving(context);
sb.append(String.format("Data saving: %b %s\r\n", saving,
saving ? "!!!" : ""));
}
try {
int finish_activities = Settings.Global.getInt(resolver,
Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0);
sb.append(String.format("Always finish: %d %s\r\n", finish_activities,
finish_activities == 0 ? "" : "!!!"));
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
sb.append("\r\n");
return sb;
}
private static void attachSettings(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "settings.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
Map<String, ?> settings = prefs.getAll();
List<String> keys = new ArrayList<>(settings.keySet());
Collections.sort(keys);
for (String key : keys) {
Object value = settings.get(key);
if ("wipe_mnemonic".equals(key) && value != null)
value = "[redacted]";
else if ("cloud_user".equals(key) && value != null)
value = "[redacted]";
else if ("cloud_password".equals(key) && value != null)
value = "[redacted]";
else if ("pin".equals(key) && value != null)
value = "[redacted]";
else if (key != null && key.startsWith("oauth."))
value = "[redacted]";
else if (key != null && key.startsWith("graph.contacts."))
value = "[redacted]";
size += write(os, key + "=" + value + "\r\n");
}
size += write(os, "\r\n");
try {
List<String> names = new ArrayList<>();
Properties props = System.getProperties();
Enumeration<?> pnames = props.propertyNames();
while (pnames.hasMoreElements())
names.add((String) pnames.nextElement());
Collections.sort(names);
for (String name : names)
size += write(os, name + "=" + props.getProperty(name) + "\r\n");
} catch (Throwable ex) {
size += write(os, ex.getMessage() + "\r\n");
}
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachAccounts(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "accounts.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
DateFormat dtf = Helper.getDateTimeInstance(context, SimpleDateFormat.SHORT, SimpleDateFormat.SHORT);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
List<EntityAccount> accounts = db.account().getAccounts();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean enabled = prefs.getBoolean("enabled", true);
int pollInterval = ServiceSynchronize.getPollInterval(context);
boolean metered = prefs.getBoolean("metered", true);
Boolean ignoring = Helper.isIgnoringOptimizations(context);
boolean canSchedule = AlarmManagerCompatEx.canScheduleExactAlarms(context);
boolean auto_optimize = prefs.getBoolean("auto_optimize", false);
boolean schedule = prefs.getBoolean("schedule", false);
String ds = ConnectionHelper.getDataSaving(context);
boolean vpn = ConnectionHelper.vpnActive(context);
boolean ng = false;
try {
PackageManager pm = context.getPackageManager();
pm.getPackageInfo("eu.faircode.netguard", 0);
ng = true;
} catch (Throwable ignored) {
}
Integer bucket = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
try {
UsageStatsManager usm = Helper.getSystemService(context, UsageStatsManager.class);
bucket = usm.getAppStandbyBucket();
} catch (Throwable ignored) {
}
Integer filter = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
NotificationManager nm = Helper.getSystemService(context, NotificationManager.class);
filter = nm.getCurrentInterruptionFilter();
}
StringBuilder filters = new StringBuilder();
StringBuilder sorts = new StringBuilder();
for (String key : prefs.getAll().keySet())
if (key.startsWith("filter_")) {
Object value = prefs.getAll().get(key);
if (Boolean.TRUE.equals(value))
filters.append(' ').append(key.substring(7)).append('=').append(value);
} else if (key.startsWith("sort_")) {
Object value = prefs.getAll().get(key);
sorts.append(' ').append(key).append('=').append(value);
}
size += write(os, "enabled=" + enabled + (enabled ? "" : " !!!") +
" interval=" + pollInterval + "\r\n" +
"metered=" + metered + (metered ? "" : " !!!") +
" saving=" + ds + ("enabled".equals(ds) ? " !!!" : "") +
" vpn=" + vpn + (vpn ? " !!!" : "") +
" ng=" + ng + "\r\n" +
"optimizing=" + (ignoring == null ? null : !ignoring) + (Boolean.FALSE.equals(ignoring) ? " !!!" : "") +
" bucket=" + (bucket == null ? null :
Helper.getStandbyBucketName(bucket) +
(bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE ? " !!!" : "")) +
" canSchedule=" + canSchedule + (canSchedule ? "" : " !!!") +
" auto_optimize=" + auto_optimize + (auto_optimize ? " !!!" : "") +
" notifications=" + (filter == null ? null :
Helper.getInterruptionFilter(filter) +
(filter == NotificationManager.INTERRUPTION_FILTER_ALL ? "" : " !!!")) + "\r\n" +
"accounts=" + accounts.size() +
" folders=" + db.folder().countSync() + "/" + db.folder().countTotal() +
" messages=" + db.message().countTotal() +
" rules=" + db.rule().countTotal() +
" ops=" + db.operation().getOperationCount() +
" outbox=" + db.message().countOutbox() + "\r\n" +
"filter " + filters + " " + sorts +
"\r\n\r\n");
if (schedule) {
int minuteStart = prefs.getInt("schedule_start", 0);
int minuteEnd = prefs.getInt("schedule_end", 0);
int minuteStartWeekend = prefs.getInt("schedule_start_weekend", minuteStart);
int minuteEndWeekend = prefs.getInt("schedule_end_weekend", minuteEnd);
size += write(os, String.format("schedule %s...%s weekend %s...%s\r\n",
CalendarHelper.formatHour(context, minuteStart),
CalendarHelper.formatHour(context, minuteEnd),
CalendarHelper.formatHour(context, minuteStartWeekend),
CalendarHelper.formatHour(context, minuteEndWeekend)));
String[] daynames = new DateFormatSymbols().getWeekdays();
for (int i = 0; i < 7; i++) {
boolean day = prefs.getBoolean("schedule_day" + i, true);
boolean weekend = CalendarHelper.isWeekend(context, i + 1);
size += write(os, String.format("schedule %s=%b %s\r\n",
daynames[i + 1], day, weekend ? "weekend" : ""));
}
size += write(os, "\r\n");
}
for (EntityAccount account : accounts)
if (account.synchronize)
try {
String info = "pwd";
if (account.auth_type == ServiceAuthenticator.AUTH_TYPE_OAUTH ||
account.auth_type == ServiceAuthenticator.AUTH_TYPE_GRAPH)
info = getTokenInfo(account.password, account.auth_type);
size += write(os, String.format("%s %s\r\n", account.name, info));
List<EntityIdentity> identities = db.identity().getSynchronizingIdentities(account.id);
for (EntityIdentity identity : identities)
if (identity.auth_type == ServiceAuthenticator.AUTH_TYPE_OAUTH ||
identity.auth_type == ServiceAuthenticator.AUTH_TYPE_GRAPH)
size += write(os, String.format("- %s %s\r\n",
identity.name, getTokenInfo(identity.password, identity.auth_type)));
} catch (Throwable ex) {
size += write(os, ex.toString() + "\r\n");
}
size += write(os, "\r\n");
Map<Long, EntityFolder> unified = new HashMap<>();
for (EntityFolder folder : db.folder().getFoldersByType(EntityFolder.INBOX))
unified.put(folder.id, folder);
for (EntityFolder folder : db.folder().getFoldersUnified(null, false))
unified.put(folder.id, folder);
for (Long fid : unified.keySet()) {
EntityFolder folder = unified.get(fid);
EntityAccount account = db.account().getAccount(folder.account);
size += write(os, String.format("%s/%s:%s sync=%b unified=%b\r\n",
(account == null ? null : account.name),
folder.name, folder.type, folder.synchronize, folder.unified));
}
size += write(os, "\r\n");
for (EntityAccount account : accounts) {
if (account.synchronize) {
int content = 0;
int messages = 0;
List<TupleFolderEx> folders = db.folder().getFoldersEx(account.id);
for (TupleFolderEx folder : folders) {
content += folder.content;
messages += folder.messages;
}
int blocked = db.contact().countBlocked(account.id);
boolean unmetered = false;
boolean ignore_schedule = false;
try {
if (account.conditions != null) {
JSONObject jconditions = new JSONObject(account.conditions);
unmetered = jconditions.optBoolean("unmetered");
ignore_schedule = jconditions.optBoolean("ignore_schedule");
}
} catch (Throwable ignored) {
}
size += write(os, account.id + ":" + account.name + (account.primary ? "*" : "") +
" " + (account.protocol == EntityAccount.TYPE_IMAP ? "IMAP" : "POP") +
" [" + (account.provider == null ? "" : account.provider) +
":" + ServiceAuthenticator.getAuthTypeName(account.auth_type) + "]" +
" " + account.host + ":" + account.port + "/" +
EmailService.getEncryptionName(account.encryption) +
(account.insecure ? " !!!" : "") +
" sync=" + account.synchronize +
" exempted=" + account.poll_exempted + (pollInterval > 0 && account.poll_exempted ? " !!!" : "") +
" poll=" + account.poll_interval +
" ondemand=" + account.ondemand + (account.ondemand ? " !!!" : "") +
" msgs=" + content + "/" + messages + " max=" + account.max_messages +
" blocked=" + blocked + (blocked == 0 ? "" : " !!!") +
" ops=" + db.operation().getOperationCount(account.id) +
" schedule=" + (!ignore_schedule) + (ignore_schedule ? " !!!" : "") +
" unmetered=" + unmetered + (unmetered ? " !!!" : "") +
" quota=" + (account.quota_usage == null ? "-" : Helper.humanReadableByteCount(account.quota_usage)) +
"/" + (account.quota_limit == null ? "-" : Helper.humanReadableByteCount(account.quota_limit)) +
" " + account.state +
(account.last_connected == null ? "" : " " + dtf.format(account.last_connected)) +
(account.error == null ? "" : "\r\n" + account.error) +
"\r\n");
if (folders.size() > 0)
Collections.sort(folders, folders.get(0).getComparator(context));
for (TupleFolderEx folder : folders)
if (folder.synchronize || account.protocol == EntityAccount.TYPE_POP) {
int unseen = db.message().countUnseen(folder.id);
int hidden = db.message().countHidden(folder.id);
int notifying = db.message().countNotifying(folder.id);
size += write(os, "- " + folder.id + ":" + folder.name + " " +
folder.type + (folder.inherited_type == null ? "" : "/" + folder.inherited_type) +
(folder.unified ? " unified" : "") +
(folder.notify ? " notify" : "") +
(Boolean.TRUE.equals(folder.subscribed) ? " subscribed" : "") +
" poll=" + folder.poll + (folder.poll || EntityFolder.INBOX.equals(folder.type) ? "" : " !!! ") +
" factor=" + folder.poll_factor +
" days=" + getDays(folder.sync_days) + "/" + getDays(folder.keep_days) +
" msgs=" + folder.content + "/" + folder.messages + "/" + folder.total +
" ops=" + db.operation().getOperationCount(folder.id, null) +
" unseen=" + unseen + " hidden=" + hidden + " notifying=" + notifying +
" " + folder.state +
(folder.last_sync == null ? "" : " " + dtf.format(folder.last_sync)) +
"\r\n");
}
List<TupleAccountSwipes> swipes = db.account().getAccountSwipes(account.id);
if (swipes == null)
size += write(os, "<> swipes?\r\n");
else
for (TupleAccountSwipes swipe : swipes) {
size += write(os, "> " + EntityMessage.getSwipeType(swipe.swipe_left) + " " +
swipe.left_name + ":" + swipe.left_type + "\r\n");
size += write(os, "< " + EntityMessage.getSwipeType(swipe.swipe_right) + " " +
swipe.right_name + ":" + swipe.right_type + "\r\n");
}
size += write(os, "\r\n");
}
}
for (EntityAccount account : accounts)
if (account.synchronize) {
List<EntityIdentity> identities = db.identity().getIdentities(account.id);
for (EntityIdentity identity : identities)
if (identity.synchronize) {
size += write(os, account.name + "/" + identity.name + (identity.primary ? "*" : "") + " " +
identity.display + " " + identity.email +
(identity.self ? "" : " !self") +
" [" + (identity.provider == null ? "" : identity.provider) +
":" + ServiceAuthenticator.getAuthTypeName(identity.auth_type) + "]" +
(TextUtils.isEmpty(identity.sender_extra_regex) ? "" : " regex=" + identity.sender_extra_regex) +
(!identity.sender_extra ? "" : " edit" +
(identity.sender_extra_name ? "+name" : "-name") +
(identity.reply_extra_name ? "+copy" : "-copy")) +
" " + identity.host + ":" + identity.port + "/" +
EmailService.getEncryptionName(identity.encryption) +
(identity.insecure ? " !!!" : "") +
" ops=" + db.operation().getOperationCount(EntityOperation.SEND) +
" max=" + (identity.max_size == null ? "-" : Helper.humanReadableByteCount(identity.max_size)) +
" " + identity.state +
(identity.last_connected == null ? "" : " " + dtf.format(identity.last_connected)) +
(identity.error == null ? "" : "\r\n" + identity.error) +
"\r\n");
}
}
size += write(os, "\r\n");
for (EntityAccount account : accounts) {
int ops = db.operation().getOperationCount(account.id);
if (account.synchronize || ops > 0)
try {
JSONObject jaccount = account.toJSON();
jaccount.put("state", account.state == null ? "null" : account.state);
jaccount.put("warning", account.warning);
jaccount.put("operations", ops);
jaccount.put("error", account.error);
jaccount.put("capabilities", account.capabilities);
if (account.last_connected != null)
jaccount.put("last_connected", new Date(account.last_connected).toString());
jaccount.put("keep_alive_ok", account.keep_alive_ok);
jaccount.put("keep_alive_failed", account.keep_alive_failed);
jaccount.put("keep_alive_succeeded", account.keep_alive_succeeded);
jaccount.remove("password");
size += write(os, "==========\r\n");
size += write(os, jaccount.toString(2) + "\r\n");
List<EntityFolder> folders = db.folder().getFolders(account.id, false, false);
if (folders.size() > 0)
Collections.sort(folders, folders.get(0).getComparator(context));
for (EntityFolder folder : folders) {
JSONObject jfolder = folder.toJSON();
jfolder.put("inherited_type", folder.inherited_type);
jfolder.put("level", folder.level);
jfolder.put("total", folder.total);
jfolder.put("initialize", folder.initialize);
jfolder.put("subscribed", folder.subscribed);
jfolder.put("state", folder.state == null ? "null" : folder.state);
jfolder.put("sync_state", folder.sync_state == null ? "null" : folder.sync_state);
jfolder.put("poll_count", folder.poll_count);
jfolder.put("read_only", folder.read_only);
jfolder.put("selectable", folder.selectable);
jfolder.put("inferiors", folder.inferiors);
jfolder.put("auto_add", folder.auto_add);
jfolder.put("flags", folder.flags == null ? null : TextUtils.join(",", folder.flags));
jfolder.put("keywords", folder.keywords == null ? null : TextUtils.join(",", folder.keywords));
jfolder.put("tbc", Boolean.TRUE.equals(folder.tbc));
jfolder.put("rename", folder.rename);
jfolder.put("tbd", Boolean.TRUE.equals(folder.tbd));
jfolder.put("operations", db.operation().getOperationCount(folder.id, null));
jfolder.put("error", folder.error);
if (folder.last_sync != null)
jfolder.put("last_sync", new Date(folder.last_sync).toString());
if (folder.last_sync_count != null)
jfolder.put("last_sync_count", folder.last_sync_count);
size += write(os, jfolder.toString(2) + "\r\n");
}
List<EntityIdentity> identities = db.identity().getIdentities(account.id);
for (EntityIdentity identity : identities)
try {
JSONObject jidentity = identity.toJSON();
jidentity.remove("password");
jidentity.remove("signature");
size += write(os, "----------\r\n");
size += write(os, jidentity.toString(2) + "\r\n");
} catch (JSONException ex) {
size += write(os, ex.toString() + "\r\n");
}
} catch (JSONException ex) {
size += write(os, ex.toString() + "\r\n");
}
}
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static String getDays(Integer days) {
if (days == null)
return "?";
else
return (days == Integer.MAX_VALUE ? "" : Integer.toString(days));
}
private static void attachNetworkInfo(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "network.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
Boolean isValidated = null;
Boolean isCaptive = null;
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
ConnectivityManager cm = Helper.getSystemService(context, ConnectivityManager.class);
NetworkInfo ani = cm.getActiveNetworkInfo();
if (ani != null)
size += write(os, "Active network info=" + ani +
" connecting=" + ani.isConnectedOrConnecting() +
" connected=" + ani.isConnected() +
" available=" + ani.isAvailable() +
" state=" + ani.getState() + "/" + ani.getDetailedState() +
" metered=" + cm.isActiveNetworkMetered() +
" roaming=" + ani.isRoaming() +
" type=" + ani.getType() + "/" + ani.getTypeName() +
"\r\n\r\n");
Network active = ConnectionHelper.getActiveNetwork(context);
NetworkInfo a = (active == null ? null : cm.getNetworkInfo(active));
NetworkCapabilities c = (active == null ? null : cm.getNetworkCapabilities(active));
LinkProperties p = (active == null ? null : cm.getLinkProperties(active));
boolean n = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
size += write(os, "Active network=" + active + " native=" + n + "\r\n");
size += write(os, " info=" + a +
" connecting=" + (a == null ? null : a.isConnectedOrConnecting()) +
" connected=" + (a == null ? null : a.isConnected()) +
" available=" + (a == null ? null : a.isAvailable()) +
" state=" + (a == null ? null : a.getState() + "/" + a.getDetailedState()) +
" roaming=" + (a == null ? null : a.isRoaming()) +
" type=" + (a == null ? null : a.getType() + "/" + a.getTypeName()) +
"\r\n");
size += write(os, " caps=" + c + "\r\n");
size += write(os, " props=" + p + "\r\n\r\n");
for (Network network : cm.getAllNetworks()) {
size += write(os, (network.equals(active) ? "active=" : "network=") + network + "\r\n");
NetworkCapabilities caps = cm.getNetworkCapabilities(network);
size += write(os, " caps=" + caps + "\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (isValidated == null)
isValidated = false;
if (caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))
isValidated = true;
if (isCaptive == null)
isCaptive = false;
if (caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL))
isCaptive = true;
}
LinkProperties props = cm.getLinkProperties(network);
size += write(os, " props=" + props + "\r\n");
size += write(os, "\r\n");
}
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces != null && interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
size += write(os, "Interface=" + ni + "\r\n");
for (InterfaceAddress iaddr : ni.getInterfaceAddresses()) {
InetAddress addr = iaddr.getAddress();
size += write(os, " addr=" + addr +
(addr.isLoopbackAddress() ? " loopback" : "") +
(addr.isSiteLocalAddress() ? " site local (LAN)" : "") +
(addr.isLinkLocalAddress() ? " link local (device)" : "") +
(addr.isAnyLocalAddress() ? " any local" : "") +
(addr.isMulticastAddress() ? " multicast" : "") + "\r\n");
}
size += write(os, "\r\n");
}
} catch (Throwable ex) {
size += write(os, ex.getMessage() + "\r\n");
}
ConnectionHelper.NetworkState state = ConnectionHelper.getNetworkState(context);
size += write(os, "Connected=" + state.isConnected() + "\r\n");
size += write(os, "Suitable=" + state.isSuitable() + "\r\n");
size += write(os, "Unmetered=" + state.isUnmetered() + "\r\n");
size += write(os, "Roaming=" + state.isRoaming() + "\r\n");
size += write(os, "\r\n");
boolean[] has46 = ConnectionHelper.has46(context);
size += write(os, "Has IPv4=" + has46[0] + " IPv6=" + has46[1] + "\r\n");
size += write(os, "VPN active=" + ConnectionHelper.vpnActive(context) + "\r\n");
size += write(os, "Data saving=" + ConnectionHelper.isDataSaving(context) + "\r\n");
size += write(os, "Airplane=" + ConnectionHelper.airplaneMode(context) + "\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
size += write(os, "Cleartext permitted= " +
NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted() + "\r\n");
size += write(os, "\r\n");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int timeout = prefs.getInt("timeout", EmailService.DEFAULT_CONNECT_TIMEOUT);
boolean metered = prefs.getBoolean("metered", true);
int download = prefs.getInt("download", MessageHelper.DEFAULT_DOWNLOAD_SIZE);
boolean download_limited = prefs.getBoolean("download_limited", false);
boolean roaming = prefs.getBoolean("roaming", true);
boolean rlah = prefs.getBoolean("rlah", true);
boolean download_headers = prefs.getBoolean("download_headers", false);
boolean download_eml = prefs.getBoolean("download_eml", false);
boolean download_plain = prefs.getBoolean("download_plain", false);
boolean standalone_vpn = prefs.getBoolean("standalone_vpn", false);
boolean require_validated = prefs.getBoolean("require_validated", false);
boolean require_validated_captive = prefs.getBoolean("require_validated_captive", true);
boolean vpn_only = prefs.getBoolean("vpn_only", false);
boolean tcp_keep_alive = prefs.getBoolean("tcp_keep_alive", false);
boolean ssl_harden = prefs.getBoolean("ssl_harden", false);
boolean ssl_harden_strict = (ssl_harden && prefs.getBoolean("ssl_harden_strict", false));
boolean cert_strict = prefs.getBoolean("cert_strict", true);
boolean open_safe = prefs.getBoolean("open_safe", false);
size += write(os, "timeout=" + timeout + "s" + (timeout == EmailService.DEFAULT_CONNECT_TIMEOUT ? "" : " !!!") + "\r\n");
size += write(os, "metered=" + metered + (metered ? "" : " !!!") + "\r\n");
size += write(os, "download=" + Helper.humanReadableByteCount(download) +
" unmetered=" + download_limited + (download_limited ? " !!!" : "") + "\r\n");
size += write(os, "roaming=" + roaming + (roaming ? "" : " !!!") + "\r\n");
size += write(os, "rlah=" + rlah + (rlah ? "" : " !!!") + "\r\n");
size += write(os, "headers=" + download_headers + (download_headers ? " !!!" : "") + "\r\n");
size += write(os, "eml=" + download_eml + (download_eml ? " !!!" : "") + "\r\n");
size += write(os, "plain=" + download_plain + (download_plain ? " !!!" : "") + "\r\n");
size += write(os, "captive=" + (isCaptive == null ? "-" : Boolean.toString(isCaptive)) + "\r\n");
size += write(os, "validation=" + require_validated + (require_validated ? " !!!" : "") +
" captive=" + require_validated_captive + (require_validated_captive ? "" : " !!!") + "\r\n");
size += write(os, "validated=" + (isValidated == null ? "-" : Boolean.toString(isValidated)) +
(Boolean.FALSE.equals(isValidated) &&
(Boolean.TRUE.equals(isCaptive) ? require_validated_captive : require_validated) ? " !!!" : "") + "\r\n");
size += write(os, "standalone_vpn=" + standalone_vpn + (standalone_vpn ? " !!!" : "") + "\r\n");
size += write(os, "vpn_only=" + vpn_only + (vpn_only ? " !!!" : "") + "\r\n");
size += write(os, "tcp_keep_alive=" + tcp_keep_alive + (tcp_keep_alive ? " !!!" : "") + "\r\n");
size += write(os, "ssl_harden=" + ssl_harden + (ssl_harden ? " !!!" : "") + "\r\n");
size += write(os, "ssl_harden_strict=" + ssl_harden_strict + (ssl_harden_strict ? " !!!" : "") + "\r\n");
size += write(os, "cert_strict=" + cert_strict + (cert_strict ? " !!!" : "") + "\r\n");
size += write(os, "open_safe=" + open_safe + "\r\n");
size += write(os, "\r\n");
size += write(os, getCiphers().toString());
try {
String algo = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algo);
tmf.init((KeyStore) null);
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null)
for (TrustManager tm : tms) {
size += write(os, String.format("Trust manager: %s (%s)\n",
tm.getClass().getName(), algo));
if (tm instanceof X509TrustManager)
for (X509Certificate cert : ((X509TrustManager) tm).getAcceptedIssuers())
size += write(os, String.format("- %s\n", cert.getIssuerDN()));
}
} catch (Throwable ex) {
size += write(os, ex.getMessage() + "\r\n");
}
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachLog(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "log.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
long from = new Date().getTime() - 24 * 3600 * 1000L;
DateFormat TF = Helper.getTimeInstance(context);
for (EntityLog entry : db.log().getLogs(from, null))
if (entry.data != null && entry.data.contains("backoff="))
size += write(os, String.format("%s %s\r\n",
TF.format(entry.time),
entry.data));
size += write(os, "\r\n");
for (EntityLog entry : db.log().getLogs(from, null)) {
size += write(os, String.format("%s [%d:%d:%d:%d:%d] %s\r\n",
TF.format(entry.time),
entry.type.ordinal(),
(entry.thread == null ? 0 : entry.thread),
(entry.account == null ? 0 : entry.account),
(entry.folder == null ? 0 : entry.folder),
(entry.message == null ? 0 : entry.message),
entry.data));
if (size > MAX_LOG_SIZE) {
size += write(os, "<truncated>\r\n");
break;
}
}
}
db.attachment().setDownloaded(attachment.id, size);
if (!BuildConfig.DEBUG && size > MIN_ZIP_SIZE)
attachment.zip(context);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachOperations(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "operations.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
DateFormat TF = Helper.getTimeInstance(context);
for (EntityOperation op : db.operation().getOperations()) {
EntityAccount account = (op.account == null ? null : db.account().getAccount(op.account));
EntityFolder folder = (op.folder == null ? null : db.folder().getFolder(op.folder));
size += write(os, String.format("%s %s/%s %d %s/%d %s %s %s\r\n",
TF.format(op.created),
account == null ? null : account.name,
folder == null ? null : folder.name,
op.message == null ? -1 : op.message,
op.name,
op.tries,
op.args,
op.state,
op.error));
}
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachTasks(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "tasks.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
for (SimpleTask task : SimpleTask.getList())
size += write(os, String.format("%s\r\n", task.toString()));
size += write(os, "\r\n");
for (TwoStateOwner owner : TwoStateOwner.getList())
size += write(os, String.format("%s\r\n", owner.toString()));
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachLogcat(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "logcat.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
attachment.zip(context, TinyLogConfigurationLoader.getFiles(context));
/*
// https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html#java
ProcessBuilder pb = new ProcessBuilder("/system/bin/logcat",
"-d",
"-v", "threadtime",
//"-t", "1000",
Log.TAG + ":I");
Map<String, String> env = pb.environment();
env.clear();
pb.directory(context.getFilesDir());
Process proc = null;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
proc = pb.start();
long size = 0;
try (BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = br.readLine()) != null)
size += write(os, line + "\r\n");
}
db.attachment().setDownloaded(attachment.id, size);
if (!BuildConfig.DEBUG && size > MIN_ZIP_SIZE)
attachment.zip(context);
} finally {
if (proc != null)
proc.destroy();
}
*/
} catch (Throwable ex) {
Log.e(ex);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static void attachNotificationInfo(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "notification.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
NotificationManager nm = Helper.getSystemService(context, NotificationManager.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
boolean permission = Helper.hasPermission(context, Manifest.permission.POST_NOTIFICATIONS);
boolean enabled = nm.areNotificationsEnabled();
size += write(os, String.format("Permission=%b %s Enabled=%b %s\r\n",
permission, (permission ? "" : "!!!"),
enabled, (enabled ? "" : "!!!")));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
boolean paused = nm.areNotificationsPaused();
size += write(os, String.format("Paused=%b %s\r\n",
paused, (paused ? "!!!" : "")));
}
int filter = nm.getCurrentInterruptionFilter();
size += write(os, String.format("Interruption filter allow=%s %s\r\n\r\n",
Helper.getInterruptionFilter(filter),
(filter == NotificationManager.INTERRUPTION_FILTER_ALL ? "" : "!!!")));
size += write(os, String.format("InCall=%b DND=%b\r\n\r\n",
MediaPlayerHelper.isInCall(context),
MediaPlayerHelper.isDnd(context)));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
StringBuilder options = new StringBuilder();
for (String key : prefs.getAll().keySet())
if (key.startsWith("notify_")) {
Object value = prefs.getAll().get(key);
boolean mark = false;
if ("notify_known".equals(key) && Boolean.TRUE.equals(value))
mark = true;
if ("notify_background_only".equals(key) && Boolean.TRUE.equals(value))
mark = true;
if ("notify_suppress_in_car".equals(key) && Boolean.TRUE.equals(value))
mark = true;
options.append(' ').append(key).append('=')
.append(value)
.append(mark ? " !!!" : "")
.append("\r\n");
}
if (options.length() > 0) {
options.append("\r\n");
size += write(os, options.toString());
}
for (NotificationChannel channel : nm.getNotificationChannels())
try {
JSONObject jchannel = NotificationHelper.channelToJSON(channel);
size += write(os, jchannel.toString(2) + "\r\n\r\n");
} catch (JSONException ex) {
size += write(os, ex + "\r\n");
}
size += write(os,
String.format("Importance none=%d; min=%d; low=%d; default=%d; high=%d; max=%d; unspecified=%d\r\n",
NotificationManager.IMPORTANCE_NONE,
NotificationManager.IMPORTANCE_MIN,
NotificationManager.IMPORTANCE_LOW,
NotificationManager.IMPORTANCE_DEFAULT,
NotificationManager.IMPORTANCE_HIGH,
NotificationManager.IMPORTANCE_MAX,
NotificationManager.IMPORTANCE_UNSPECIFIED));
size += write(os,
String.format("Visibility private=%d; public=%d; secret=%d\r\n",
Notification.VISIBILITY_PRIVATE,
Notification.VISIBILITY_PUBLIC,
Notification.VISIBILITY_SECRET));
size += write(os, String.format("Interruption filter\r\n"));
size += write(os, String.format("- All: no notifications are suppressed.\r\n"));
size += write(os, String.format("- Priority: all notifications are suppressed except those that match the priority criteria. Some audio streams are muted.\r\n"));
size += write(os, String.format("- None: all notifications are suppressed and all audio streams (except those used for phone calls) and vibrations are muted.\r\n"));
size += write(os, String.format("- Alarm: all notifications except those of category alarm are suppressed. Some audio streams are muted.\r\n"));
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachEnvironment(Context context, long id, int sequence) {
try {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "environment.txt";
attachment.type = "text/plain";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
long now = new Date().getTime();
PackageManager pm = context.getPackageManager();
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
size += write(os, String.format("Photo picker=%b\r\n", Helper.hasPhotoPicker()));
size += write(os, String.format("Double tap timeout=%d\r\n", ViewConfiguration.getDoubleTapTimeout()));
size += write(os, String.format("Long press timeout=%d\r\n", ViewConfiguration.getLongPressTimeout()));
for (Class<?> cls : new Class[]{
ActivitySendSelf.class,
ActivitySearch.class,
ActivityAnswer.class,
ReceiverAutoStart.class})
size += write(os, String.format("%s=%b\r\n",
cls.getSimpleName(), Helper.isComponentEnabled(context, cls)));
size += write(os, "\r\n");
try {
ApplicationInfo app = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
List<String> metas = getExtras(app.metaData);
size += write(os, "Manifest metas=" + (metas == null ? null : metas.size()) + "\r\n");
for (String meta : metas)
size += write(os, String.format("%s\r\n", meta));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
int flags = PackageManager.GET_RESOLVED_FILTER;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
flags |= PackageManager.MATCH_ALL;
try {
Intent home = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> homes = context.getPackageManager().queryIntentActivities(home, PackageManager.MATCH_DEFAULT_ONLY);
size += write(os, "Launchers=" + (homes == null ? null : homes.size()) + "\r\n");
if (homes != null)
for (ResolveInfo ri : homes)
size += write(os, String.format("Launcher=%s\r\n", ri.activityInfo.packageName));
ResolveInfo rid = context.getPackageManager().resolveActivity(home, PackageManager.MATCH_DEFAULT_ONLY);
size += write(os, String.format("Default launcher=%s\r\n", (rid == null ? null : rid.activityInfo.packageName)));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
try {
Intent intent = new Intent(Intent.ACTION_VIEW)
//.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.parse("http://example.com/"));
ResolveInfo main = pm.resolveActivity(intent, 0);
List<ResolveInfo> ris = pm.queryIntentActivities(intent, flags);
size += write(os, "Browsers=" + (ris == null ? null : ris.size()) + "\r\n");
if (ris != null)
for (ResolveInfo ri : ris) {
CharSequence label = pm.getApplicationLabel(ri.activityInfo.applicationInfo);
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(ri.activityInfo.packageName);
boolean tabs = (pm.resolveService(serviceIntent, 0) != null);
StringBuilder sb = new StringBuilder();
sb.append("Browser=").append(ri.activityInfo.packageName);
if (Objects.equals(main == null ? null : main.activityInfo.packageName, ri.activityInfo.packageName))
sb.append("*");
sb.append(" (").append(label).append(")");
sb.append(" tabs=").append(tabs);
sb.append(" view=").append(ri.filter.hasAction(Intent.ACTION_VIEW));
sb.append(" browsable=").append(ri.filter.hasCategory(Intent.CATEGORY_BROWSABLE));
sb.append(" authorities=").append(ri.filter.authoritiesIterator() != null);
sb.append(" schemes=");
boolean first = true;
Iterator<String> schemeIter = ri.filter.schemesIterator();
while (schemeIter.hasNext()) {
String scheme = schemeIter.next();
if (first)
first = false;
else
sb.append(',');
sb.append(scheme);
}
if (tabs && BuildConfig.DEBUG)
try {
boolean bindable = context.bindService(serviceIntent, new CustomTabsServiceConnection() {
@Override
public void onCustomTabsServiceConnected(@NonNull final ComponentName component, final CustomTabsClient client) {
try {
context.unbindService(this);
} catch (Throwable ex) {
Log.e(ex);
}
}
@Override
public void onServiceDisconnected(final ComponentName component) {
// Do nothing
}
}, 0);
sb.append(" bindable=").append(bindable);
} catch (Throwable ex) {
size += write(os, ex.toString() + "\r\n");
}
sb.append("\r\n");
size += write(os, sb.toString());
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String open_with_pkg = prefs.getString("open_with_pkg", null);
boolean open_with_tabs = prefs.getBoolean("open_with_tabs", true);
size += write(os, String.format("Selected: %s tabs=%b\r\n",
open_with_pkg, open_with_tabs));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
try {
Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
ResolveInfo main = pm.resolveActivity(intent, 0);
List<ResolveInfo> ris = pm.queryIntentActivities(intent, flags);
size += write(os, "Recorders=" + (ris == null ? null : ris.size()) + "\r\n");
if (ris != null)
for (ResolveInfo ri : ris) {
CharSequence label = pm.getApplicationLabel(ri.activityInfo.applicationInfo);
StringBuilder sb = new StringBuilder();
sb.append("Recorder=").append(ri.activityInfo.packageName);
if (Objects.equals(main.activityInfo.packageName, ri.activityInfo.packageName))
sb.append("*");
sb.append(" (").append(label).append(")");
sb.append("\r\n");
size += write(os, sb.toString());
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
try {
List<UriPermission> uperms = context.getContentResolver().getPersistedUriPermissions();
size += write(os, "Persisted URIs=" + (uperms == null ? null : uperms.size()) + "\r\n");
if (uperms != null)
for (UriPermission uperm : uperms) {
size += write(os, String.format("%s r=%b w=%b %s\r\n",
uperm.getUri().toString(),
uperm.isReadPermission(),
uperm.isWritePermission(),
new Date(uperm.getPersistedTime())));
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
try {
PackageInfo pi = pm.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_PERMISSIONS);
for (int i = 0; i < pi.requestedPermissions.length; i++)
if (pi.requestedPermissions[i] != null &&
pi.requestedPermissions[i].startsWith("android.permission.")) {
boolean granted = ((pi.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
size += write(os, String.format("%s=%b\r\n",
pi.requestedPermissions[i].replace("android.permission.", ""), granted));
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
for (String prop : NETWORK_PROPS)
size += write(os, prop + "=" + System.getProperty(prop) + "\r\n");
size += write(os, "\r\n");
ApplicationInfo ai = context.getApplicationInfo();
if (ai != null)
size += write(os, String.format("Source: %s\r\n public: %s\r\n",
ai.sourceDir, ai.publicSourceDir));
size += write(os, String.format("Files: %s\r\n", context.getFilesDir()));
size += write(os, String.format("Cache: %s\r\n external: %s\n",
context.getCacheDir(), context.getExternalCacheDir()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
size += write(os, String.format("Data: %s\r\n", context.getDataDir().getAbsolutePath()));
size += write(os, String.format("Database: %s\r\n",
context.getDatabasePath(DB.DB_NAME)));
try (Cursor cursor = SQLiteDatabase.create(null).rawQuery(
"SELECT sqlite_version() AS sqlite_version", null)) {
if (cursor.moveToNext())
size += write(os, String.format("sqlite: %s\r\n", cursor.getString(0)));
}
try {
TupleFtsStats stats = db.message().getFts();
size += write(os, String.format("fts: %d/%d %s\r\n", stats.fts, stats.total,
Helper.humanReadableByteCount(Fts4DbHelper.size(context))));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
DomainVerificationManager dvm = Helper.getSystemService(context, DomainVerificationManager.class);
DomainVerificationUserState userState = dvm.getDomainVerificationUserState(context.getPackageName());
Map<String, Integer> hostToStateMap = userState.getHostToStateMap();
for (String key : hostToStateMap.keySet()) {
Integer stateValue = hostToStateMap.get(key);
if (stateValue == DomainVerificationUserState.DOMAIN_STATE_VERIFIED)
size += write(os, String.format("Verified: %s\r\n", key));
else if (stateValue == DomainVerificationUserState.DOMAIN_STATE_SELECTED)
size += write(os, String.format("selected: %s\r\n", key));
else
size += write(os, String.format("Unverified: %s (%d)\r\n", key,
stateValue == null ? -1 : stateValue));
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
}
try {
List<WorkInfo> works = WorkManager
.getInstance(context)
.getWorkInfos(WorkQuery.fromStates(
WorkInfo.State.ENQUEUED,
WorkInfo.State.BLOCKED,
WorkInfo.State.RUNNING))
.get();
for (WorkInfo work : works) {
size += write(os, String.format("Work: %s\r\n",
work.toString()));
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
try {
Map<Integer, Integer> exts = SdkExtensions.getAllExtensionVersions();
for (Integer ext : exts.keySet())
size += write(os, String.format("Extension %d / %d\r\n", ext, exts.get(ext)));
if (exts.size() > 0)
size += write(os, "\r\n");
size += write(os, String.format("Max. pick images: %d\r\n\r\n", MediaStore.getPickImagesMaxLimit()));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
for (FileStore store : FileSystems.getDefault().getFileStores())
if (!store.isReadOnly() &&
store.getUsableSpace() != 0 &&
!"tmpfs".equals(store.type())) {
long total = store.getTotalSpace();
long unalloc = store.getUnallocatedSpace();
size += write(os, String.format("%s %s %s/%s\r\n",
store,
store.type(),
Helper.humanReadableByteCount(total - unalloc),
Helper.humanReadableByteCount(total)));
}
} catch (IOException ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
}
List<File> files = new ArrayList<>();
try {
files.addAll(Helper.listFiles(context.getFilesDir(), MIN_FILE_SIZE));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
try {
files.addAll(Helper.listFiles(context.getCacheDir(), MIN_FILE_SIZE));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File f1, File f2) {
return -Long.compare(f1.length(), f2.length());
}
});
for (int i = 0; i < Math.min(100, files.size()); i++)
size += write(os, String.format("%d %s %s\r\n", i + 1,
Helper.humanReadableByteCount(files.get(i).length()),
files.get(i).getAbsoluteFile()));
size += write(os, "\r\n");
size += write(os, String.format("Configuration: %s\r\n\r\n",
context.getResources().getConfiguration()));
for (Provider p : Security.getProviders())
size += write(os, String.format("%s\r\n", p));
size += write(os, "\r\n");
String pgpPackage = PgpHelper.getPackageName(context);
boolean pgpInstalled = PgpHelper.isOpenKeychainInstalled(context);
size += write(os, String.format("%s=%b\r\n", pgpPackage, pgpInstalled));
if (pgpInstalled)
try {
PackageInfo pi = pm.getPackageInfo(pgpPackage, PackageManager.GET_PERMISSIONS);
for (int i = 0; i < pi.requestedPermissions.length; i++) {
boolean granted = ((pi.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
size += write(os, String.format("- %s=%b\r\n", pi.requestedPermissions[i], granted));
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
try {
int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
size += write(os, context.getString(R.string.title_advanced_aes_key_size,
Helper.humanReadableByteCount(maxKeySize, false)));
size += write(os, "\r\n");
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
Map<String, String> stats = Debug.getRuntimeStats();
for (String key : stats.keySet())
size += write(os, String.format("%s=%s\r\n", key, stats.get(key)));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
// https://developer.android.com/reference/android/app/ApplicationExitInfo
ActivityManager am = Helper.getSystemService(context, ActivityManager.class);
List<ApplicationExitInfo> infos = am.getHistoricalProcessExitReasons(
context.getPackageName(), 0, 100);
for (ApplicationExitInfo info : infos)
size += write(os, String.format("%s: %s %s/%s reason=%s status=%d importance=%d\r\n",
new Date(info.getTimestamp()), info.getDescription(),
Helper.humanReadableByteCount(info.getPss() * 1024L),
Helper.humanReadableByteCount(info.getRss() * 1024L),
Helper.getExitReason(info.getReason()), info.getStatus(), info.getImportance()));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
size += write(os, "\r\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
try {
UsageStatsManager usm = Helper.getSystemService(context, UsageStatsManager.class);
UsageEvents events = usm.queryEventsForSelf(now - 12 * 3600L, now);
UsageEvents.Event event = new UsageEvents.Event();
while (events != null && events.hasNextEvent()) {
events.getNextEvent(event);
size += write(os, String.format("%s %s %s b=%d s=%d\r\n",
new Date(event.getTimeStamp()),
Helper.getEventType(event.getEventType()),
event.getClassName(),
event.getAppStandbyBucket(),
event.getShortcutId()));
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
try {
List<PermissionGroupInfo> groups = pm.getAllPermissionGroups(0);
groups.add(0, null); // Ungrouped
for (PermissionGroupInfo group : groups) {
String name = (group == null ? null : group.name);
size += write(os, String.format("\r\n%s\r\n", name == null ? "Ungrouped" : name));
size += write(os, "----------------------------------------\r\n");
try {
for (PermissionInfo permission : pm.queryPermissionsByGroup(name, 0))
size += write(os, String.format("%s\r\n", permission.name));
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
}
} catch (Throwable ex) {
size += write(os, String.format("%s\r\n", ex));
}
}
db.attachment().setDownloaded(attachment.id, size);
} catch (Throwable ex) {
Log.e(ex);
}
}
private static void attachClassifierData(Context context, long id, int sequence) throws IOException, JSONException {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "classifier.json";
attachment.type = "application/json";
attachment.disposition = Part.ATTACHMENT;
attachment.size = null;
attachment.progress = 0;
attachment.id = db.attachment().insertAttachment(attachment);
MessageClassifier.save(context);
File source = MessageClassifier.getFile(context, false);
File target = attachment.getFile(context);
Helper.copy(source, target);
db.attachment().setDownloaded(attachment.id, target.length());
}
static String getTokenInfo(String password, int auth_type) throws JSONException {
AuthState authState = AuthState.jsonDeserialize(password);
Long expiration = authState.getAccessTokenExpirationTime();
TokenResponse t = authState.getLastTokenResponse();
Set<String> scopeSet = (t == null ? null : t.getScopeSet());
String[] scopes = (scopeSet == null ? new String[0] : scopeSet.toArray(new String[0]));
return String.format("%s expire=%s need=%b %s",
ServiceAuthenticator.getAuthTypeName(auth_type),
(expiration == null ? null : new Date(expiration)),
authState.getNeedsTokenRefresh(),
TextUtils.join(",", scopes));
}
static SpannableStringBuilder getCiphers() {
SpannableStringBuilder ssb = new SpannableStringBuilderEx();
for (Provider provider : new Provider[]{
null, // Android
new BouncyCastleJsseProvider(),
new BouncyCastleJsseProvider(true)})
for (String protocol : new String[]{"SSL", "TLS"})
try {
int begin = ssb.length();
SSLContext sslContext = (provider == null
? SSLContext.getInstance(protocol)
: SSLContext.getInstance(protocol, provider));
ssb.append("SSL protocol: ").append(sslContext.getProtocol()).append("\r\n");
Provider sslProvider = sslContext.getProvider();
ssb.append("SSL provider: ").append(sslProvider.getName());
if (sslProvider instanceof BouncyCastleJsseProvider) {
boolean fips = ((BouncyCastleJsseProvider) sslProvider).isFipsMode();
if (fips)
ssb.append(" FIPS");
}
ssb.append("\r\n");
ssb.append("SSL class: ").append(sslProvider.getClass().getName()).append("\r\n");
ssb.setSpan(new StyleSpan(Typeface.BOLD), begin, ssb.length(), 0);
ssb.append("\r\n");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
ssb.append("Trust provider: ").append(tmf.getProvider().getName()).append("\r\n");
ssb.append("Trust class: ").append(tmf.getProvider().getClass().getName()).append("\r\n");
ssb.append("Trust algorithm: ").append(tmf.getAlgorithm()).append("\r\n");
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null)
for (TrustManager tm : tms)
ssb.append("Trust manager: ").append(tm.getClass().getName()).append("\r\n");
ssb.append("\r\n");
sslContext.init(null, tmf.getTrustManagers(), null);
SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket();
List<String> protocols = new ArrayList<>();
protocols.addAll(Arrays.asList(socket.getEnabledProtocols()));
for (String p : socket.getSupportedProtocols()) {
boolean enabled = protocols.contains(p);
if (!enabled)
ssb.append('(');
int start = ssb.length();
ssb.append(p);
if (!enabled) {
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), 0);
ssb.append(')');
}
ssb.append("\r\n");
}
ssb.append("\r\n");
List<String> ciphers = new ArrayList<>();
ciphers.addAll(Arrays.asList(socket.getEnabledCipherSuites()));
for (String c : socket.getSupportedCipherSuites()) {
boolean enabled = ciphers.contains(c);
if (!enabled)
ssb.append('(');
int start = ssb.length();
ssb.append(c);
if (!enabled) {
ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), 0);
ssb.append(')');
}
ssb.append("\r\n");
}
ssb.append("\r\n");
} catch (Throwable ex) {
Log.e(ex);
}
ssb.setSpan(new RelativeSizeSpan(HtmlHelper.FONT_SMALL), 0, ssb.length(), 0);
return ssb;
}
private static int write(OutputStream os, String text) throws IOException {
byte[] bytes = text.getBytes();
os.write(bytes); // TODO CASA
return bytes.length;
}
private static long getFreeMem() {
Runtime rt = Runtime.getRuntime();
long used = (rt.totalMemory() - rt.freeMemory());
long max = rt.maxMemory();
return (max - used);
}
static int getFreeMemMb() {
return (int) (getFreeMem() / 1024L / 1024L);
}
static int getAvailableMb() {
Runtime rt = Runtime.getRuntime();
return (int) (rt.maxMemory() / 1024L / 1024L);
}
static InternetAddress myAddress() throws UnsupportedEncodingException {
return new InternetAddress("marcel+fairemail@faircode.eu", "FairCode", StandardCharsets.UTF_8.name());
}
static StringBuilder getSpans(CharSequence text) {
StringBuilder sb = new StringBuilder();
TextUtils.dumpSpans(text, new Printer() {
@Override
public void println(String x) {
if (sb.length() > 0)
sb.append(' ');
sb.append(x.replace('\n', '|')).append(']');
}
}, "[");
return sb;
}
}