diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index af85b92f..5c8f2f72 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -40,6 +40,7 @@ import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY; import com.termux.app.activities.HelpActivity; import com.termux.app.activities.SettingsActivity; +import com.termux.shared.termux.crash.TermuxCrashUtils; import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; import com.termux.app.terminal.TermuxSessionsListViewController; import com.termux.app.terminal.io.TerminalToolbarViewPager; @@ -55,7 +56,6 @@ import com.termux.shared.theme.NightMode; import com.termux.shared.view.ViewUtils; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSessionClient; -import com.termux.app.utils.CrashUtils; import com.termux.view.TerminalView; import com.termux.view.TerminalViewClient; @@ -199,10 +199,11 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo if (savedInstanceState != null) mIsActivityRecreated = savedInstanceState.getBoolean(ARG_ACTIVITY_RECREATED, false); - - // Check if a crash happened on last run of the app and show a + + // Check if a crash happened on last run of the app or if a plugin crashed and show a // notification with the crash details if it did - CrashUtils.notifyAppCrashOnLastRun(this, LOG_TAG); + TermuxCrashUtils.notifyAppCrashFromCrashLogFile(this, LOG_TAG); + // Delete ReportInfo serialized object files from cache older than 14 days ReportActivity.deleteReportInfoFilesOlderThanXDays(this, 14, false); diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index f31230aa..47dcd28e 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -10,8 +10,8 @@ import android.util.Pair; import android.view.WindowManager; import com.termux.R; -import com.termux.app.utils.CrashUtils; import com.termux.shared.file.FileUtils; +import com.termux.shared.termux.crash.TermuxCrashUtils; import com.termux.shared.termux.file.TermuxFileUtils; import com.termux.shared.interact.MessageDialogUtils; import com.termux.shared.logger.Logger; @@ -256,7 +256,7 @@ final class TermuxInstaller { // Add info of all install Termux plugin apps as well since their target sdk or installation // on external/portable sd card can affect Termux app files directory access or exec. - CrashUtils.sendCrashReportNotification(activity, LOG_TAG, + TermuxCrashUtils.sendCrashReportNotification(activity, LOG_TAG, title, null, "## " + title + "\n\n" + message + "\n\n" + TermuxUtils.getTermuxDebugMarkdownString(activity), true, false, TermuxUtils.AppInfoMode.TERMUX_AND_PLUGIN_PACKAGES, true); @@ -278,7 +278,7 @@ final class TermuxInstaller { if (error != null) { Logger.logErrorAndShowToast(context, LOG_TAG, error.getMessage()); Logger.logErrorExtended(LOG_TAG, "Setup Storage Error\n" + error.toString()); - CrashUtils.sendCrashReportNotification(context, LOG_TAG, title, null, + TermuxCrashUtils.sendCrashReportNotification(context, LOG_TAG, title, null, "## " + title + "\n\n" + Error.getErrorMarkdownString(error), true, false, TermuxUtils.AppInfoMode.TERMUX_PACKAGE, true); return; @@ -319,7 +319,7 @@ final class TermuxInstaller { } catch (Exception e) { Logger.logErrorAndShowToast(context, LOG_TAG, e.getMessage()); Logger.logStackTraceWithMessage(LOG_TAG, "Setup Storage Error: Error setting up link", e); - CrashUtils.sendCrashReportNotification(context, LOG_TAG, title, null, + TermuxCrashUtils.sendCrashReportNotification(context, LOG_TAG, title, null, "## " + title + "\n\n" + Logger.getStackTracesMarkdownString(null, Logger.getStackTracesStringArray(e)), true, false, TermuxUtils.AppInfoMode.TERMUX_PACKAGE, true); } 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 ee47605a..d32005f2 100644 --- a/app/src/main/java/com/termux/app/models/UserAction.java +++ b/app/src/main/java/com/termux/app/models/UserAction.java @@ -3,7 +3,6 @@ package com.termux.app.models; public enum UserAction { ABOUT("about"), - CRASH_REPORT("crash report"), PLUGIN_EXECUTION_COMMAND("plugin execution command"), REPORT_ISSUE_FROM_TRANSCRIPT("report issue from transcript"); diff --git a/app/src/main/java/com/termux/app/utils/CrashUtils.java b/app/src/main/java/com/termux/app/utils/CrashUtils.java deleted file mode 100644 index b93e8237..00000000 --- a/app/src/main/java/com/termux/app/utils/CrashUtils.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.termux.app.utils; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.os.Environment; - -import androidx.annotation.Nullable; - -import com.termux.R; -import com.termux.shared.activities.ReportActivity; -import com.termux.shared.markdown.MarkdownUtils; -import com.termux.shared.errors.Error; -import com.termux.shared.notification.NotificationUtils; -import com.termux.shared.file.FileUtils; -import com.termux.shared.models.ReportInfo; -import com.termux.app.models.UserAction; -import com.termux.shared.termux.notification.TermuxNotificationUtils; -import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; -import com.termux.shared.termux.settings.preferences.TermuxPreferenceConstants; -import com.termux.shared.data.DataUtils; -import com.termux.shared.logger.Logger; -import com.termux.shared.android.AndroidUtils; -import com.termux.shared.termux.TermuxUtils; - -import com.termux.shared.termux.TermuxConstants; - -import java.nio.charset.Charset; - -public class CrashUtils { - - private static final String LOG_TAG = "CrashUtils"; - - /** - * Notify the user of an app crash at last run by reading the crash info from the crash log file - * at {@link TermuxConstants#TERMUX_CRASH_LOG_FILE_PATH}. The crash log file would have been - * created by {@link com.termux.shared.crash.CrashHandler}. - * - * 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 TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME} 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 notifyAppCrashOnLastRun(final Context context, final String logTagParam) { - if (context == null) return; - - TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); - if (preferences == null) return; - - // If user has disabled notifications for crashes - if (!preferences.areCrashReportNotificationsEnabled()) - 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; - - Error error; - StringBuilder reportStringBuilder = new StringBuilder(); - - // Read report string from crash log file - error = FileUtils.readTextFromFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportStringBuilder, false); - if (error != null) { - Logger.logErrorExtended(logTag, error.toString()); - return; - } - - // Move crash log file to backup location if it exists - error = FileUtils.moveRegularFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, TermuxConstants.TERMUX_CRASH_LOG_BACKUP_FILE_PATH, true); - if (error != null) { - Logger.logErrorExtended(logTag, error.toString()); - } - - String reportString = reportStringBuilder.toString(); - - if (reportString.isEmpty()) - return; - - Logger.logDebug(logTag, "A crash log file found at \"" + TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH + "\"."); - - sendCrashReportNotification(context, logTag, null, null, reportString, false, false, null, false); - } - }.start(); - } - - /** - * Send a crash report notification for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} - * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. - * - * @param context The {@link Context} for operations. - * @param logTag The log tag to use for logging. - * @param title The title for the crash report and notification. - * @param notificationTextString The text of the notification. - * @param message The message for the crash report. - * @param forceNotification If set to {@code true}, then a notification will be shown - * regardless of if pending intent is {@code null} or - * {@link TermuxPreferenceConstants.TERMUX_APP#KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED} - * is {@code false}. - * @param showToast If set to {@code true}, then a toast will be shown for {@code notificationTextString}. - * @param appInfoMode The {@link TermuxUtils.AppInfoMode} to use to add app info to the message. - * Set to {@code null} if app info should not be appended to the message. - * @param addDeviceInfo If set to {@code true}, then device info should be appended to the message. - */ - public static void sendCrashReportNotification(final Context context, String logTag, - CharSequence title, - String notificationTextString, - String message, boolean forceNotification, - boolean showToast, - TermuxUtils.AppInfoMode appInfoMode, - boolean addDeviceInfo) { - if (context == null) return; - - TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); - if (preferences == null) return; - - // If user has disabled notifications for crashes - if (!preferences.areCrashReportNotificationsEnabled() && !forceNotification) - return; - - logTag = DataUtils.getDefaultIfNull(logTag, LOG_TAG); - - if (showToast) - Logger.showToast(context, notificationTextString, true); - - // Send a notification to show the crash log which when clicked will open the {@link ReportActivity} - // to show the details of the crash - if (title == null || title.toString().isEmpty()) - title = TermuxConstants.TERMUX_APP_NAME + " Crash Report"; - - Logger.logDebug(logTag, "Sending \"" + title + "\" notification."); - - StringBuilder reportString = new StringBuilder(message); - - if (appInfoMode != null) - reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(context, appInfoMode)); - - if (addDeviceInfo) - reportString.append("\n\n").append(AndroidUtils.getDeviceInfoMarkdownString(context)); - - String userActionName = UserAction.CRASH_REPORT.getName(); - - ReportInfo reportInfo = new ReportInfo(userActionName, logTag, title.toString()); - reportInfo.setReportString(reportString.toString()); - reportInfo.setReportStringSuffix("\n\n" + TermuxUtils.getReportIssueMarkdownString(context)); - reportInfo.setAddReportInfoHeaderToMarkdown(true); - reportInfo.setReportSaveFileLabelAndPath(userActionName, - Environment.getExternalStorageDirectory() + "/" + - FileUtils.sanitizeFileName(TermuxConstants.TERMUX_APP_NAME + "-" + userActionName + ".log", true, true)); - - ReportActivity.NewInstanceResult result = ReportActivity.newInstance(context, reportInfo); - if (result.contentIntent == null) return; - - // Must ensure result code for PendingIntents and id for notification are unique otherwise will override previous - int nextNotificationId = TermuxNotificationUtils.getNextNotificationId(context); - - PendingIntent contentIntent = PendingIntent.getActivity(context, nextNotificationId, result.contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - PendingIntent deleteIntent = null; - if (result.deleteIntent != null) - deleteIntent = PendingIntent.getBroadcast(context, nextNotificationId, result.deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - // Setup the notification channel if not already set up - setupCrashReportsNotificationChannel(context); - - // Use markdown in notification - CharSequence notificationTextCharSequence = MarkdownUtils.getSpannedMarkdownText(context, notificationTextString); - //CharSequence notificationTextCharSequence = notificationTextString; - - // Build the notification - Notification.Builder builder = getCrashReportsNotificationBuilder(context, title, - notificationTextCharSequence, notificationTextCharSequence, contentIntent, deleteIntent, - NotificationUtils.NOTIFICATION_MODE_VIBRATE); - if (builder == null) return; - - // Send the notification - NotificationManager notificationManager = NotificationUtils.getNotificationManager(context); - if (notificationManager != null) - notificationManager.notify(nextNotificationId, builder.build()); - } - - /** - * Get {@link Notification.Builder} for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} - * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. - * - * @param context The {@link Context} for operations. - * @param title The title for the notification. - * @param notificationText The second line text of the notification. - * @param notificationBigText The full text of the notification that may optionally be styled. - * @param contentIntent The {@link PendingIntent} which should be sent when notification is clicked. - * @param deleteIntent The {@link PendingIntent} which should be sent when notification is deleted. - * @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 notificationText, final CharSequence notificationBigText, final PendingIntent contentIntent, final PendingIntent deleteIntent, final int notificationMode) { - - Notification.Builder builder = NotificationUtils.geNotificationBuilder(context, - TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID, Notification.PRIORITY_HIGH, - title, notificationText, notificationBigText, contentIntent, deleteIntent, 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 TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} and - * {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. - * - * @param context The {@link Context} for operations. - */ - public static void setupCrashReportsNotificationChannel(final Context context) { - NotificationUtils.setupNotificationChannel(context, TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID, - TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); - } - -} diff --git a/termux-shared/src/main/java/com/termux/shared/termux/crash/TermuxCrashUtils.java b/termux-shared/src/main/java/com/termux/shared/termux/crash/TermuxCrashUtils.java index e7bc7100..69b8d408 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/crash/TermuxCrashUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/crash/TermuxCrashUtils.java @@ -1,12 +1,36 @@ package com.termux.shared.termux.crash; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.os.Environment; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.termux.shared.R; +import com.termux.shared.activities.ReportActivity; +import com.termux.shared.android.AndroidUtils; import com.termux.shared.crash.CrashHandler; +import com.termux.shared.data.DataUtils; +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; +import com.termux.shared.logger.Logger; +import com.termux.shared.markdown.MarkdownUtils; +import com.termux.shared.models.ReportInfo; +import com.termux.shared.notification.NotificationUtils; import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.TermuxConstants.TERMUX_APP; import com.termux.shared.termux.TermuxUtils; +import com.termux.shared.termux.models.UserAction; +import com.termux.shared.termux.notification.TermuxNotificationUtils; +import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; +import com.termux.shared.termux.settings.preferences.TermuxPreferenceConstants; + +import java.nio.charset.Charset; public class TermuxCrashUtils implements CrashHandler.CrashHandlerClient { @@ -29,4 +53,301 @@ public class TermuxCrashUtils implements CrashHandler.CrashHandlerClient { return TermuxUtils.getAppInfoMarkdownString(context, true); } + /** + * Notify the user of an app crash by reading the crash info from the crash log file + * at {@link TermuxConstants#TERMUX_CRASH_LOG_FILE_PATH}. The crash log file would have been + * created by {@link com.termux.shared.crash.CrashHandler}. + * + * 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 TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME} 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 currentPackageContext The {@link Context} of current package. + * @param logTagParam The log tag to use for logging. + */ + public static void notifyAppCrashFromCrashLogFile(final Context currentPackageContext, final String logTagParam) { + if (currentPackageContext == null) return; + String currentPackageName = currentPackageContext.getPackageName(); + + final Context context = TermuxUtils.getTermuxPackageContext(currentPackageContext); + if (context == null) { + Logger.logWarn(LOG_TAG, "Ignoring call to notifyAppCrash() since failed to get \"" + TermuxConstants.TERMUX_PACKAGE_NAME + "\" package context from \"" + currentPackageName + "\" context"); + return; + } + + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); + if (preferences == null) return; + + // If user has disabled notifications for crashes + if (!preferences.areCrashReportNotificationsEnabled(false)) + return; + + new Thread() { + @Override + public void run() { + notifyAppCrashFromCrashLogFileInner(context, logTagParam); + } + }.start(); + } + + private static synchronized void notifyAppCrashFromCrashLogFileInner(final Context context, final String logTagParam) { + String logTag = DataUtils.getDefaultIfNull(logTagParam, LOG_TAG); + + if (!FileUtils.regularFileExists(TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, false)) + return; + + Error error; + StringBuilder reportStringBuilder = new StringBuilder(); + + // Read report string from crash log file + error = FileUtils.readTextFromFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportStringBuilder, false); + if (error != null) { + Logger.logErrorExtended(logTag, error.toString()); + return; + } + + // Move crash log file to backup location if it exists + error = FileUtils.moveRegularFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, TermuxConstants.TERMUX_CRASH_LOG_BACKUP_FILE_PATH, true); + if (error != null) { + Logger.logErrorExtended(logTag, error.toString()); + } + + String reportString = reportStringBuilder.toString(); + + if (reportString.isEmpty()) + return; + + Logger.logDebug(logTag, "A crash log file found at \"" + TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH + "\"."); + + sendCrashReportNotification(context, logTag, null, null, reportString, false, false, null, false); + } + + + + + /** + * Send a crash report notification for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} + * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. + * + * @param currentPackageContext The {@link Context} of current package. + * @param logTag The log tag to use for logging. + * @param title The title for the crash report and notification. + * @param message The message for the crash report. + * @param throwable The {@link Throwable} for the crash report. + */ + public static void sendPluginCrashReportNotification(final Context currentPackageContext, String logTag, + CharSequence title, String message, Throwable throwable) { + TermuxCrashUtils.sendPluginCrashReportNotification(currentPackageContext, logTag, + title, message, + MarkdownUtils.getMarkdownCodeForString(Logger.getMessageAndStackTraceString(message, throwable), true), + false, false, true); + } + + /** + * Send a crash report notification for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} + * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. + * + * @param currentPackageContext The {@link Context} of current package. + * @param logTag The log tag to use for logging. + * @param title The title for the crash report and notification. + * @param notificationTextString The text of the notification. + * @param message The message for the crash report. + */ + public static void sendPluginCrashReportNotification(final Context currentPackageContext, String logTag, + CharSequence title, String notificationTextString, + String message) { + TermuxCrashUtils.sendPluginCrashReportNotification(currentPackageContext, logTag, + title, notificationTextString, message, + false, false, true); + } + + /** + * Send a crash report notification for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} + * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. + * + * @param currentPackageContext The {@link Context} of current package. + * @param logTag The log tag to use for logging. + * @param title The title for the crash report and notification. + * @param notificationTextString The text of the notification. + * @param message The message for the crash report. + * @param forceNotification If set to {@code true}, then a notification will be shown + * regardless of if pending intent is {@code null} or + * {@link TermuxPreferenceConstants.TERMUX_APP#KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED} + * is {@code false}. + * @param showToast If set to {@code true}, then a toast will be shown for {@code notificationTextString}. + * @param addDeviceInfo If set to {@code true}, then device info should be appended to the message. + */ + public static void sendPluginCrashReportNotification(final Context currentPackageContext, String logTag, + CharSequence title, String notificationTextString, + String message, boolean forceNotification, + boolean showToast, + boolean addDeviceInfo) { + TermuxCrashUtils.sendCrashReportNotification(currentPackageContext, logTag, + title, notificationTextString, "## " + title + "\n\n" + message + "\n\n", + forceNotification, showToast, TermuxUtils.AppInfoMode.TERMUX_AND_PLUGIN_PACKAGE, addDeviceInfo); + } + + /** + * Send a crash report notification for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} + * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. + * + * @param currentPackageContext The {@link Context} of current package. + * @param logTag The log tag to use for logging. + * @param title The title for the crash report and notification. + * @param notificationTextString The text of the notification. + * @param message The message for the crash report. + * @param forceNotification If set to {@code true}, then a notification will be shown + * regardless of if pending intent is {@code null} or + * {@link TermuxPreferenceConstants.TERMUX_APP#KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED} + * is {@code false}. + * @param showToast If set to {@code true}, then a toast will be shown for {@code notificationTextString}. + * @param appInfoMode The {@link TermuxUtils.AppInfoMode} to use to add app info to the message. + * Set to {@code null} if app info should not be appended to the message. + * @param addDeviceInfo If set to {@code true}, then device info should be appended to the message. + */ + public static void sendCrashReportNotification(final Context currentPackageContext, String logTag, + CharSequence title, + String notificationTextString, + String message, boolean forceNotification, + boolean showToast, + TermuxUtils.AppInfoMode appInfoMode, + boolean addDeviceInfo) { + // Note: Do not change currentPackageContext or termuxPackageContext passed to functions or things will break + + if (currentPackageContext == null) return; + String currentPackageName = currentPackageContext.getPackageName(); + + final Context termuxPackageContext = TermuxUtils.getTermuxPackageContext(currentPackageContext); + if (termuxPackageContext == null) { + Logger.logWarn(LOG_TAG, "Ignoring call to sendCrashReportNotification() since failed to get \"" + TermuxConstants.TERMUX_PACKAGE_NAME + "\" package context from \"" + currentPackageName + "\" context"); + return; + } + + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(termuxPackageContext); + if (preferences == null) return; + + // If user has disabled notifications for crashes + if (!preferences.areCrashReportNotificationsEnabled(true) && !forceNotification) + return; + + logTag = DataUtils.getDefaultIfNull(logTag, LOG_TAG); + + if (showToast) + Logger.showToast(currentPackageContext, notificationTextString, true); + + // Send a notification to show the crash log which when clicked will open the {@link ReportActivity} + // to show the details of the crash + if (title == null || title.toString().isEmpty()) + title = TermuxConstants.TERMUX_APP_NAME + " Crash Report"; + + Logger.logDebug(logTag, "Sending \"" + title + "\" notification."); + + StringBuilder reportString = new StringBuilder(message); + + if (appInfoMode != null) + reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(currentPackageContext, appInfoMode, currentPackageName)); + + if (addDeviceInfo) + reportString.append("\n\n").append(AndroidUtils.getDeviceInfoMarkdownString(currentPackageContext)); + + String userActionName = UserAction.CRASH_REPORT.getName(); + + ReportInfo reportInfo = new ReportInfo(userActionName, logTag, title.toString()); + reportInfo.setReportString(reportString.toString()); + reportInfo.setReportStringSuffix("\n\n" + TermuxUtils.getReportIssueMarkdownString(currentPackageContext)); + reportInfo.setAddReportInfoHeaderToMarkdown(true); + reportInfo.setReportSaveFileLabelAndPath(userActionName, + Environment.getExternalStorageDirectory() + "/" + + FileUtils.sanitizeFileName(TermuxConstants.TERMUX_APP_NAME + "-" + userActionName + ".log", true, true)); + + ReportActivity.NewInstanceResult result = ReportActivity.newInstance(termuxPackageContext, reportInfo); + if (result.contentIntent == null) return; + + // Must ensure result code for PendingIntents and id for notification are unique otherwise will override previous + int nextNotificationId = TermuxNotificationUtils.getNextNotificationId(termuxPackageContext); + + PendingIntent contentIntent = PendingIntent.getActivity(termuxPackageContext, nextNotificationId, result.contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + PendingIntent deleteIntent = null; + if (result.deleteIntent != null) + deleteIntent = PendingIntent.getBroadcast(termuxPackageContext, nextNotificationId, result.deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + // Setup the notification channel if not already set up + setupCrashReportsNotificationChannel(termuxPackageContext); + + // Use markdown in notification + CharSequence notificationTextCharSequence = MarkdownUtils.getSpannedMarkdownText(termuxPackageContext, notificationTextString); + //CharSequence notificationTextCharSequence = notificationTextString; + + // Build the notification + Notification.Builder builder = getCrashReportsNotificationBuilder(currentPackageContext, termuxPackageContext, + title, notificationTextCharSequence, notificationTextCharSequence, contentIntent, deleteIntent, + NotificationUtils.NOTIFICATION_MODE_VIBRATE); + if (builder == null) return; + + // Send the notification + NotificationManager notificationManager = NotificationUtils.getNotificationManager(termuxPackageContext); + if (notificationManager != null) + notificationManager.notify(nextNotificationId, builder.build()); + } + + /** + * Get {@link Notification.Builder} for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} + * and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. + * + * @param currentPackageContext The {@link Context} of current package. + * @param termuxPackageContext The {@link Context} of termux package. + * @param title The title for the notification. + * @param notificationText The second line text of the notification. + * @param notificationBigText The full text of the notification that may optionally be styled. + * @param contentIntent The {@link PendingIntent} which should be sent when notification is clicked. + * @param deleteIntent The {@link PendingIntent} which should be sent when notification is deleted. + * @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 currentPackageContext, + final Context termuxPackageContext, + final CharSequence title, + final CharSequence notificationText, + final CharSequence notificationBigText, + final PendingIntent contentIntent, + final PendingIntent deleteIntent, + final int notificationMode) { + + Notification.Builder builder = NotificationUtils.geNotificationBuilder(termuxPackageContext, + TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID, Notification.PRIORITY_HIGH, + title, notificationText, notificationBigText, contentIntent, deleteIntent, notificationMode); + + if (builder == null) return null; + + // Enable timestamp + builder.setShowWhen(true); + + // Set notification icon + builder.setSmallIcon(Icon.createWithResource(currentPackageContext, 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 TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} and + * {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}. + * + * @param context The {@link Context} for operations. + */ + public static void setupCrashReportsNotificationChannel(final Context context) { + NotificationUtils.setupNotificationChannel(context, TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID, + TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); + } + } diff --git a/termux-shared/src/main/java/com/termux/shared/termux/models/UserAction.java b/termux-shared/src/main/java/com/termux/shared/termux/models/UserAction.java new file mode 100644 index 00000000..fe89d0f8 --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/termux/models/UserAction.java @@ -0,0 +1,17 @@ +package com.termux.shared.termux.models; + +public enum UserAction { + + CRASH_REPORT("crash report"); + + private final String name; + + UserAction(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/termux-shared/src/main/res/raw/keep.xml b/termux-shared/src/main/res/raw/keep.xml new file mode 100644 index 00000000..7b29406c --- /dev/null +++ b/termux-shared/src/main/res/raw/keep.xml @@ -0,0 +1,3 @@ + +