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.net."))
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 (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 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;
}
StringBuilder sb = new StringBuilder();
if (BuildConfig.DEBUG)
sb.append(ex.toString());
else
sb.append(ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage());
Throwable cause = ex.getCause();
while (cause != null) {
if (BuildConfig.DEBUG)
sb.append(separator).append(cause.toString());
else
sb.append(separator).append(cause.getMessage() == null ? cause.getClass().getName() : cause.getMessage());
cause = cause.getCause();
}
return sb.toString();
}
static void writeCrashLog(Context context, Throwable ex) {
File file = new File(context.getCacheDir(), "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" + android.util.Log.getStackTraceString(ex) + "\r\n");
} catch (IOException e) {
Log.e(e);
}
}
static EntityMessage getDebugInfo(Context context, int title, Throwable ex, String log) throws IOException, JSONException {
StringBuilder sb = new StringBuilder();
sb.append(context.getString(title)).append("\n\n\n\n");
sb.append(getAppInfo(context));
if (ex != null)
sb.append(ex.toString()).append("\n").append(android.util.Log.getStackTraceString(ex));
if (log != null)
sb.append(log);
String body = "" + TextUtils.htmlEncode(sb.toString()) + "
";
EntityMessage draft;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List 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.to = new Address[]{myAddress()};
draft.subject = context.getString(R.string.app_name) + " " +
BuildConfig.VERSION_NAME + BuildConfig.REVISION + " debug info";
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);
db.message().setMessageContent(draft.id, true, null, false, 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);
//if (MessageClassifier.isEnabled(context))
// attachClassifierData(context, draft.id, 9);
EntityOperation.queue(context, draft, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "debuginfo");
return draft;
}
static void unexpectedError(FragmentManager manager, Throwable ex) {
unexpectedError(manager, ex, true);
}
static void unexpectedError(FragmentManager manager, Throwable ex, boolean report) {
Log.e(ex);
if (ex instanceof OutOfMemoryError)
report = false;
Bundle args = new Bundle();
args.putSerializable("ex", ex);
args.putBoolean("report", report);
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) {
final Throwable ex = (Throwable) getArguments().getSerializable("ex");
boolean report = getArguments().getBoolean("report", true);
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);
tvError.setText(Log.formatThrowable(ex, false));
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.cancel, null);
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() {
@Override
protected Long onExecute(Context context, Bundle args) throws Throwable {
return Log.getDebugInfo(context, R.string.title_unexpected_info_remark, ex, 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) {
if (ex instanceof IllegalArgumentException)
ToastEx.makeText(context, ex.getMessage(), Toast.LENGTH_LONG).show();
else
ToastEx.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
}
}.execute(getContext(), getActivity(), new Bundle(), "error:unexpected");
}
});
return builder.create();
}
}
private static StringBuilder getAppInfo(Context context) {
StringBuilder sb = new StringBuilder();
ContentResolver resolver = context.getContentResolver();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
PackageManager pm = context.getPackageManager();
String installer = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID);
int targetSdk = -1;
try {
ApplicationInfo ai = pm.getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
targetSdk = ai.targetSdkVersion;
} catch (PackageManager.NameNotFoundException ex) {
sb.append(ex).append("\r\n");
}
// Get version info
sb.append(String.format("%s %s/%d%s%s%s%s\r\n",
context.getString(R.string.app_name),
BuildConfig.VERSION_NAME + BuildConfig.REVISION,
Helper.hasValidFingerprint(context) ? 1 : 3,
BuildConfig.PLAY_STORE_RELEASE ? "p" : "",
Helper.hasPlayStore(context) ? "s" : "",
BuildConfig.DEBUG ? "d" : "",
ActivityBilling.isPro(context) ? "+" : "-"));
sb.append(String.format("Package: %s\r\n", BuildConfig.APPLICATION_ID));
sb.append(String.format("Android: %s (SDK %d/%d)\r\n",
Build.VERSION.RELEASE, Build.VERSION.SDK_INT, targetSdk));
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));
}
sb.append(String.format("Installer: %s\r\n", installer));
sb.append(String.format("Installed: %s\r\n", new Date(Helper.getInstallTime(context))));
sb.append(String.format("Now: %s\r\n", new Date()));
sb.append("\r\n");
// 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("uid: %d\r\n", android.os.Process.myUid()));
sb.append("\r\n");
sb.append(String.format("Processors: %d\r\n", Runtime.getRuntime().availableProcessors()));
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
sb.append(String.format("Memory class: %d/%d MB Total: %s\r\n",
am.getMemoryClass(), am.getLargeMemoryClass(), Helper.humanReadableByteCount(mi.totalMem)));
long storage_available = Helper.getAvailableStorageSpace();
long storage_total = Helper.getTotalStorageSpace();
long storage_used = Helper.getSize(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)));
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)));
}
try {
int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
sb.append(context.getString(R.string.title_advanced_aes_key_size,
Helper.humanReadableByteCount(maxKeySize, false))).append("\r\n");
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
sb.append("\r\n");
Locale slocale = Resources.getSystem().getConfiguration().locale;
String language = prefs.getString("language", null);
sb.append(String.format("Locale: def=%s sys=%s lang=%s\r\n",
Locale.getDefault(), slocale, language));
String charset = MimeUtility.getDefaultJavaCharset();
sb.append(String.format("Default charset: %s/%s\r\n", charset, MimeUtility.mimeCharset(charset)));
sb.append("Transliterate: ")
.append(TextHelper.canTransliterate())
.append("\r\n");
sb.append("\r\n");
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point dim = new Point();
display.getSize(dim);
float density = context.getResources().getDisplayMetrics().density;
sb.append(String.format("Density %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("\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) ? "!!!" : ""));
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) {
UsageStatsManager usm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
int bucket = usm.getAppStandbyBucket();
boolean inactive = usm.isAppInactive(BuildConfig.APPLICATION_ID);
sb.append(String.format("Standby bucket: %d-%s;p inactive: %b\r\n",
bucket, Helper.getStandbyBucketName(bucket), 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");
sb.append(String.format("Configuration: %s\r\n", config));
sb.append("\r\n");
for (Provider p : Security.getProviders())
sb.append(p).append("\r\n");
sb.append("\r\n");
try {
PackageInfo pi = context.getPackageManager()
.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);
sb.append(pi.requestedPermissions[i].replace("android.permission.", ""))
.append('=').append(granted).append("\r\n");
}
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
sb.append("\r\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
// https://developer.android.com/reference/android/app/ApplicationExitInfo
List infos = am.getHistoricalProcessExitReasons(
context.getPackageName(), 0, 20);
for (ApplicationExitInfo info : infos)
sb.append(String.format("%s: %s %s/%s reason=%d status=%d importance=%d\r\n",
new Date(info.getTimestamp()), info.getDescription(),
Helper.humanReadableByteCount(info.getPss() * 1024L),
Helper.humanReadableByteCount(info.getRss() * 1024L),
info.getReason(), info.getStatus(), info.getReason()));
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
sb.append("\r\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
try {
Map stats = Debug.getRuntimeStats();
for (String key : stats.keySet())
sb.append(key).append('=').append(stats.get(key)).append("\r\n");
sb.append("\r\n");
} catch (Throwable ex) {
sb.append(ex).append("\r\n");
}
return sb;
}
private static void attachSettings(Context context, long id, int sequence) throws IOException {
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 settings = prefs.getAll();
List keys = new ArrayList<>(settings.keySet());
Collections.sort(keys);
for (String key : keys)
size += write(os, key + "=" + settings.get(key) + "\r\n");
}
db.attachment().setDownloaded(attachment.id, size);
}
private static void attachAccounts(Context context, long id, int sequence) throws IOException {
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 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 auto_optimize = prefs.getBoolean("auto_optimize", false);
boolean schedule = prefs.getBoolean("schedule", false);
size += write(os, "accounts=" + accounts.size() +
" enabled=" + enabled +
" interval=" + pollInterval + "\r\n" +
" metered=" + metered +
" VPN=" + ConnectionHelper.vpnActive(context) +
" NetGuard=" + Helper.isInstalled(context, "eu.faircode.netguard") + "\r\n" +
" optimizing=" + (ignoring == null ? null : !ignoring) +
" auto_optimize=" + auto_optimize +
"\r\n\r\n");
if (schedule) {
int minuteStart = prefs.getInt("schedule_start", 0);
int minuteEnd = prefs.getInt("schedule_end", 0);
size += write(os, "schedule " +
(minuteStart / 60) + ":" + (minuteStart % 60) + "..." +
(minuteEnd / 60) + ":" + (minuteEnd % 60) + "\r\n");
String[] daynames = new DateFormatSymbols().getWeekdays();
for (int i = 0; i < 7; i++) {
boolean day = prefs.getBoolean("schedule_day" + i, true);
size += write(os, "schedule " + daynames[i + 1] + "=" + day + "\r\n");
}
size += write(os, "\r\n");
}
for (EntityAccount account : accounts) {
if (account.synchronize) {
int content = 0;
int messages = 0;
List folders = db.folder().getFoldersEx(account.id);
for (TupleFolderEx folder : folders) {
content += folder.content;
messages += folder.messages;
}
size += write(os, account.name +
" " + (account.protocol == EntityAccount.TYPE_IMAP ? "IMAP" : "POP") + "/" + account.auth_type +
" " + account.host + ":" + account.port + "/" + account.encryption +
" sync=" + account.synchronize +
" exempted=" + account.poll_exempted +
" poll=" + account.poll_interval +
" ondemand=" + account.ondemand +
" messages=" + content + "/" + messages +
" " + account.state +
(account.last_connected == null ? "" : " " + dtf.format(account.last_connected)) +
"\r\n");
if (folders.size() > 0)
Collections.sort(folders, folders.get(0).getComparator(context));
for (TupleFolderEx folder : folders)
if (folder.synchronize)
size += write(os, "- " + folder.name + " " + folder.type +
(folder.unified ? " unified" : "") +
(folder.notify ? " notify" : "") +
" poll=" + folder.poll + "/" + folder.poll_factor +
" days=" + folder.sync_days + "/" + folder.keep_days +
" msgs=" + folder.content + "/" + folder.messages +
" " + folder.state +
(folder.last_sync == null ? "" : " " + dtf.format(folder.last_sync)) +
"\r\n");
size += write(os, "\r\n");
}
}
for (EntityAccount account : accounts)
if (account.synchronize)
try {
JSONObject jaccount = account.toJSON();
jaccount.put("state", account.state == null ? "null" : account.state);
jaccount.put("warning", account.warning);
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 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("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("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 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);
}
private static void attachNetworkInfo(Context context, long id, int sequence) throws IOException {
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);
long size = 0;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ani = cm.getActiveNetworkInfo();
if (ani != null)
size += write(os, ani.toString() +
" connected=" + ani.isConnected() +
" metered=" + cm.isActiveNetworkMetered() +
" roaming=" + ani.isRoaming() +
" type=" + ani.getType() + "/" + ani.getTypeName() +
"\r\n\r\n");
Network active = ConnectionHelper.getActiveNetwork(context);
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");
LinkProperties props = cm.getLinkProperties(network);
size += write(os, " props=" + props + "\r\n");
size += write(os, "\r\n");
}
try {
Enumeration 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");
}
size += write(os, "VPN active=" + ConnectionHelper.vpnActive(context) + "\r\n\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, "Airplane=" + ConnectionHelper.airplaneMode(context) + "\r\n");
}
db.attachment().setDownloaded(attachment.id, size);
}
private static void attachLog(Context context, long id, int sequence) throws IOException {
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))
size += write(os, String.format("%s [%d:%d:%d:%d] %s\r\n",
TF.format(entry.time),
entry.type.ordinal(),
(entry.account == null ? 0 : entry.account),
(entry.folder == null ? 0 : entry.folder),
(entry.message == null ? 0 : entry.message),
entry.data));
}
db.attachment().setDownloaded(attachment.id, size);
}
private static void attachOperations(Context context, long id, int sequence) throws IOException {
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);
}
private static void attachTasks(Context context, long id, int sequence) throws IOException {
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);
}
private static void attachLogcat(Context context, long id, int sequence) throws IOException {
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);
Process proc = null;
File file = attachment.getFile(context);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
String[] cmd = new String[]{"logcat",
"-d",
"-v", "threadtime",
//"-t", "1000",
Log.TAG + ":I"};
proc = Runtime.getRuntime().exec(cmd);
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);
} finally {
if (proc != null)
proc.destroy();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static void attachNotificationInfo(Context context, long id, int sequence) throws IOException {
DB db = DB.getInstance(context);
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = sequence;
attachment.name = "channel.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 = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
String name;
int filter = nm.getCurrentInterruptionFilter();
switch (filter) {
case NotificationManager.INTERRUPTION_FILTER_UNKNOWN:
name = "Unknown";
break;
case NotificationManager.INTERRUPTION_FILTER_ALL:
name = "All";
break;
case NotificationManager.INTERRUPTION_FILTER_PRIORITY:
name = "Priority";
break;
case NotificationManager.INTERRUPTION_FILTER_NONE:
name = "None";
break;
case NotificationManager.INTERRUPTION_FILTER_ALARMS:
name = "Alarms";
break;
default:
name = Integer.toString(filter);
}
size += write(os, String.format("Interruption filter allow=%s\r\n\r\n", name));
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.toString() + "\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));
}
db.attachment().setDownloaded(attachment.id, size);
}
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);
File target = attachment.getFile(context);
Helper.copy(source, target);
db.attachment().setDownloaded(attachment.id, target.length());
}
private static int write(OutputStream os, String text) throws IOException {
byte[] bytes = text.getBytes();
os.write(bytes);
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;
}
}