You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
FoxMagiskModuleManager/app/src/main/java/com/fox2code/mmm/CrashHandler.java

176 lines
9.6 KiB
Java

package com.fox2code.mmm;
import android.annotation.SuppressLint;
import android.content.ClipboardManager;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.fox2code.foxcompat.app.FoxActivity;
import com.google.android.material.textview.MaterialTextView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import timber.log.Timber;
public class CrashHandler extends FoxActivity {
@SuppressLint("RestrictedApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
Timber.i("CrashHandler.onCreate(%s)", savedInstanceState);
// log intent with extras
Timber.d("CrashHandler.onCreate: intent=%s", getIntent());
// get exception, stacktrace, and lastEventId from intent and log them
Timber.d("CrashHandler.onCreate: exception=%s", getIntent().getSerializableExtra("exception"));
Timber.d("CrashHandler.onCreate: stacktrace=%s", getIntent().getSerializableExtra("stacktrace"));
Timber.d("CrashHandler.onCreate: lastEventId=%s", getIntent().getStringExtra("lastEventId"));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash_handler);
// set crash_details MaterialTextView to the exception passed in the intent or unknown if null
// convert stacktrace from array to string, and pretty print it (first line is the exception, the rest is the stacktrace, with each line indented by 4 spaces)
// first line is the exception, the rest is the stacktrace, with each line indented by 4 spaces. empty out the material text view first
MaterialTextView crashDetails = findViewById(R.id.crash_details);
crashDetails.setText("");
// get the exception from the intent
Throwable exception = (Throwable) getIntent().getSerializableExtra("exception");
// if the exception is null, set the crash details to "Unknown"
if (exception == null) {
crashDetails.setText(R.string.crash_details);
} else {
// if the exception is not null, set the crash details to the exception and stacktrace
// stacktrace is an StacktraceElement, so convert it to a string and replace the commas with newlines
StringWriter stringWriter = new StringWriter();
exception.printStackTrace(new java.io.PrintWriter(stringWriter));
String stacktrace = stringWriter.toString();
stacktrace = stacktrace.replace(",", "\n ");
crashDetails.setText(getString(R.string.crash_full_stacktrace, stacktrace));
}
SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE);
// get lastEventId from intent
String lastEventId = getIntent().getStringExtra("lastEventId");
// disable feedback if sentry is disabled
//noinspection ConstantConditions
if (MainApplication.isCrashReportingEnabled() && !BuildConfig.SENTRY_TOKEN.equals("") && lastEventId != null) {
// get name, email, and message fields
EditText name = findViewById(R.id.feedback_name);
EditText email = findViewById(R.id.feedback_email);
EditText description = findViewById(R.id.feedback_message);
// get submit button
findViewById(R.id.feedback_submit).setOnClickListener(v -> {
// require the feedback_message, rest is optional
if (description.getText().toString().equals("")) {
Toast.makeText(this, R.string.sentry_dialogue_empty_message, Toast.LENGTH_LONG).show();
return;
}
// if email or name is empty, use "Anonymous"
final String[] nameString = {name.getText().toString().equals("") ? "Anonymous" : name.getText().toString()};
final String[] emailString = {email.getText().toString().equals("") ? "Anonymous" : email.getText().toString()};
// Prevent strict mode violation
// create sentry userFeedback request
new Thread(() -> {
try {
HttpURLConnection connection = (HttpURLConnection) new URL("https" + "://sentry.io/api/0/projects/androidacy-i6/foxmmm/user-feedback/").openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + BuildConfig.SENTRY_TOKEN);
// Setups the JSON body
if (nameString[0].equals(""))
nameString[0] = "Anonymous";
if (emailString[0].equals(""))
emailString[0] = "Anonymous";
JSONObject body = new JSONObject();
body.put("event_id", lastEventId);
body.put("name", nameString[0]);
body.put("email", emailString[0]);
body.put("comments", description.getText().toString());
// Send the request
connection.setDoOutput(true);
connection.getOutputStream().write(body.toString().getBytes());
connection.connect();
// For debug builds, log the response code and response body
if (BuildConfig.DEBUG) {
Timber.d("Response Code: %s", connection.getResponseCode());
}
// Check if the request was successful
if (connection.getResponseCode() == 200) {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG).show());
} else {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
// close and disconnect the connection
connection.disconnect();
} catch (
JSONException |
IOException ignored) {
// Show a toast if the user feedback could not be submitted
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
}).start();
// Close the activity
finish();
// start the main activity
startActivity(getPackageManager().getLaunchIntentForPackage(getPackageName()));
});
// get restart button
findViewById(R.id.restart).setOnClickListener(v -> {
// Save the user's name and email
preferences.edit().putString("name", name.getText().toString()).putString("email", email.getText().toString()).apply();
// Restart the app
finish();
startActivity(getPackageManager().getLaunchIntentForPackage(getPackageName()));
});
} else {
// disable feedback if sentry is disabled
findViewById(R.id.feedback_name).setEnabled(false);
findViewById(R.id.feedback_email).setEnabled(false);
findViewById(R.id.feedback_message).setEnabled(false);
// fade out all the fields
findViewById(R.id.feedback_name).setAlpha(0.5f);
findViewById(R.id.feedback_email).setAlpha(0.5f);
findViewById(R.id.feedback_message).setAlpha(0.5f);
// fade out the submit button
findViewById(R.id.feedback_submit).setAlpha(0.5f);
// set feedback_text to "Crash reporting is disabled"
((MaterialTextView) findViewById(R.id.feedback_text)).setText(R.string.sentry_enable_nag);
findViewById(R.id.feedback_submit).setOnClickListener(v -> Toast.makeText(this, R.string.sentry_dialogue_disabled, Toast.LENGTH_LONG).show());
// handle restart button
// we have to explicitly enable it because it's disabled by default
findViewById(R.id.restart).setEnabled(true);
findViewById(R.id.restart).setOnClickListener(v -> {
// Restart the app
finish();
startActivity(getPackageManager().getLaunchIntentForPackage(getPackageName()));
});
}
}
public void copyCrashDetails(View view) {
// change view to a checkmark
view.setBackgroundResource(R.drawable.baseline_check_24);
// copy crash_details to clipboard
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
String crashDetails = ((MaterialTextView) findViewById(R.id.crash_details)).getText().toString();
clipboard.setPrimaryClip(android.content.ClipData.newPlainText("crash_details", crashDetails));
// show a toast
Toast.makeText(this, R.string.crash_details_copied, Toast.LENGTH_LONG).show();
// after 1 second, change the view back to a copy button
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (
InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(() -> view.setBackgroundResource(R.drawable.baseline_copy_all_24));
}).start();
}
}