Misc optimizations

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/242/head
androidacy-user 2 years ago
parent 816d94aee4
commit 86c46de069

@ -38,7 +38,7 @@ android {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
'proguard-rules.pro'
}
debug {
applicationIdSuffix '.debug'
@ -57,6 +57,13 @@ android {
dimension "type"
buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true"
buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true"
if (hasSentryConfig) {
Properties properties = new Properties()
try (FileInputStream fis = new FileInputStream(sentryConfigFile)) {
properties.load(fis)
}
buildConfigField "String", "SENTRY_TOKEN", '"' + properties.getProperty("auth." + "token") + '"'
}
// Get the androidacy client ID from the androidacy.properties
Properties properties = new Properties()
// If androidacy.properties doesn't exist, use the default client ID which is heavily
@ -91,6 +98,14 @@ android {
// Disable crash reporting for F-Droid flavor by default
buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false"
if (hasSentryConfig) {
Properties properties = new Properties()
try (FileInputStream fis = new FileInputStream(sentryConfigFile)) {
properties.load(fis)
}
buildConfigField "String", "SENTRY_TOKEN", '"' + properties.getProperty("auth." + "token") + '"'
}
// Repo with ads or tracking feature are disabled by default for the
// F-Droid flavor.
buildConfigField("java.util.List<String>",
@ -128,7 +143,7 @@ aboutLibraries {
// This is because gradle doesn't allow to enable/disable plugins conditionally
sentry {
// Disable sentry on F-Droid flavor
ignoredFlavors = hasSentryConfig ? [] : ["default", "fdroid"]
ignoredFlavors = []
// Disables or enables the handling of Proguard mapping for Sentry.
// If enabled the plugin will generate a UUID and will take care of
@ -164,14 +179,14 @@ sentry {
// Does auto instrumentation for specified features through bytecode manipulation.
// Default is enabled.
tracingInstrumentation {
enabled = hasSentryConfig
enabled = true
}
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
// Default is enabled.
// Only available v3.1.0 and above.
autoInstallation {
enabled = hasSentryConfig
enabled = true
// Specifies a version of the sentry-android SDK and fragment, timber and okhttp integrations.
//
@ -188,9 +203,6 @@ sentry {
configurations {
implementation.exclude group: 'org.jetbrains', module: 'annotations'
if (!hasSentryConfig) {
implementation.exclude group: 'io.sentry', module: 'sentry-android'
}
}
dependencies {
@ -223,11 +235,7 @@ dependencies {
implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.9'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
if (hasSentryConfig) {
// Error reporting
implementation 'io.sentry:sentry-android:6.9.2'
}
implementation 'io.sentry:sentry-android:6.9.2'
// Markdown
implementation "io.noties.markwon:core:4.6.2"
@ -241,6 +249,9 @@ dependencies {
// Test
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
// Add okhttp logging interceptor if debug build
debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.10'
}
if (hasSentryConfig) {
@ -257,8 +268,7 @@ if (hasSentryConfig) {
}
final String sentrySrc = hasSentryConfig ? 'src/sentry/java' : 'src/sentryless/java'
final String sentryManifestSrc = hasSentryConfig ?
'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml'
final String sentryManifestSrc = hasSentryConfig ? 'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml'
android {
sourceSets {

@ -13,13 +13,17 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
@ -50,7 +54,19 @@ import com.fox2code.mmm.utils.NoodleDebug;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import eightbitlab.com.blurview.BlurView;
import io.sentry.Sentry;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
private static final String TAG = "MainActivity";
@ -241,6 +257,104 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
}, true);
ExternalHelper.INSTANCE.refreshHelper(this);
this.initMode = false;
// Show an material alert dialog if lastEventId is not "" or null in the private sentry shared preferences
if (MainApplication.isCrashReportingEnabled()) {
SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE);
String lastExitReason = preferences.getString("lastExitReason", "");
if (lastExitReason.equals("crash")) {
String lastEventId = preferences.getString("lastEventId", "");
if (!lastEventId.equals("")) {
// Three edit texts for the user to enter their email, name and a description of the issue
EditText email = new EditText(this);
email.setHint(R.string.email);
email.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
EditText name = new EditText(this);
name.setHint(R.string.name);
name.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
EditText description = new EditText(this);
description.setHint(R.string.additional_info);
// Set description to be multiline and auto resize
description.setSingleLine(false);
description.setMaxHeight(1000);
// Make description required-
new MaterialAlertDialogBuilder(this).setCancelable(false).setTitle(R.string.sentry_dialogue_title).setMessage(R.string.sentry_dialogue_message).setView(new LinearLayout(this) {{
setOrientation(LinearLayout.VERTICAL);
setPadding(40, 20, 40, 10);
addView(email);
addView(name);
addView(description);
}}).setPositiveButton(R.string.submit, (dialog, which) -> {
// Make sure the user has entered a description
if (description.getText().toString().equals("")) {
Toast.makeText(this, R.string.sentry_dialogue_no_description, Toast.LENGTH_LONG).show();
dialog.cancel();
}
preferences.edit().remove("lastEventId").apply();
// Prevent strict mode violation
new Thread(() -> {
try {
// Use HTTPConnectionURL to send a post request to the sentry server
ExperimentalCronetEngine cronetEngine = new ExperimentalCronetEngine.Builder(this).build();
CronetURLStreamHandlerFactory cronetURLStreamHandlerFactory = new CronetURLStreamHandlerFactory(cronetEngine);
URL.setURLStreamHandlerFactory(cronetURLStreamHandlerFactory);
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);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setChunkedStreamingMode(0);
connection.connect();
// Write the JSON data to the output stream
OutputStream outputStream = connection.getOutputStream();
// Name and email are optional, so if they are empty, set them to
// Anonymous and Anonymous respectively
String nameString = name.getText().toString();
String emailString = email.getText().toString();
if (nameString.equals("")) nameString = "Anonymous";
if (emailString.equals("")) emailString = "Anonymous";
String finalNameString = nameString;
String finalEmailString = emailString;
outputStream.write(new JSONObject() {{
put("event_id", lastEventId);
put("name", finalNameString);
put("email", finalEmailString);
put("comments", description.getText().toString());
}}.toString().getBytes());
outputStream.flush();
outputStream.close();
// Read the response
InputStream inputStream = connection.getInputStream();
byte[] buffer = new byte[1024];
int length;
StringBuilder stringBuilder = new StringBuilder();
while ((length = inputStream.read(buffer)) != -1) {
stringBuilder.append(new String(buffer, 0, length));
}
inputStream.close();
connection.disconnect();
if (BuildConfig.DEBUG) Log.d("Sentry", stringBuilder.toString());
// Valid responses will be a json object with a key "id"
JSONObject jsonObject = new JSONObject(stringBuilder.toString());
if (jsonObject.has("id")) {
// Show a toast to the user to confirm that the feedback has been sent
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG).show());
} else {
// Show a toast to the user to confirm that the feedback has not been sent
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
} catch (IOException | JSONException 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();
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
preferences.edit().remove("lastEventId").apply();
Sentry.captureMessage("User has ignored the crash");
}).show();
}
}
}
}
private void cardIconifyUpdate() {

@ -330,7 +330,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
} else {
findPreference("pref_crash").setOnPreferenceClickListener(preference -> {
// Hard crash the app
throw new Error("This is a test crash");
throw new RuntimeException("This is a test crash");
});
}
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND || !MainApplication.isDeveloper()) {
@ -645,9 +645,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
prefAndroidacyRepoApiKey.setPositiveButtonText(R.string.save_api_key);
prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> {
if (originalApiKeyRef[0].equals(newValue)) return true; // Skip if nothing changed.
// Curious if this actually works - so crash the app on purpose
// throw new RuntimeException("This is a test crash");
if (originalApiKeyRef[0].equals(newValue)) return true;
// get original api key
String apiKey = String.valueOf(newValue);
// Show snack bar with indeterminate progress

@ -19,7 +19,6 @@ import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.repo.RepoManager;
import com.google.net.cronet.okhttptransport.CronetCallFactory;
import com.google.net.cronet.okhttptransport.CronetInterceptor;
import org.chromium.net.CronetEngine;
@ -54,6 +53,7 @@ import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.dnsoverhttps.DnsOverHttps;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.BufferedSink;
public class Http {
@ -140,13 +140,6 @@ public class Http {
return chain.proceed(request.build());
});
// Add cronet interceptor
// install cronet
/*try {
// Detect if cronet is installed
CronetProviderInstaller.installProvider(mainApplication);
} catch (Exception e) {
Log.e(TAG, "Failed to install cronet", e);
}*/
// init cronet
try {
// Load the cronet library
@ -165,8 +158,7 @@ public class Http {
}
builder.setStoragePath(mainApplication.getCacheDir().getAbsolutePath() + "/cronet");
builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 10 * 1024 * 1024);
CronetEngine engine =
builder.build();
CronetEngine engine = builder.build();
httpclientBuilder.addInterceptor(CronetInterceptor.newBuilder(engine).build());
} catch (Exception e) {
Log.e(TAG, "Failed to init cronet", e);
@ -179,6 +171,13 @@ public class Http {
httpClient = followRedirects(httpclientBuilder, true).build();
followRedirects(httpclientBuilder, false).build();
httpclientBuilder.dns(fallbackDNS);
if (BuildConfig.DEBUG) {
// Enable logging
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(s -> Log.d(TAG, s));
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpclientBuilder.addInterceptor(logging);
Log.d(TAG, "OkHttp logging enabled");
}
httpClientDoH = followRedirects(httpclientBuilder, true).build();
followRedirects(httpclientBuilder, false).build();
httpclientBuilder.cache(new Cache(new File(mainApplication.getCacheDir(), "http_cache"), 16L * 1024L * 1024L)); // 16Mib of cache
@ -234,8 +233,7 @@ public class Http {
@SuppressWarnings("resource")
public static byte[] doHttpGet(String url, boolean allowCache) throws IOException {
checkNeedBlockAndroidacyRequest(url);
Response response =
(allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code());
@ -263,12 +261,15 @@ public class Http {
private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException {
if (BuildConfig.DEBUG) Log.d(TAG, "POST " + url + " " + data);
checkNeedBlockAndroidacyRequest(url);
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute();
Response response;
response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute();
if (response.isRedirect()) {
return response.request().url().uri().toString();
}
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
if (BuildConfig.DEBUG)
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code() + ", body: " + response.body().string());
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}

@ -204,10 +204,14 @@
<string name="transparent_theme_dialogue_title">You are setting a transparent theme</string>
<string name="transparent_theme_dialogue_message">Transparent themes may have some inconsistencies and may not work on all ROMs. In additon, monet and blur will be disabled. You can change back at any time.</string>
<string name="custom_repo_always_on">Custom repos are always on until you remove them.</string>
<string name="sentry_dialogue_message">We encountered an error, please help us improve the app by adding some information about the error.</string>
<string name="sentry_dialogue_title">Sentry report</string>
<string name="sentry_dialogue_message">We encountered an error! Please help us improve the app
by adding some information about the error below. Name and email are optional.</string>
<string name="sentry_dialogue_title">Oops! Something went wrong.</string>
<string name="name">Name</string>
<string name="email">Email</string>
<string name="additional_info">Tell us what happened</string>
<string name="submit">Submit</string>
<string name="sentry_dialogue_failed_toast">Could not submit feedback</string>
<string name="sentry_dialogue_success">Submitted feedback</string>
<string name="sentry_dialogue_no_description">Please provide a description/comment for the issue</string>
</resources>

@ -3,26 +3,26 @@ package com.fox2code.mmm.sentry;
import static io.sentry.TypeCheckHint.SENTRY_TYPE_CHECK_HINT;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.EditText;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
import io.sentry.JsonObjectWriter;
import io.sentry.NoOpLogger;
import io.sentry.Sentry;
import io.sentry.UserFeedback;
import io.sentry.android.core.SentryAndroid;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.hints.DiskFlushNotification;
@ -36,7 +36,7 @@ public class SentryMain {
* Initialize Sentry
* Sentry is used for crash reporting and performance monitoring. The SDK is explcitly configured not to send PII, and server side scrubbing of sensitive data is enabled (which also removes IP addresses)
*/
@SuppressLint("RestrictedApi")
@SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"})
public static void initialize(final MainApplication mainApplication) {
SentryAndroid.init(mainApplication, options -> {
// If crash reporting is disabled, stop here.
@ -96,46 +96,10 @@ public class SentryMain {
Log.i(TAG, stringBuilder.toString());
}
if (MainApplication.isCrashReportingEnabled()) {
// Get user feedback
SentryId sentryId = event.getEventId();
if (sentryId != null) {
UserFeedback userFeedback = new UserFeedback(sentryId);
// Get the current activity
Activity context = MainApplication.getINSTANCE().getLastCompatActivity();
// Create a material dialog
new Handler(Looper.getMainLooper()).post(() -> {
if (context != null) {
// Show fields for name, email, and comment, and two buttons: "Submit" and "Cancel"
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(R.string.sentry_dialogue_title);
builder.setMessage(R.string.sentry_dialogue_message);
// Add the text fields, set the text to the previously entered values
EditText name = new EditText(context);
name.setHint(R.string.name);
builder.setView(name);
EditText email = new EditText(context);
email.setHint(R.string.email);
builder.setView(email);
EditText comment = new EditText(context);
comment.setHint(R.string.additional_info);
builder.setView(comment);
// Add the buttons
builder.setPositiveButton(R.string.submit, (dialog, id) -> {
// User clicked "Submit"
userFeedback.setName(name.getText().toString());
userFeedback.setEmail(email.getText().toString());
userFeedback.setComments(comment.getText().toString());
// Send the feedback
Sentry.captureUserFeedback(userFeedback);
});
builder.setNegativeButton(R.string.cancel, (dialog, id) -> {
// User cancelled the dialog
});
// Create and show the AlertDialog
builder.create().show();
}
});
}
// Save lastEventId to private shared preferences
SharedPreferences sharedPreferences = mainApplication.getSharedPreferences("sentry", Context.MODE_PRIVATE);
sharedPreferences.edit().putString("lastEventId",
Objects.requireNonNull(event.getEventId()).toString()).apply();
return event;
} else {
// We need to do this to avoid crash delay on crash when the event is dropped
@ -154,6 +118,37 @@ public class SentryMain {
}
return breadcrumb;
});
// On uncaught exception, set the lastEventId in private sentry preferences
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
SentryId lastEventId = Sentry.captureException(throwable);
SharedPreferences.Editor editor = mainApplication.getSharedPreferences("sentry", 0).edit();
editor.putString("lastExitReason", "crash");
editor.apply();
// Start a new instance of the main activity
// The intent flags ensure that the activity is started as a new task
// and that any existing task is cleared before the activity is started
// This ensures that the activity stack is cleared and the app is restarted
// from the root activity
Intent intent = new Intent(mainApplication, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Set an alarm to restart the app one second after it is killed
// This is necessary because the app is killed before the intent is started
// and the intent is ignored if the app is not running
PendingIntent pendingIntent;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntent = PendingIntent.getActivity(mainApplication, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mainApplication, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager alarmManager = (AlarmManager) mainApplication.getSystemService(Context.ALARM_SERVICE);
if (alarmManager != null) {
alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent);
}
// Kill the app
System.exit(2);
});
}
});
}

Loading…
Cancel
Save