Changed: Move com.termux.app.utils.CrashUtils to com.termux.shared.termux.crash.TermuxCrashUtils so that plugins trigger plugin notifications too

Calls to `notifyAppCrashFromCrashLogFile()` will now be synchronized as well.
This commit is contained in:
agnostic-apollo
2022-03-16 21:13:41 +05:00
parent 4dbfc1fac8
commit 5f00531381
7 changed files with 350 additions and 248 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/ic_error_notification"/>