diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index e586540289..5d872ee5b0 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -34,6 +34,7 @@ import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY; import com.termux.app.activities.HelpActivity; import com.termux.app.activities.SettingsActivity; +import com.termux.app.crash.CrashUtils; import com.termux.app.settings.preferences.TermuxAppSharedPreferences; import com.termux.app.terminal.TermuxSessionsListViewController; import com.termux.app.terminal.io.TerminalToolbarViewPager; @@ -155,6 +156,10 @@ public void onCreate(Bundle savedInstanceState) { Logger.logDebug(LOG_TAG, "onCreate"); + // Check if a crash happened on last run of the app and show a + // notification with the crash details if it did + CrashUtils.notifyCrash(this, LOG_TAG); + // Load termux shared preferences and properties mPreferences = new TermuxAppSharedPreferences(this); mProperties = new TermuxSharedProperties(this); diff --git a/app/src/main/java/com/termux/app/TermuxApplication.java b/app/src/main/java/com/termux/app/TermuxApplication.java index 53d129eb2f..62713ac20f 100644 --- a/app/src/main/java/com/termux/app/TermuxApplication.java +++ b/app/src/main/java/com/termux/app/TermuxApplication.java @@ -2,6 +2,7 @@ import android.app.Application; +import com.termux.app.crash.CrashHandler; import com.termux.app.settings.preferences.TermuxAppSharedPreferences; import com.termux.app.utils.Logger; @@ -10,10 +11,14 @@ public class TermuxApplication extends Application { public void onCreate() { super.onCreate(); - updateLogLevel(); + // Set crash handler for the app + CrashHandler.setCrashHandler(this); + + // Set log level for the app + setLogLevel(); } - private void updateLogLevel() { + private void setLogLevel() { // Load the log level from shared preferences and set it to the {@link Loggger.CURRENT_LOG_LEVEL} TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(getApplicationContext()); preferences.setLogLevel(null, preferences.getLogLevel()); diff --git a/app/src/main/java/com/termux/app/activities/ReportActivity.java b/app/src/main/java/com/termux/app/activities/ReportActivity.java index ae3b5db03a..57a4ae3c03 100644 --- a/app/src/main/java/com/termux/app/activities/ReportActivity.java +++ b/app/src/main/java/com/termux/app/activities/ReportActivity.java @@ -18,7 +18,6 @@ import com.termux.app.TermuxConstants; import com.termux.app.utils.MarkdownUtils; import com.termux.app.utils.ShareUtils; -import com.termux.app.utils.TermuxUtils; import com.termux.app.models.ReportInfo; import org.commonmark.node.FencedCodeBlock; @@ -32,6 +31,7 @@ public class ReportActivity extends AppCompatActivity { private static final String EXTRA_REPORT_INFO = "report_info"; ReportInfo mReportInfo; + String mReportMarkdownString; String mReportActivityMarkdownString; @Override @@ -131,11 +131,11 @@ public void onBackPressed() { public boolean onOptionsItemSelected(final MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_item_share_report) { - if (mReportInfo != null) - ShareUtils.shareText(this, getString(R.string.title_report_text), mReportActivityMarkdownString); + if (mReportMarkdownString != null) + ShareUtils.shareText(this, getString(R.string.title_report_text), mReportMarkdownString); } else if (id == R.id.menu_item_copy_report) { - if (mReportInfo != null) - ShareUtils.copyTextToClipboard(this, mReportActivityMarkdownString, null); + if (mReportMarkdownString != null) + ShareUtils.copyTextToClipboard(this, mReportMarkdownString, null); } return false; @@ -145,7 +145,16 @@ public boolean onOptionsItemSelected(final MenuItem item) { * Generate the markdown {@link String} to be shown in {@link ReportActivity}. */ private void generateReportActivityMarkdownString() { - mReportActivityMarkdownString = ReportInfo.getReportInfoMarkdownString(this, mReportInfo); + mReportMarkdownString = ReportInfo.getReportInfoMarkdownString(mReportInfo); + + mReportActivityMarkdownString = ""; + if(mReportInfo.reportStringPrefix != null) + mReportActivityMarkdownString += mReportInfo.reportStringPrefix; + + mReportActivityMarkdownString += mReportMarkdownString; + + if(mReportInfo.reportStringSuffix != null) + mReportActivityMarkdownString += mReportInfo.reportStringSuffix; } diff --git a/app/src/main/java/com/termux/app/crash/CrashHandler.java b/app/src/main/java/com/termux/app/crash/CrashHandler.java new file mode 100644 index 0000000000..7de2f1aa35 --- /dev/null +++ b/app/src/main/java/com/termux/app/crash/CrashHandler.java @@ -0,0 +1,34 @@ +package com.termux.app.crash; + +import android.content.Context; + +import androidx.annotation.NonNull; + +/** + * Catches uncaught exceptions and logs them. + */ +public class CrashHandler implements Thread.UncaughtExceptionHandler { + + private final Context context; + private final Thread.UncaughtExceptionHandler defaultUEH; + + private CrashHandler(final Context context) { + this.context = context; + this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler(); + } + + public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { + CrashUtils.logCrash(context,thread, throwable); + defaultUEH.uncaughtException(thread, throwable); + } + + /** + * Set default uncaught crash handler of current thread to {@link CrashHandler}. + */ + public static void setCrashHandler(final Context context) { + if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CrashHandler)) { + Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(context)); + } + } + +} diff --git a/app/src/main/java/com/termux/app/crash/CrashUtils.java b/app/src/main/java/com/termux/app/crash/CrashUtils.java new file mode 100644 index 0000000000..c4d900d857 --- /dev/null +++ b/app/src/main/java/com/termux/app/crash/CrashUtils.java @@ -0,0 +1,189 @@ +package com.termux.app.crash; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import androidx.annotation.Nullable; + +import com.termux.R; +import com.termux.app.activities.ReportActivity; +import com.termux.app.file.FileUtils; +import com.termux.app.models.ReportInfo; +import com.termux.app.models.UserAction; +import com.termux.app.settings.preferences.TermuxAppSharedPreferences; +import com.termux.app.settings.preferences.TermuxPreferenceConstants; +import com.termux.app.utils.DataUtils; +import com.termux.app.utils.Logger; +import com.termux.app.utils.MarkdownUtils; +import com.termux.app.utils.NotificationUtils; +import com.termux.app.utils.TermuxUtils; + +import com.termux.app.TermuxConstants; + +import java.nio.charset.Charset; + +public class CrashUtils { + + private static final String NOTIFICATION_CHANNEL_ID_CRASH_REPORT_ERRORS = "termux_crash_reports_notification_channel"; + private static final String NOTIFICATION_CHANNEL_NAME_CRASH_REPORT_ERRORS = TermuxConstants.TERMUX_APP_NAME + " Crash Reports"; + + private static final String LOG_TAG = "CrashUtils"; + + /** + * Log a crash in the crash log file at + * {@link TermuxConstants#TERMUX_CRASH_LOG_FILE_PATH}. + * + * @param context The {@link Context} for operations. + * @param thread The {@link Thread} in which the crash happened. + * @param thread The {@link Throwable} thrown for the crash. + */ + public static void logCrash(final Context context, final Thread thread, final Throwable throwable) { + + StringBuilder reportString = new StringBuilder(); + + reportString.append("## Crash Details\n"); + reportString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Crash Thread", thread.toString(), "-")); + reportString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Crash Timestamp", TermuxUtils.getCurrentTimeStamp(), "-")); + + reportString.append("\n\n").append(Logger.getStackTracesMarkdownString("Stacktrace", Logger.getStackTraceStringArray(throwable))); + reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(context, true)); + reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context)); + + // Log report string to logcat + Logger.logError(reportString.toString()); + + // Write report string to crash log file + String errmsg = FileUtils.writeStringToFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportString.toString(), false); + if(errmsg != null) { + Logger.logError(LOG_TAG, errmsg); + } + } + + /** + * Notify the user of a previous app crash by reading the crash info from the crash log file at + * {@link TermuxConstants#TERMUX_CRASH_LOG_FILE_PATH}. + * + * If the crash log file exists and is not empty and + * {@link TermuxPreferenceConstants.TERMUX_APP#KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED} is + * enabled, then a notification will be shown for the crash on the + * {@link #NOTIFICATION_CHANNEL_NAME_CRASH_REPORT_ERRORS} channel, otherwise nothing will be done. + * + * After reading from the crash log file, it will be moved to {@link TermuxConstants#TERMUX_CRASH_LOG_BACKUP_FILE_PATH}. + * + * @param context The {@link Context} for operations. + * @param logTagParam The log tag to use for logging. + */ + public static void notifyCrash(final Context context, final String logTagParam) { + if(context == null) return; + + + TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); + // If user has disabled notifications for crashes + if (!preferences.getCrashReportNotificationsEnabled()) + return; + + new Thread() { + @Override + public void run() { + String logTag = DataUtils.getDefaultIfNull(logTagParam, LOG_TAG); + + if(!FileUtils.regularFileExists(TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, false)) + return; + + String errmsg; + StringBuilder reportStringBuilder = new StringBuilder(); + + // Read report string from crash log file + errmsg = FileUtils.readStringFromFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportStringBuilder, false); + if(errmsg != null) { + Logger.logError(logTag, errmsg); + return; + } + + // Move crash log file to backup location if it exists + FileUtils.moveRegularFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, TermuxConstants.TERMUX_CRASH_LOG_BACKUP_FILE_PATH, true); + if(errmsg != null) { + Logger.logError(logTag, errmsg); + } + + String reportString = reportStringBuilder.toString(); + + if(reportString == null || reportString.isEmpty()) + return; + + // Send a notification to show the crash log which when clicked will open the {@link ReportActivity} + // to show the details of the crash + String title = TermuxConstants.TERMUX_APP_NAME + " Crash Report"; + + Logger.logDebug(logTag, "The crash log file at \"" + TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH + "\" found. Sending \"" + title + "\" notification."); + + Intent notificationIntent = ReportActivity.newInstance(context, new ReportInfo(UserAction.CRASH_REPORT, logTag, title, null, reportString, "\n\n" + TermuxUtils.getReportIssueMarkdownString(context), true)); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + // Setup the notification channel if not already set up + setupCrashReportsNotificationChannel(context); + + // Build the notification + Notification.Builder builder = getCrashReportsNotificationBuilder(context, title, null, null, pendingIntent, NotificationUtils.NOTIFICATION_MODE_VIBRATE); + if(builder == null) return; + + // Send the notification + int nextNotificationId = NotificationUtils.getNextNotificationId(context); + NotificationManager notificationManager = NotificationUtils.getNotificationManager(context); + if(notificationManager != null) + notificationManager.notify(nextNotificationId, builder.build()); + } + }.start(); + } + + /** + * Get {@link Notification.Builder} for {@link #NOTIFICATION_CHANNEL_ID_CRASH_REPORT_ERRORS} + * and {@link #NOTIFICATION_CHANNEL_NAME_CRASH_REPORT_ERRORS}. + * + * @param context The {@link Context} for operations. + * @param title The title for the notification. + * @param notifiationText The second line text of the notification. + * @param notificationBigText The full text of the notification that may optionally be styled. + * @param pendingIntent The {@link PendingIntent} which should be sent when notification is clicked. + * @param notificationMode The notification mode. It must be one of {@code NotificationUtils.NOTIFICATION_MODE_*}. + * @return Returns the {@link Notification.Builder}. + */ + @Nullable + public static Notification.Builder getCrashReportsNotificationBuilder(final Context context, final CharSequence title, final CharSequence notifiationText, final CharSequence notificationBigText, final PendingIntent pendingIntent, final int notificationMode) { + + Notification.Builder builder = NotificationUtils.geNotificationBuilder(context, + NOTIFICATION_CHANNEL_ID_CRASH_REPORT_ERRORS, Notification.PRIORITY_HIGH, + title, notifiationText, notificationBigText, pendingIntent, notificationMode); + + if(builder == null) return null; + + // Enable timestamp + builder.setShowWhen(true); + + // Set notification icon + builder.setSmallIcon(R.drawable.ic_error_notification); + + // Set background color for small notification icon + builder.setColor(0xFF607D8B); + + // Dismiss on click + builder.setAutoCancel(true); + + return builder; + } + + /** + * Setup the notification channel for {@link #NOTIFICATION_CHANNEL_ID_CRASH_REPORT_ERRORS} and + * {@link #NOTIFICATION_CHANNEL_NAME_CRASH_REPORT_ERRORS}. + * + * @param context The {@link Context} for operations. + */ + public static void setupCrashReportsNotificationChannel(final Context context) { + NotificationUtils.setupNotificationChannel(context, NOTIFICATION_CHANNEL_ID_CRASH_REPORT_ERRORS, + NOTIFICATION_CHANNEL_NAME_CRASH_REPORT_ERRORS, NotificationManager.IMPORTANCE_HIGH); + } + +} diff --git a/app/src/main/java/com/termux/app/models/ExecutionCommand.java b/app/src/main/java/com/termux/app/models/ExecutionCommand.java index 2de23ffc9b..aa83d74722 100644 --- a/app/src/main/java/com/termux/app/models/ExecutionCommand.java +++ b/app/src/main/java/com/termux/app/models/ExecutionCommand.java @@ -415,7 +415,7 @@ public String geStackTracesLogString() { } public String geStackTracesMarkdownString() { - return Logger.getStackTracesMarkdownString("StackTraces:", Logger.getStackTraceStringArray(throwableList)); + return Logger.getStackTracesMarkdownString("StackTraces", Logger.getStackTraceStringArray(throwableList)); } diff --git a/app/src/main/java/com/termux/app/models/ReportInfo.java b/app/src/main/java/com/termux/app/models/ReportInfo.java index 7832d64602..cd12e5ed55 100644 --- a/app/src/main/java/com/termux/app/models/ReportInfo.java +++ b/app/src/main/java/com/termux/app/models/ReportInfo.java @@ -1,7 +1,5 @@ package com.termux.app.models; -import android.content.Context; - import com.termux.app.utils.MarkdownUtils; import com.termux.app.utils.TermuxUtils; @@ -15,49 +13,51 @@ public class ReportInfo implements Serializable { public String sender; /** The report title. */ public String reportTitle; - /** The markdown text for the report. */ + /** The markdown report text prefix. Will not be part of copy and share operations, etc. */ + public String reportStringPrefix; + /** The markdown report text. */ public String reportString; + /** The markdown report text suffix. Will not be part of copy and share operations, etc. */ + public String reportStringSuffix; /** If set to {@code true}, then report, app and device info will be added to the report when * markdown is generated. */ public boolean addReportInfoToMarkdown; /** The timestamp for the report. */ - public String creationTimestammp; + public String reportTimestammp; - public ReportInfo(UserAction userAction, String sender, String reportTitle, String reportString, boolean addReportInfoToMarkdown) { + public ReportInfo(UserAction userAction, String sender, String reportTitle, String reportStringPrefix, String reportString, String reportStringSuffix, boolean addReportInfoToMarkdown) { this.userAction = userAction; this.sender = sender; this.reportTitle = reportTitle; + this.reportStringPrefix = reportStringPrefix; this.reportString = reportString; + this.reportStringSuffix = reportStringSuffix; this.addReportInfoToMarkdown = addReportInfoToMarkdown; - this.creationTimestammp = TermuxUtils.getCurrentTimeStamp(); + this.reportTimestammp = TermuxUtils.getCurrentTimeStamp(); } /** * Get a markdown {@link String} for {@link ReportInfo}. * - * @param currentPackageContext The context of current package. * @param reportInfo The {@link ReportInfo} to convert. * @return Returns the markdown {@link String}. */ - public static String getReportInfoMarkdownString(final Context currentPackageContext, final ReportInfo reportInfo) { + public static String getReportInfoMarkdownString(final ReportInfo reportInfo) { if (reportInfo == null) return "null"; StringBuilder markdownString = new StringBuilder(); - markdownString.append(reportInfo.reportString); - if(reportInfo.addReportInfoToMarkdown) { markdownString.append("## Report Info\n\n"); markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("User Action", reportInfo.userAction, "-")); markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Sender", reportInfo.sender, "-")); - markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Creation Timestamp", reportInfo.creationTimestammp, "-")); - markdownString.append("\n##\n"); - - markdownString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(currentPackageContext, true)); - markdownString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(currentPackageContext)); + markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Report Timestamp", reportInfo.reportTimestammp, "-")); + markdownString.append("\n##\n\n"); } + markdownString.append(reportInfo.reportString); + return markdownString.toString(); } diff --git a/app/src/main/java/com/termux/app/models/UserAction.java b/app/src/main/java/com/termux/app/models/UserAction.java index 611304ea2f..9a27992fc4 100644 --- a/app/src/main/java/com/termux/app/models/UserAction.java +++ b/app/src/main/java/com/termux/app/models/UserAction.java @@ -2,7 +2,8 @@ public enum UserAction { - PLUGIN_EXECUTION_COMMAND("plugin execution command"); + PLUGIN_EXECUTION_COMMAND("plugin execution command"), + CRASH_REPORT("crash report"); private final String name; diff --git a/app/src/main/java/com/termux/app/utils/MarkdownUtils.java b/app/src/main/java/com/termux/app/utils/MarkdownUtils.java index 712b01b232..5d40a20393 100644 --- a/app/src/main/java/com/termux/app/utils/MarkdownUtils.java +++ b/app/src/main/java/com/termux/app/utils/MarkdownUtils.java @@ -117,6 +117,13 @@ public static String getMultiLineMarkdownStringEntry(String label, Object object return "**" + label + "**: " + def + "\n"; } + public static String getLinkMarkdownString(String label, Object object) { + if (object != null) + return "[" + label + "](" + object + ")"; + else + return label; + } + /** Check following for more info: * https://github.com/noties/Markwon/tree/v4.6.2/app-sample diff --git a/app/src/main/java/com/termux/app/utils/PluginUtils.java b/app/src/main/java/com/termux/app/utils/PluginUtils.java index 4c9bc136a4..f95dd35a30 100644 --- a/app/src/main/java/com/termux/app/utils/PluginUtils.java +++ b/app/src/main/java/com/termux/app/utils/PluginUtils.java @@ -149,7 +149,13 @@ public static void processPluginExecutionCommandError(final Context context, Str // to show the details of the error String title = TermuxConstants.TERMUX_APP_NAME + " Plugin Execution Command Error"; - Intent notificationIntent = ReportActivity.newInstance(context, new ReportInfo(UserAction.PLUGIN_EXECUTION_COMMAND, logTag, title, ExecutionCommand.getExecutionCommandMarkdownString(executionCommand), true)); + StringBuilder reportString = new StringBuilder(); + + reportString.append(ExecutionCommand.getExecutionCommandMarkdownString(executionCommand)); + reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(context, true)); + reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context)); + + Intent notificationIntent = ReportActivity.newInstance(context, new ReportInfo(UserAction.PLUGIN_EXECUTION_COMMAND, logTag, title, null, reportString.toString(), null,true)); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Setup the notification channel if not already set up diff --git a/app/src/main/java/com/termux/app/utils/TermuxUtils.java b/app/src/main/java/com/termux/app/utils/TermuxUtils.java index 9640b63fed..79ead17aea 100644 --- a/app/src/main/java/com/termux/app/utils/TermuxUtils.java +++ b/app/src/main/java/com/termux/app/utils/TermuxUtils.java @@ -11,6 +11,7 @@ import com.google.common.base.Joiner; +import com.termux.R; import com.termux.app.TermuxConstants; import java.io.BufferedReader; @@ -238,6 +239,49 @@ public static String getDeviceInfoMarkdownString(@NonNull final Context context) return markdownString.toString(); } + /** + * Get a markdown {@link String} for reporting an issue. + * + * @param context The context for operations. + * @return Returns the markdown {@link String}. + */ + public static String getReportIssueMarkdownString(@NonNull final Context context) { + if (context == null) return "null"; + + StringBuilder markdownString = new StringBuilder(); + + markdownString.append("## Report Issue"); + + markdownString.append("\n\n").append(context.getString(R.string.msg_report_issue)).append("\n"); + + //markdownString.append("\n\n### Email\n"); + //markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_SUPPORT_EMAIL, TermuxConstants.TERMUX_SUPPORT_EMAIL_MAILTO_URL)).append(" "); + + markdownString.append("\n\n### Reddit\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_REDDIT_SUBREDDIT, TermuxConstants.TERMUX_REDDIT_SUBREDDIT_URL)).append(" "); + + markdownString.append("\n\n### Github Issues for Termux apps\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_APP_NAME, TermuxConstants.TERMUX_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_API_APP_NAME, TermuxConstants.TERMUX_API_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_BOOT_APP_NAME, TermuxConstants.TERMUX_BOOT_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_FLOAT_APP_NAME, TermuxConstants.TERMUX_FLOAT_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_STYLING_APP_NAME, TermuxConstants.TERMUX_STYLING_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_TASKER_APP_NAME, TermuxConstants.TERMUX_TASKER_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_WIDGET_APP_NAME, TermuxConstants.TERMUX_WIDGET_GITHUB_ISSUES_REPO_URL)).append(" "); + + markdownString.append("\n\n### Github Issues for Termux packages\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_GAME_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_GAME_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_SCIENCE_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_SCIENCE_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_ROOT_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_ROOT_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_UNSTABLE_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_UNSTABLE_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_X11_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_X11_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); + + markdownString.append("\n##\n"); + + return markdownString.toString(); + } + public static Properties getSystemProperties() { Properties systemProperties = new Properties(); @@ -311,7 +355,7 @@ private static String getPropertyMarkdown(String label, Object value) { public static String getCurrentTimeStamp() { @SuppressLint("SimpleDateFormat") - final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); df.setTimeZone(TimeZone.getTimeZone("UTC")); return df.format(new Date()); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3a127581c..ca6a00ae73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,6 +158,8 @@ Share With Report Text + If you think this report should be reported, then copy its text from the options menu (3-dots on top right) and post an issue on one of the following links at which the report belongs at. +