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 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 = "" + 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.from = new Address[]{new InternetAddress(identity.email, identity.name, StandardCharsets.UTF_8.name())};
+ draft.to = new Address[]{Log.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 system info
+ 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;
+ }
+
+ 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", Log.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 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 settings = prefs.getAll();
+ List 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 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 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 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 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 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 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 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 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 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 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 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, Log.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, "\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 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 metas = Log.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 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 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 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 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 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 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 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 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 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() {
+ @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 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 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 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());
+ }
+
+ 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 String getDays(Integer days) {
+ if (days == null)
+ return "?";
+ else
+ return (days == Integer.MAX_VALUE ? "∞" : Integer.toString(days));
+ }
+
+ static String getTokenInfo(String password, int auth_type) throws JSONException {
+ AuthState authState = AuthState.jsonDeserialize(password);
+ Long expiration = authState.getAccessTokenExpirationTime();
+ TokenResponse t = authState.getLastTokenResponse();
+ Set 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));
+ }
+
+ private static int write(OutputStream os, String text) throws IOException {
+ byte[] bytes = text.getBytes();
+ os.write(bytes); // TODO CASA system info
+ return bytes.length;
+ }
+
+ static void writeCrashLog(Context context, Throwable ex) {
+ File file = new File(context.getFilesDir(), CRASH_LOG_NAME);
+ Log.w("Writing exception to " + file);
+
+ try (FileWriter out = new FileWriter(file, true)) {
+ out.write(BuildConfig.VERSION_NAME + BuildConfig.REVISION + " " + new Date() + "\r\n");
+ ThrowableWrapper w = new ThrowableWrapper(ex);
+ out.write(w.toSafeString() + "\r\n");
+ out.write(w.getSafeStackTraceString() + "\r\n");
+ } catch (IOException e) {
+ Log.e(e);
+ }
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java
index 474eee3943..e3009a6923 100644
--- a/app/src/main/java/eu/faircode/email/ActivitySetup.java
+++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java
@@ -499,11 +499,15 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
@Override
protected Long onExecute(Context context, Bundle args) throws IOException, JSONException {
- return Log.getDebugInfo(context, "setup", R.string.title_debug_info_remark, null, null, args).id;
+ EntityMessage m = DebugHelper.getDebugInfo(context,
+ "setup", R.string.title_debug_info_remark, null, null, args);
+ return (m == null ? null : m.id);
}
@Override
protected void onExecuted(Bundle args, Long id) {
+ if (id == null)
+ return;
startActivity(new Intent(ActivitySetup.this, ActivityCompose.class)
.putExtra("action", "edit")
.putExtra("id", id));
diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java
index 574d36c348..3eff57835c 100644
--- a/app/src/main/java/eu/faircode/email/ActivityView.java
+++ b/app/src/main/java/eu/faircode/email/ActivityView.java
@@ -1502,7 +1502,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
new SimpleTask() {
@Override
protected Long onExecute(Context context, Bundle args) throws Throwable {
- File file = new File(context.getFilesDir(), Log.CRASH_LOG_NAME);
+ File file = new File(context.getFilesDir(), DebugHelper.CRASH_LOG_NAME);
if (file.exists()) {
StringBuilder sb = new StringBuilder();
try {
@@ -1512,7 +1512,9 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
sb.append(line).append("\r\n");
}
- return Log.getDebugInfo(context, "crash", R.string.title_crash_info_remark, null, sb.toString(), null).id;
+ EntityMessage m = DebugHelper.getDebugInfo(context,
+ "crash", R.string.title_crash_info_remark, null, sb.toString(), null);
+ return (m == null ? null : m.id);
} finally {
Helper.secureDelete(file);
}
@@ -1523,11 +1525,12 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
@Override
protected void onExecuted(Bundle args, Long id) {
- if (id != null)
- startActivity(
- new Intent(ActivityView.this, ActivityCompose.class)
- .putExtra("action", "edit")
- .putExtra("id", id));
+ if (id == null)
+ return;
+ startActivity(
+ new Intent(ActivityView.this, ActivityCompose.class)
+ .putExtra("action", "edit")
+ .putExtra("id", id));
}
@Override
@@ -1535,7 +1538,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
ToastEx.makeText(ActivityView.this,
Log.formatThrowable(ex, false), Toast.LENGTH_LONG).show();
}
- }.execute(this, new Bundle(), Log.CRASH_LOG_NAME);
+ }.execute(this, new Bundle(), DebugHelper.CRASH_LOG_NAME);
}
private void checkUpdate(boolean always) {
@@ -2277,14 +2280,17 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
protected Long onExecute(Context context, Bundle args) throws IOException, JSONException {
boolean send = args.getBoolean("send");
- long id = Log.getDebugInfo(context, "main", R.string.title_debug_info_remark, null, null, args).id;
+ EntityMessage m = DebugHelper.getDebugInfo(context,
+ "main", R.string.title_debug_info_remark, null, null, args);
+ if (m == null)
+ return null;
if (send) {
DB db = DB.getInstance(context);
try {
db.beginTransaction();
- EntityMessage draft = db.message().getMessage(id);
+ EntityMessage draft = db.message().getMessage(m.id);
if (draft != null) {
draft.folder = EntityFolder.getOutbox(context).id;
db.message().updateMessage(draft);
@@ -2301,11 +2307,14 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
}
}
- return id;
+ return m.id;
}
@Override
protected void onExecuted(Bundle args, Long id) {
+ if (id == null)
+ return;
+
boolean sent = args.getBoolean("sent");
if (sent) {
ToastEx.makeText(ActivityView.this, R.string.title_debug_info_send, Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/eu/faircode/email/ApplicationEx.java b/app/src/main/java/eu/faircode/email/ApplicationEx.java
index d22acaf51d..2457e20777 100644
--- a/app/src/main/java/eu/faircode/email/ApplicationEx.java
+++ b/app/src/main/java/eu/faircode/email/ApplicationEx.java
@@ -151,7 +151,7 @@ public class ApplicationEx extends Application
if (BuildConfig.BETA_RELEASE ||
!Helper.isPlayStoreInstall())
- Log.writeCrashLog(ApplicationEx.this, ex);
+ DebugHelper.writeCrashLog(ApplicationEx.this, ex);
if (prev != null)
prev.uncaughtException(thread, ex);
diff --git a/app/src/main/java/eu/faircode/email/Log.java b/app/src/main/java/eu/faircode/email/Log.java
index 0efd347c56..0a58a6c477 100644
--- a/app/src/main/java/eu/faircode/email/Log.java
+++ b/app/src/main/java/eu/faircode/email/Log.java
@@ -19,61 +19,23 @@ package eu.faircode.email;
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;
@@ -81,30 +43,18 @@ 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;
@@ -121,119 +71,51 @@ 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;
- static final String CRASH_LOG_NAME = "crash.log";
-
- 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 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?";
@@ -1845,98 +1727,6 @@ public class Log {
return sb.toString();
}
- static void writeCrashLog(Context context, Throwable ex) {
- File file = new File(context.getFilesDir(), CRASH_LOG_NAME);
- Log.w("Writing exception to " + file);
-
- try (FileWriter out = new FileWriter(file, true)) {
- out.write(BuildConfig.VERSION_NAME + BuildConfig.REVISION + " " + new Date() + "\r\n");
- ThrowableWrapper w = new ThrowableWrapper(ex);
- out.write(w.toSafeString() + "\r\n");
- out.write(w.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 = "" + 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.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 system info
- 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);
}
@@ -2044,11 +1834,16 @@ public class Log {
new SimpleTask() {
@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;
+ EntityMessage m = DebugHelper.getDebugInfo(context,
+ "report", R.string.title_unexpected_info_remark, ex, null, null);
+ return (m == null ? null : m.id);
}
@Override
protected void onExecuted(Bundle args, Long id) {
+ if (id == null)
+ return;
+
context.startActivity(new Intent(context, ActivityCompose.class)
.putExtra("action", "edit")
.putExtra("id", id));
@@ -2066,1679 +1861,6 @@ public class Log {
}
}
- 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 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 settings = prefs.getAll();
- List 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 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 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 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 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 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 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 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 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 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 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, "\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 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 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 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 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 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 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 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 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 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 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 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() {
- @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 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 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 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 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();
@@ -3826,12 +1948,6 @@ public class Log {
return ssb;
}
- private static int write(OutputStream os, String text) throws IOException {
- byte[] bytes = text.getBytes();
- os.write(bytes); // TODO CASA system info
- return bytes.length;
- }
-
private static long getFreeMem() {
Runtime rt = Runtime.getRuntime();
long used = (rt.totalMemory() - rt.freeMemory());