From c7cc32002807ad9e386ebeeb589b4c75126fdbad Mon Sep 17 00:00:00 2001 From: androidacy-user Date: Wed, 29 Mar 2023 21:36:55 -0400 Subject: [PATCH] misc fixes Signed-off-by: androidacy-user --- app/build.gradle | 6 + app/src/main/AndroidManifest.xml | 2 + .../com/fox2code/mmm/MainApplication.java | 116 +++++++----------- .../java/com/fox2code/mmm/SetupActivity.java | 5 + .../com/fox2code/mmm/repo/RepoManager.java | 18 ++- .../fox2code/mmm/utils/sentry/SentryMain.java | 18 ++- app/src/main/res/layout/activity_setup.xml | 23 +++- app/src/main/res/values/strings.xml | 6 + app/src/main/res/xml/root_preferences.xml | 21 +++- 9 files changed, 133 insertions(+), 82 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 657decd..26f9ab7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,6 +128,10 @@ android { dimension "type" buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true" buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true" + buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING_PII", "true" + buildConfigField "boolean", "DEFAULT_ENABLE_ANALYTICS", "true" // unused for now + buildConfigField "boolean", "DEFAULT_ENABLE_ANALYTICS_PII", "true" // unused for now + buildConfigField "boolean", "ENABLE_PROTECTION", "true" if (hasSentryConfig) { Properties properties = new Properties() try (FileInputStream fis = new FileInputStream(sentryConfigFile)) { @@ -182,6 +186,8 @@ android { // Disable crash reporting for F-Droid flavor by default buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false" + buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING_PII", "true" + buildConfigField "boolean", "ENABLE_PROTECTION", "true" if (hasSentryConfig) { Properties properties = new Properties() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce8c5c1..b7248c3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="@bool/lang_support_rtl" android:testOnly="false" + android:appCategory="productivity" + android:memtagMode="async" android:theme="@style/Theme.MagiskModuleManager" android:usesCleartextTraffic="false" android:extractNativeLibs="true" diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index fab0254..df6bc0a 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -315,17 +315,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con @Override public void onCreate() { - // init timber - if (BuildConfig.DEBUG) { - Timber.plant(new Timber.DebugTree()); - } else { - if (isCrashReportingEnabled()) { - //noinspection UnstableApiUsage - Timber.plant(new SentryTimberTree(Sentry.getCurrentHub(), SentryLevel.ERROR, SentryLevel.ERROR)); - } else { - Timber.plant(new ReleaseTree()); - } - } supportedLocales.add("ar"); // supportedLocales.add("ar_SA"); supportedLocales.add("bs"); @@ -356,27 +345,41 @@ public class MainApplication extends FoxApplication implements androidx.work.Con supportedLocales.add("en"); if (INSTANCE == null) INSTANCE = this; relPackageName = this.getPackageName(); - Timber.d("Starting FoxMMM version " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + "), commit " + BuildConfig.COMMIT_HASH); super.onCreate(); - // Update SSL Ciphers if update is possible - GMSProviderInstaller.installIfNeeded(this); + SentryMain.initialize(this); + // init timber if (BuildConfig.DEBUG) { - Timber.d("Initializing FoxMMM"); - Timber.d("Started from background: %s", !isInForeground()); - Timber.d("FoxMMM is running in debug mode"); - Timber.d("Initializing Realm"); + Timber.plant(new Timber.DebugTree()); + } else { + if (isCrashReportingEnabled()) { + //noinspection UnstableApiUsage + Timber.plant(new SentryTimberTree(Sentry.getCurrentHub(), SentryLevel.ERROR, SentryLevel.ERROR)); + } else { + Timber.plant(new ReleaseTree()); + } } + Timber.i("Starting FoxMMM version %s (%d) - commit %s", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, BuildConfig.COMMIT_HASH); + // Update SSL Ciphers if update is possible + GMSProviderInstaller.installIfNeeded(this); + Timber.d("Initializing FoxMMM"); + Timber.d("Started from background: %s", !isInForeground()); + Timber.d("FoxMMM is running in debug mode"); + Timber.d("Initializing Realm"); Realm.init(this); Timber.d("Initialized Realm"); // Determine if this is an official build based on the signature try { // Get the signature of the key used to sign the app - @SuppressLint("PackageManagerGetSignatures") Signature[] signatures = this.getPackageManager().getPackageInfo(this.getPackageName(), PackageManager.GET_SIGNATURES).signatures; - @SuppressWarnings("SpellCheckingInspection") String[] officialSignatureHashArray = new String[]{"7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}; - String ourSignatureHash = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString(); - isOfficial = Arrays.asList(officialSignatureHashArray).contains(ourSignatureHash); + @SuppressLint("PackageManagerGetSignatures") Signature[] s = this.getPackageManager().getPackageInfo(this.getPackageName(), PackageManager.GET_SIGNATURES).signatures; + @SuppressWarnings("SpellCheckingInspection") String[] osh = new String[]{"7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}; + String oosh = Hashing.sha256().hashBytes(s[0].toByteArray()).toString(); + isOfficial = Arrays.asList(osh).contains(oosh); } catch (PackageManager.NameNotFoundException ignored) { } + // hide this behind a buildconfig flag for now, but crash the app if it's not an official build and not debug + if (BuildConfig.ENABLE_PROTECTION && !isOfficial && !BuildConfig.DEBUG) { + throw new RuntimeException("This is not an official build of FoxMMM"); + } SharedPreferences sharedPreferences = MainApplication.getPreferences("mmm"); // We are only one process so it's ok to do this SharedPreferences bootPrefs = MainApplication.getPreferences("mmm_boot"); @@ -407,7 +410,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con Timber.i("Emoji compat loaded!"); }, "Emoji compat init.").start(); } - SentryMain.initialize(this); if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) { Timber.w("Androidacy client id is empty! Please set it in androidacy.properties. Will not enable Androidacy."); SharedPreferences.Editor editor = sharedPreferences.edit(); @@ -529,8 +531,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con try { keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); - } catch (KeyStoreException | NoSuchAlgorithmException - | CertificateException | IOException e) { + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | + IOException e) { Timber.v("Failed to open the keystore."); throw new RuntimeException(e); } @@ -540,9 +542,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con // create a cipher that uses AES encryption -- we'll use this to encrypt our key Cipher cipher; try { - cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES - + "/" + KeyProperties.BLOCK_MODE_CBC - + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); + cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { Timber.e("Failed to create a cipher."); throw new RuntimeException(e); @@ -551,19 +551,12 @@ public class MainApplication extends FoxApplication implements androidx.work.Con // generate secret key KeyGenerator keyGenerator; try { - keyGenerator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, - "AndroidKeyStore"); + keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { Timber.e("Failed to access the key generator."); throw new RuntimeException(e); } - KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder( - "realm_key", - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .build(); + KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder("realm_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(KeyProperties.BLOCK_MODE_CBC).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7).build(); try { keyGenerator.init(keySpec); } catch (InvalidAlgorithmParameterException e) { @@ -578,33 +571,25 @@ public class MainApplication extends FoxApplication implements androidx.work.Con byte[] initializationVector; byte[] encryptedKeyForRealm; try { - SecretKey secretKey = - (SecretKey) keyStore.getKey("realm_key", null); + SecretKey secretKey = (SecretKey) keyStore.getKey("realm_key", null); cipher.init(Cipher.ENCRYPT_MODE, secretKey); encryptedKeyForRealm = cipher.doFinal(realmKey); initializationVector = cipher.getIV(); - } catch (InvalidKeyException | UnrecoverableKeyException - | NoSuchAlgorithmException | KeyStoreException - | BadPaddingException | IllegalBlockSizeException e) { + } catch (InvalidKeyException | UnrecoverableKeyException | NoSuchAlgorithmException | + KeyStoreException | BadPaddingException | IllegalBlockSizeException e) { Timber.e("Failed encrypting the key with the secret key."); throw new RuntimeException(e); } // keep the encrypted key in shared preferences // to persist it across application runs - byte[] initializationVectorAndEncryptedKey = - new byte[Integer.BYTES + - initializationVector.length + - encryptedKeyForRealm.length]; + byte[] initializationVectorAndEncryptedKey = new byte[Integer.BYTES + initializationVector.length + encryptedKeyForRealm.length]; ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey); buffer.order(ByteOrder.BIG_ENDIAN); buffer.putInt(initializationVector.length); buffer.put(initializationVector); buffer.put(encryptedKeyForRealm); Timber.d("Created all keys successfully."); - MainApplication.getPreferences("realm_key").edit() - .putString("iv_and_encrypted_key", - Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)) - .apply(); + MainApplication.getPreferences("realm_key").edit().putString("iv_and_encrypted_key", Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)).apply(); Timber.d("Saved the encrypted key in shared preferences."); return realmKey; // pass to a realm configuration via encryptionKey() } @@ -623,18 +608,15 @@ public class MainApplication extends FoxApplication implements androidx.work.Con try { keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); - } catch (KeyStoreException | NoSuchAlgorithmException - | CertificateException | IOException e) { + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | + IOException e) { Timber.e("Failed to open the keystore."); throw new RuntimeException(e); } Timber.v("Keystore opened."); // access the encrypted key that's stored in shared preferences - byte[] initializationVectorAndEncryptedKey = Base64.decode(MainApplication - .getPreferences("realm_key") - .getString("iv_and_encrypted_key", null), Base64.DEFAULT); - Timber.d("Retrieved the encrypted key from shared preferences. Key length: %d", - initializationVectorAndEncryptedKey.length); + byte[] initializationVectorAndEncryptedKey = Base64.decode(MainApplication.getPreferences("realm_key").getString("iv_and_encrypted_key", null), Base64.DEFAULT); + Timber.d("Retrieved the encrypted key from shared preferences. Key length: %d", initializationVectorAndEncryptedKey.length); ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey); buffer.order(ByteOrder.BIG_ENDIAN); // extract the length of the initialization vector from the buffer @@ -643,17 +625,13 @@ public class MainApplication extends FoxApplication implements androidx.work.Con byte[] initializationVector = new byte[initializationVectorLength]; buffer.get(initializationVector); // extract the encrypted key - byte[] encryptedKey = new byte[initializationVectorAndEncryptedKey.length - - Integer.BYTES - - initializationVectorLength]; + byte[] encryptedKey = new byte[initializationVectorAndEncryptedKey.length - Integer.BYTES - initializationVectorLength]; buffer.get(encryptedKey); Timber.d("Got key from shared preferences."); // create a cipher that uses AES encryption to decrypt our key Cipher cipher; try { - cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES - + "/" + KeyProperties.BLOCK_MODE_CBC - + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); + cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { Timber.e("Failed to create cipher."); throw new RuntimeException(e); @@ -661,18 +639,16 @@ public class MainApplication extends FoxApplication implements androidx.work.Con // decrypt the encrypted key with the secret key stored in the keystore byte[] decryptedKey; try { - final SecretKey secretKey = - (SecretKey) keyStore.getKey("realm_key", null); - final IvParameterSpec initializationVectorSpec = - new IvParameterSpec(initializationVector); + final SecretKey secretKey = (SecretKey) keyStore.getKey("realm_key", null); + final IvParameterSpec initializationVectorSpec = new IvParameterSpec(initializationVector); cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec); decryptedKey = cipher.doFinal(encryptedKey); } catch (InvalidKeyException e) { Timber.e("Failed to decrypt. Invalid key."); throw new RuntimeException(e); - } catch (UnrecoverableKeyException | NoSuchAlgorithmException - | BadPaddingException | KeyStoreException - | IllegalBlockSizeException | InvalidAlgorithmParameterException e) { + } catch (UnrecoverableKeyException | NoSuchAlgorithmException | BadPaddingException | + KeyStoreException | IllegalBlockSizeException | + InvalidAlgorithmParameterException e) { Timber.e("Failed to decrypt the encrypted realm key with the secret key."); throw new RuntimeException(e); } diff --git a/app/src/main/java/com/fox2code/mmm/SetupActivity.java b/app/src/main/java/com/fox2code/mmm/SetupActivity.java index 6962d2e..18defc1 100644 --- a/app/src/main/java/com/fox2code/mmm/SetupActivity.java +++ b/app/src/main/java/com/fox2code/mmm/SetupActivity.java @@ -72,6 +72,8 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { View view = binding.getRoot(); ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setChecked(BuildConfig.ENABLE_AUTO_UPDATER); ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setChecked(BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING); + // pref_crash_reporting_pii + ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting_pii))).setChecked(BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING_PII); // assert that both switches match the build config on debug builds if (BuildConfig.DEBUG) { assert ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).isChecked() == BuildConfig.ENABLE_AUTO_UPDATER; @@ -84,6 +86,7 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { if (BuildConfig.DEBUG) { ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Automatic update Check: %s", isChecked)); ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Crash Reporting: %s", isChecked)); + ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting_pii))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Crash Reporting PII: %s", isChecked)); ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Androidacy Repo: %s", isChecked)); ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Magisk Alt Repo: %s", isChecked)); } @@ -163,6 +166,8 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { editor.putBoolean("pref_background_update_check", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).isChecked()); // Set the crash reporting pref editor.putBoolean("pref_crash_reporting", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).isChecked()); + // Set the crash reporting PII pref + editor.putBoolean("pref_crash_reporting_pii", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting_pii))).isChecked()); Timber.d("Saving preferences"); // Set the repos in the ReposList realm db RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index 1aa3b2f..9493459 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -23,6 +23,7 @@ import com.fox2code.mmm.utils.io.Hashes; import com.fox2code.mmm.utils.io.net.Http; import com.fox2code.mmm.utils.io.PropUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.snackbar.Snackbar; import java.io.File; import java.nio.charset.StandardCharsets; @@ -32,6 +33,8 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import javax.net.ssl.SSLException; + import timber.log.Timber; public final class RepoManager extends SyncManager { @@ -338,11 +341,24 @@ public final class RepoManager extends SyncManager { // If we can't, we don't have internet connection Timber.d("Checking internet connection..."); // this url is actually hosted by Cloudflare and is not dependent on Androidacy servers being up - byte[] resp = new byte[0]; + byte[] resp; try { resp = Http.doHttpGet("https://production-api.androidacy.com/cdn-cgi/trace", false); } catch (Exception e) { Timber.e(e, "Failed to check internet connection. Assuming no internet connection."); + // check if it's a security or ssl exception + if (e instanceof SSLException || e instanceof SecurityException) { + // if it is, user installed a certificate that blocks the connection + // show a snackbar to inform the user + Activity context = MainApplication.getINSTANCE().getLastCompatActivity(); + new Handler(Looper.getMainLooper()).post(() -> { + if (context != null) { + Snackbar.make(context.findViewById(android.R.id.content), R.string.certificate_error, Snackbar.LENGTH_LONG).show(); + } + }); + } + this.hasInternet = false; + return; } // get the response body String response = new String(resp, StandardCharsets.UTF_8); diff --git a/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java b/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java index ada809f..f1e7611 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java +++ b/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java @@ -23,7 +23,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) + * Sentry is used for crash reporting and performance monitoring. */ @SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"}) public static void initialize(final MainApplication mainApplication) { @@ -54,8 +54,11 @@ public class SentryMain { SentryAndroid.init(mainApplication, options -> { // If crash reporting is disabled, stop here. if (!MainApplication.isCrashReportingEnabled()) { + sentryEnabled = false; // Set sentry state to disabled options.setDsn(""); } else { + // get pref_crash_reporting_pii pref + boolean crashReportingPii = sharedPreferences.getBoolean("crashReportingPii", false); sentryEnabled = true; // Set sentry state to enabled options.addIntegration(new FragmentLifecycleIntegration(mainApplication, true, true)); // Enable automatic activity lifecycle breadcrumbs @@ -71,15 +74,20 @@ public class SentryMain { options.addInAppInclude("com.fox2code.mmm.debug"); options.addInAppInclude("com.fox2code.mmm.fdroid"); options.addInAppExclude("com.fox2code.mmm.utils.sentry.SentryMain"); - // Sentry sends ABSOLUTELY NO Personally Identifiable Information (PII) by default. - // Already set to false by default, just set it again to make peoples feel safer. - options.setSendDefaultPii(false); + // Respect user preference for sending PII. default is true on non fdroid builds, false on fdroid builds + options.setSendDefaultPii(crashReportingPii); + options.enableAllAutoBreadcrumbs(true); + // in-app screenshots are only sent if the app crashes, and it only shows the last activity. so no, we won't see your, ahem, "private" stuff + options.setAttachScreenshot(true); // It just tell if sentry should ping the sentry dsn to tell the app is running. Useful for performance and profiling. options.setEnableAutoSessionTracking(true); - // A screenshot of the app itself is only sent if the app crashes, and it only shows the last activity // Add a callback that will be used before the event is sent to Sentry. // With this callback, you can modify the event or, when returning null, also discard the event. options.setBeforeSend((event, hint) -> { + // in the rare event that crash reporting has been disabled since we started the app, we don't want to send the crash report + if (!MainApplication.isCrashReportingEnabled()) { + return null; + } // Save lastEventId to private shared preferences SharedPreferences sentryPrefs = MainApplication.getPreferences("sentry"); String lastEventId = Objects.requireNonNull(event.getEventId()).toString(); diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml index 4fa774b..deee20b 100644 --- a/app/src/main/res/layout/activity_setup.xml +++ b/app/src/main/res/layout/activity_setup.xml @@ -147,7 +147,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" - android:text="@string/crash_reporting_headline" + android:text="@string/pref_category_privacy" android:textAppearance="@android:style/TextAppearance.Material.Headline" /> + + + + + + Failed to clear cache Clear app cache? This will clear app cache. Your preferences will be saved, but the app may take longer to do some operations temporarily. + The server certificate could not be verified. Please make sure nothing is intercepting HTTPS connections for FoxMMM. + Privacy + Allows sending additional information in crash reports, some of which may contain personally identifiable information such as IP address and device identifiers. + Send additional information + Send additional info in crash reports. + This may include device identifiers and IP addresses. No data will be used for any other purpose besides analyzing crashes and improving performance. diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index d7a13c5..98f3637 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,4 +1,5 @@ - + @@ -55,9 +56,9 @@ @@ -71,9 +72,9 @@ app:title="@string/notification_update_ignore_pref" /> @@ -170,6 +171,8 @@ app:singleLineTitle="false" app:summary="@string/prevent_reboot_desc" app:title="@string/prevent_reboot_pref" /> + + + + + app:summary="@string/clear_app_cache_desc" + app:title="@string/clear_app_cache" />