diff --git a/.github/workflows/build-debug.yml b/.github/workflows/build-debug.yml index 30d9279..b4ccfb1 100644 --- a/.github/workflows/build-debug.yml +++ b/.github/workflows/build-debug.yml @@ -62,33 +62,33 @@ jobs: # UPLOAD ARTIFACT SECTION # Will be shorter, when https://github.com/actions/upload-artifact/pull/354 will be merged - # FoxMMM-default-debug - - name: Upload FoxMMM-default-arm64-v8a-debug + # AMMM-default-debug + - name: Upload AMMM-default-arm64-v8a-debug uses: actions/upload-artifact@v3 with: - name: FoxMMM-default-arm64-v8a-debug + name: AMMM-default-arm64-v8a-debug path: app/build/outputs/apk/default/debug/*-default-arm64-v8a-debug.apk - - name: Upload FoxMMM-default-armeabi-v7a-debug + - name: Upload AMMM-default-armeabi-v7a-debug uses: actions/upload-artifact@v3 with: - name: FoxMMM-default-armeabi-v7a-debug + name: AMMM-default-armeabi-v7a-debug path: app/build/outputs/apk/default/debug/*-default-armeabi-v7a-debug.apk - - name: Upload FoxMMM-default-universal-debug + - name: Upload AMMM-default-universal-debug uses: actions/upload-artifact@v3 with: - name: FoxMMM-default-universal-debug + name: AMMM-default-universal-debug path: app/build/outputs/apk/default/debug/*-default-universal-debug.apk - - name: Upload FoxMMM-default-x86-debug + - name: Upload AMMM-default-x86-debug uses: actions/upload-artifact@v3 with: - name: FoxMMM-default-x86-debug + name: AMMM-default-x86-debug path: app/build/outputs/apk/default/debug/*-default-x86-debug.apk - - name: Upload FoxMMM-default-x86_64-debug + - name: Upload AMMM-default-x86_64-debug uses: actions/upload-artifact@v3 with: - name: FoxMMM-default-x86_64-debug + name: AMMM-default-x86_64-debug path: app/build/outputs/apk/default/debug/*-default-x86_64-debug.apk diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 698c0bf..5b597c9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,7 +5,6 @@ @file:Suppress("UnstableApiUsage", "SpellCheckingInspection") import com.android.build.api.variant.FilterConfiguration.FilterType.ABI -import io.sentry.android.gradle.instrumentation.logcat.LogcatLevel import java.util.Properties plugins { @@ -14,11 +13,8 @@ plugins { id("com.mikepenz.aboutlibraries.plugin") kotlin("android") kotlin("kapt") - id("com.google.devtools.ksp") version "1.8.22-1.0.11" - id("io.sentry.android.gradle") version "3.12.0" + id("com.google.devtools.ksp") version "1.9.10-1.0.13" } - -val hasSentryConfig = File(rootProject.projectDir, "sentry.properties").exists() android { // functions to get git info: gitCommitHash, gitBranch, gitRemote val gitCommitHash = providers.exec { @@ -52,8 +48,8 @@ android { applicationId = "com.fox2code.mmm" minSdk = 26 targetSdk = 34 - versionCode = 85 - versionName = "2.3.1" + versionCode = 86 + versionName = "2.3.2" vectorDrawables { useSupportLibrary = true } @@ -85,10 +81,6 @@ android { "en" ) ) - // ksp room processor - room { - schemaLocationDir.set(file("roomSchemas")) - } } splits { @@ -159,39 +151,23 @@ android { buildConfigField("boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true") buildConfigField("boolean", "DEFAULT_ENABLE_CRASH_REPORTING_PII", "true") buildConfigField("boolean", "DEFAULT_ENABLE_ANALYTICS", "true") - val properties = Properties() - if (project.rootProject.file("local.properties").exists()) { - properties.load(project.rootProject.file("local.properties").reader()) - // grab matomo.url - buildConfigField( - "String", "ANALYTICS_ENDPOINT", "\"" + properties.getProperty( - "matomo.url", "https://s-api.androidacy.com/matomo.php" - ) + "\"" - ) - } else { - buildConfigField( - "String", "ANALYTICS_ENDPOINT", "\"https://s-api.androidacy.com/matomo.php\"" - ) - } buildConfigField("boolean", "ENABLE_PROTECTION", "true") // Get the androidacy client ID from the androidacy.properties val propertiesA = Properties() - // If androidacy.properties doesn"t exist, use the default client ID which is heavily - // rate limited to 30 requests per minute + val default = "5KYccdYxWB2RxMq5FTbkWisXi2dS6yFN9R7RVlFCG98FRdz6Mf5ojY2fyJCUlXJZ" if (project.rootProject.file("androidacy.properties").exists()) { propertiesA.load(project.rootProject.file("androidacy.properties").reader()) - properties.setProperty( - "client_id", "\"" + propertiesA.getProperty( + propertiesA.setProperty( + "client_id", propertiesA.getProperty( "client_id", - "5KYccdYxWB2RxMq5FTbkWisXi2dS6yFN9R7RVlFCG98FRdz6Mf5ojY2fyJCUlXJZ" - ) + "\"" + default + ) ) } else { - properties.setProperty( - "client_id", "5KYccdYxWB2RxMq5FTbkWisXi2dS6yFN9R7RVlFCG98FRdz6Mf5ojY2fyJCUlXJZ" - ) + propertiesA.setProperty("client_id", "\"" + default + "\"") } + buildConfigField( "String", "ANDROIDACY_CLIENT_ID", "\"" + propertiesA.getProperty("client_id") + "\"" ) @@ -228,38 +204,21 @@ android { buildConfigField("boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true") buildConfigField("boolean", "DEFAULT_ENABLE_CRASH_REPORTING_PII", "true") buildConfigField("boolean", "DEFAULT_ENABLE_ANALYTICS", "true") - val properties = Properties() - if (project.rootProject.file("local.properties").exists()) { - properties.load(project.rootProject.file("local.properties").reader()) - // grab matomo.url - buildConfigField( - "String", "ANALYTICS_ENDPOINT", "\"" + properties.getProperty( - "matomo.url", "https://s-api.androidacy.com/matomo.php" - ) + "\"" - ) - } else { - buildConfigField( - "String", "ANALYTICS_ENDPOINT", "\"https://s-api.androidacy.com/matomo.php\"" - ) - } buildConfigField("boolean", "ENABLE_PROTECTION", "true") // Get the androidacy client ID from the androidacy.properties val propertiesA = Properties() - // If androidacy.properties doesn"t exist, use the default client ID which is heavily - // rate limited to 30 requests per minute + val default = "5KYccdYxWB2RxMq5FTbkWisXi2dS6yFN9R7RVlFCG98FRdz6Mf5ojY2fyJCUlXJZ" if (project.rootProject.file("androidacy.properties").exists()) { propertiesA.load(project.rootProject.file("androidacy.properties").reader()) - properties.setProperty( - "client_id", "\"" + propertiesA.getProperty( - "play_client_id", - "5KYccdYxWB2RxMq5FTbkWisXi2dS6yFN9R7RVlFCG98FRdz6Mf5ojY2fyJCUlXJZ" - ) + "\"" + propertiesA.setProperty( + "client_id", propertiesA.getProperty( + "client_id", + default + ) ) } else { - properties.setProperty( - "client_id", "5KYccdYxWB2RxMq5FTbkWisXi2dS6yFN9R7RVlFCG98FRdz6Mf5ojY2fyJCUlXJZ" - ) + propertiesA.setProperty("client_id", "\"" + default + "\"") } buildConfigField( "String", "ANDROIDACY_CLIENT_ID", "\"" + propertiesA.getProperty("client_id") + "\"" @@ -300,20 +259,6 @@ android { buildConfigField("boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false") buildConfigField("boolean", "DEFAULT_ENABLE_CRASH_REPORTING_PII", "false") buildConfigField("boolean", "DEFAULT_ENABLE_ANALYTICS", "false") - val properties = Properties() - if (project.rootProject.file("local.properties").exists()) { - properties.load(project.rootProject.file("local.properties").reader()) - // grab matomo.url - buildConfigField( - "String", "ANALYTICS_ENDPOINT", "\"" + properties.getProperty( - "matomo.url", "https://s-api.androidacy.com/matomo.php" - ) + "\"" - ) - } else { - buildConfigField( - "String", "ANALYTICS_ENDPOINT", "\"https://s-api.androidacy.com/matomo.php\"" - ) - } buildConfigField("boolean", "ENABLE_PROTECTION", "true") // Repo with ads or tracking feature are disabled by default for the @@ -355,44 +300,6 @@ android { } } -sentry { - - includeProguardMapping.set(true) - - autoUploadProguardMapping.set(hasSentryConfig) - - experimentalGuardsquareSupport.set(true) - - uploadNativeSymbols.set(hasSentryConfig) - - includeNativeSources.set(hasSentryConfig) - - tracingInstrumentation { - enabled.set(true) - - logcat { - enabled.set(true) - - minLevel.set(LogcatLevel.WARNING) - } - } - - autoInstallation { - enabled.set(true) - } - - includeDependenciesReport.set(true) - includeSourceContext.set(hasSentryConfig) - - // Includes additional source directories into the source bundle. - // These directories are resolved relative to the project directory. - additionalSourceDirsForSourceContext.set(setOf("src/main/java", "src/main/kotlin")) - - org.set("androidacy") - projectName.set("foxmmm") - uploadNativeSymbols.set(hasSentryConfig) -} - val abiCodes = mapOf("armeabi-v7a" to 1, "x86" to 2, "x86_64" to 3, "arm64-v8a" to 4) // For per-density APKs, create a similar map: @@ -452,11 +359,7 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.webkit:webkit:1.8.0") implementation("com.google.android.material:material:1.9.0") - implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0") - implementation("dev.rikka.rikkax.insets:insets:1.3.0") - implementation("com.github.KieronQuinn:MonetCompat:0.4.1") - implementation("com.github.Fox2Code.FoxCompat:foxcompat:1.2.14") - implementation("com.github.Fox2Code.FoxCompat:hiddenapis:1.2.14") + implementation("com.mikepenz:aboutlibraries:10.8.3") // Utils @@ -481,9 +384,6 @@ dependencies { implementation("com.github.Fox2Code:RosettaX:1.0.9") implementation("com.github.Fox2Code:AndroidANSI:1.2.1") - // sentry - implementation("io.sentry:sentry-android:6.29.0") - // Markdown // TODO: switch to an updated implementation implementation("io.noties.markwon:core:4.6.2") @@ -493,10 +393,6 @@ dependencies { implementation("com.google.net.cronet:cronet-okhttp:0.1.0") implementation("com.caverock:androidsvg:1.4") - implementation("dev.rikka.rikkax.core:core:1.4.1") - implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") - implementation("com.github.tiann:FreeReflection:3.1.0") - implementation("androidx.core:core-ktx:1.12.0") // timber @@ -510,7 +406,7 @@ dependencies { implementation("org.apache.commons:commons-compress:1.24.0") // analytics - implementation("com.github.matomo-org:matomo-sdk-android:HEAD") + implementation("ly.count.android:sdk:23.8.2") // annotations implementation("org.jetbrains:annotations-java5:24.0.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3927c50..d8a8e0e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,8 +4,6 @@ tools:ignore="QueryAllPackagesPermission" tools:targetApi="tiramisu"> - - @@ -32,9 +30,12 @@ - + - + @@ -187,31 +188,6 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/shared_file_paths" /> - - - - - - - - - diff --git a/app/src/main/kotlin/com/fox2code/mmm/CrashHandler.kt b/app/src/main/kotlin/com/fox2code/mmm/CrashHandler.kt index 6a0a548..292b5c9 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/CrashHandler.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/CrashHandler.kt @@ -10,20 +10,16 @@ import android.content.ClipboardManager import android.content.DialogInterface import android.os.Bundle import android.view.View -import android.widget.EditText import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textview.MaterialTextView -import io.sentry.Sentry -import io.sentry.UserFeedback -import io.sentry.protocol.SentryId import timber.log.Timber import java.io.PrintWriter import java.io.StringWriter class CrashHandler : AppCompatActivity() { - @Suppress("DEPRECATION", "KotlinConstantConditions") + @Suppress("DEPRECATION") @SuppressLint("RestrictedApi") override fun onCreate(savedInstanceState: Bundle?) { Timber.i("CrashHandler.onCreate(%s)", savedInstanceState) @@ -38,7 +34,7 @@ class CrashHandler : AppCompatActivity() { // get the exception from the intent val exception = intent.getSerializableExtra("exception") as Throwable? // get the crashReportingEnabled from the intent - val crashReportingEnabled = intent.getBooleanExtra("crashReportingEnabled", false) + intent.getBooleanExtra("crashReportingEnabled", false) // if the exception is null, set the crash details to "Unknown" if (exception == null) { crashDetails.setText(R.string.crash_details) @@ -51,113 +47,6 @@ class CrashHandler : AppCompatActivity() { stacktrace = stacktrace.replace(",", "\n ") crashDetails.text = getString(R.string.crash_full_stacktrace, stacktrace) } - // force sentry to send all events - Sentry.flush(2000) - var lastEventId = intent.getStringExtra("lastEventId") - // if event id is all zeros, set it to "". test this by matching the regex ^0+$ (all zeros) - if (lastEventId?.matches("^0+$".toRegex()) == true) { - lastEventId = "" - } - if (BuildConfig.DEBUG) Timber.d( - "CrashHandler.onCreate: lastEventId=%s, crashReportingEnabled=%s", - lastEventId, - crashReportingEnabled - ) - - // get name, email, and message fields - val name = findViewById(R.id.feedback_name) - val email = findViewById(R.id.feedback_email) - val description = findViewById(R.id.feedback_message) - val submit = findViewById(R.id.feedback_submit) - if (lastEventId.isNullOrEmpty() && crashReportingEnabled) { - // if lastEventId is null, hide the feedback button - if (BuildConfig.DEBUG) Timber.d("CrashHandler.onCreate: lastEventId is null but crash reporting is enabled. This may indicate a bug in the crash reporting system.") - submit.visibility = View.GONE - findViewById(R.id.feedback_text).setText(R.string.no_sentry_id) - } else { - // if lastEventId is not null, enable the feedback name, email, message, and submit button - email.isEnabled = true - name.isEnabled = true - description.isEnabled = true - submit.isEnabled = true - } - // disable feedback if sentry is disabled - if (crashReportingEnabled && lastEventId != null) { - // get submit button - findViewById(R.id.feedback_submit).setOnClickListener { _: View? -> - // require the feedback_message, rest is optional - if (description.text.toString() == "") { - Toast.makeText(this, R.string.sentry_dialogue_empty_message, Toast.LENGTH_LONG) - .show() - return@setOnClickListener - } - // if email or name is empty, use "Anonymous" - val nameString = - arrayOf(if (name.text.toString() == "") "Anonymous" else name.text.toString()) - val emailString = - arrayOf(if (email.text.toString() == "") "Anonymous" else email.text.toString()) - Thread { - try { - val userFeedback = - UserFeedback(SentryId(lastEventId)) - // Setups the JSON body - if (nameString[0] == "") nameString[0] = "Anonymous" - if (emailString[0] == "") emailString[0] = "Anonymous" - userFeedback.name = nameString[0] - userFeedback.email = emailString[0] - userFeedback.comments = description.text.toString() - Sentry.captureUserFeedback(userFeedback) - Timber.i( - "Submitted user feedback: name %s email %s comment %s", - nameString[0], - emailString[0], - description.text.toString() - ) - runOnUiThread { - Toast.makeText( - this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG - ).show() - } - // Close the activity - finish() - // start the main activity - startActivity(packageManager.getLaunchIntentForPackage(packageName)) - } catch (e: Exception) { - Timber.e(e, "Failed to submit user feedback") - // 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() - } - // get restart button - findViewById(R.id.restart).setOnClickListener { _: View? -> - // Restart the app and submit sans feedback - val sentryException = intent.getSerializableExtra("sentryException") as Throwable? - if (crashReportingEnabled) Sentry.captureException(sentryException!!) - finish() - startActivity(packageManager.getLaunchIntentForPackage(packageName)) - } - } else if (!crashReportingEnabled) { - // set feedback_text to "Crash reporting is disabled" - (findViewById(R.id.feedback_text) as MaterialTextView).setText(R.string.sentry_enable_nag) - submit.setOnClickListener { _: View? -> - 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).isEnabled = true - findViewById(R.id.restart).setOnClickListener { _: View? -> - // Restart the app - finish() - startActivity(packageManager.getLaunchIntentForPackage(packageName)) - } - } // handle reset button findViewById(R.id.reset).setOnClickListener { _: View? -> // show a confirmation material dialog diff --git a/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt index 458d3c3..21b647e 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt @@ -35,7 +35,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.room.Room import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener -import com.fox2code.foxcompat.view.FoxDisplay import com.fox2code.mmm.AppUpdateManager.Companion.appUpdateManager import com.fox2code.mmm.OverScrollManager.OverScrollHelper import com.fox2code.mmm.androidacy.AndroidacyRepoData @@ -65,7 +64,8 @@ import com.google.android.material.elevation.SurfaceColors import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.textfield.TextInputEditText -import org.matomo.sdk.extra.TrackHelper +import ly.count.android.sdk.Countly +import ly.count.android.sdk.ModuleFeedback.FeedbackCallback import timber.log.Timber import java.sql.Timestamp @@ -118,7 +118,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { onMainActivityCreate(this) super.onCreate(savedInstanceState) INSTANCE = this - TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker) + // 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 && !MainApplication.o && !BuildConfig.DEBUG) { throw RuntimeException("This is not an official build of AMM") @@ -143,8 +143,11 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { db.close() if (enabledRepos.isNotEmpty()) { enabledRepos.delete(enabledRepos.length - 2, enabledRepos.length) - TrackHelper.track().event("Enabled Repos", enabledRepos.toString()) - .with(MainApplication.INSTANCE!!.tracker) + // use countly to track enabled repos + val repoMap = HashMap() + repoMap["repos"] = enabledRepos.toString() + Countly.sharedInstance().events().recordEvent("enabled_repos", + repoMap as Map?, 1) } }.start() val ts = Timestamp(System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000) @@ -235,7 +238,9 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { // filter the appropriate list based on visibility if (initMode) return val query = s.toString() - TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker) + Countly.sharedInstance().events().recordEvent("search", HashMap().apply { + put("query", query) + } as Map?, 1) Thread { if (moduleViewListBuilder.setQueryChange(query)) { Timber.i("Query submit: %s on offline list", query) @@ -261,7 +266,9 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { if (actionId == EditorInfo.IME_ACTION_SEARCH) { // filter the appropriate list based on visibility val query = textInputEditText.text.toString() - TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker) + Countly.sharedInstance().events().recordEvent("search", HashMap().apply { + put("query", query) + } as Map?, 1) Thread { if (moduleViewListBuilder.setQueryChange(query)) { Timber.i("Query submit: %s on offline list", query) @@ -396,7 +403,6 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { } } }) - textInputEditText.minimumHeight = FoxDisplay.dpToPixel(16f) textInputEditText.imeOptions = EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN @@ -405,8 +411,6 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { bottomNavigationView.setOnItemSelectedListener { item: MenuItem -> when (item.itemId) { R.id.settings_menu_item -> { - TrackHelper.track().event("view_list", "settings") - .with(MainApplication.INSTANCE!!.tracker) // start settings activity so that when user presses back, they go back to main activity and on api34 they see a preview of the main activity. tell settings activity current active tab so that it can be selected when user goes back to main activity val intent = Intent(this@MainActivity, SettingsActivity::class.java) when (bottomNavigationView.selectedItemId) { @@ -417,8 +421,6 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { } R.id.online_menu_item -> { - TrackHelper.track().event("view_list", "online_modules") - .with(MainApplication.INSTANCE!!.tracker) searchTextInputEditText!!.clearFocus() searchTextInputEditText!!.text?.clear() // set module_list_online as visible and module_list as gone. fade in/out @@ -439,8 +441,6 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { } R.id.installed_menu_item -> { - TrackHelper.track().event("view_list", "installed_modules") - .with(MainApplication.INSTANCE!!.tracker) searchTextInputEditText!!.clearFocus() searchTextInputEditText!!.text?.clear() // set module_list_online as gone and module_list as visible. fade in/out @@ -747,6 +747,44 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) moduleViewListBuilderOnline.applyTo(moduleListOnline!!, moduleViewAdapterOnline!!) }, "Repo update thread").start() + if (MainApplication.shouldShowFeedback()) { + Countly.sharedInstance().feedback() + .getAvailableFeedbackWidgets { retrievedWidgets, error -> + if (error == null) { + if (retrievedWidgets.size > 0) { + val feedbackWidget = retrievedWidgets[0] + Countly.sharedInstance().feedback().presentFeedbackWidget( + feedbackWidget, + this@MainActivity, + "Close", + object : FeedbackCallback { + override fun onClosed() { + } + + // maybe show a toast when the widget is closed + override fun onFinished(error: String) { + // error handling here + if (error.isNotEmpty()) { + Toast.makeText( + this@MainActivity, + "Error: $error", + Toast.LENGTH_LONG + ).show() + } else { + Toast.makeText( + this@MainActivity, + "Feedback sent", + Toast.LENGTH_LONG + ).show() + } + } + }) + } + } else { + Timber.e(error) + } + } + } } fun maybeShowUpgrade() { diff --git a/app/src/main/kotlin/com/fox2code/mmm/MainApplication.kt b/app/src/main/kotlin/com/fox2code/mmm/MainApplication.kt index 8ae4637..e629e69 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/MainApplication.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/MainApplication.kt @@ -17,6 +17,7 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.os.Build import android.os.Bundle +import android.os.Process import android.os.SystemClock import android.util.Log import androidx.annotation.StyleRes @@ -28,7 +29,6 @@ import androidx.emoji2.text.EmojiCompat import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import androidx.work.Configuration -import com.fox2code.foxcompat.app.internal.FoxProcessExt import com.fox2code.mmm.installer.InstallerInitializer import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskVersion import com.fox2code.mmm.manager.LocalModuleInfo @@ -37,8 +37,6 @@ import com.fox2code.mmm.utils.TimberUtils.configTimber import com.fox2code.mmm.utils.io.FileUtils import com.fox2code.mmm.utils.io.GMSProviderInstaller.Companion.installIfNeeded import com.fox2code.mmm.utils.io.net.Http.Companion.getHttpClientWithCache -import com.fox2code.mmm.utils.sentry.SentryMain -import com.fox2code.mmm.utils.sentry.SentryMain.initialize import com.fox2code.rosettax.LanguageSwitcher import com.google.common.hash.Hashing import com.topjohnwu.superuser.Shell @@ -46,12 +44,8 @@ import io.noties.markwon.Markwon import io.noties.markwon.html.HtmlPlugin import io.noties.markwon.image.ImagesPlugin import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler -import io.sentry.Sentry -import io.sentry.SentryLevel -import org.matomo.sdk.Matomo -import org.matomo.sdk.Tracker -import org.matomo.sdk.TrackerBuilder -import org.matomo.sdk.extra.TrackHelper +import ly.count.android.sdk.Countly +import ly.count.android.sdk.CountlyConfig import timber.log.Timber import java.io.File import java.security.SecureRandom @@ -60,6 +54,7 @@ import java.util.Date import java.util.Random import kotlin.math.abs + @Suppress("unused", "MemberVisibilityCanBePrivate") class MainApplication : Application(), Configuration.Provider, ActivityLifecycleCallbacks { @@ -97,18 +92,6 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle return field } private var existingKey: CharArray? = null - - var tracker: Tracker? = null - get() { - if (field == null) { - field = TrackerBuilder.createDefault(BuildConfig.ANALYTICS_ENDPOINT, 1) - .build(Matomo.getInstance(this)) - val tracker = field!! - tracker.startNewSession() - tracker.dispatchInterval = 1000 - } - return field - } private var makingNewKey = false private var isCrashHandler = false @@ -216,6 +199,27 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle get() = !this.isLightTheme override fun onCreate() { + + Thread.setDefaultUncaughtExceptionHandler { _: Thread?, throwable: Throwable -> + clearCachedSharedPrefs() + // open crash handler and exit + val intent = Intent(this, CrashHandler::class.java) + // pass the entire exception to the crash handler + intent.putExtra("exception", throwable) + // add stacktrace as string + intent.putExtra("stacktrace", throwable.stackTrace) + // serialize Sentry.captureException and pass it to the crash handler + intent.putExtra("sentryException", throwable) + // pass crashReportingEnabled to crash handler + intent.putExtra("crashReportingEnabled", isCrashReportingEnabled) + // add isCrashing to intent + intent.putExtra("isCrashing", true) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + Timber.e("Starting crash handler") + startActivity(intent) + Timber.e("Exiting") + Process.killProcess(Process.myPid()) + } supportedLocales.addAll( listOf( "ar", @@ -254,7 +258,6 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle Timber.e(e, "Failed to register activity lifecycle callbacks") } } - initialize(this) // Initialize Timber configTimber() Timber.i( @@ -276,19 +279,25 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle if (BuildConfig.DEBUG) Timber.d("Started from background: %s", !isInForeground) if (BuildConfig.DEBUG) Timber.d("AMM is running in debug mode") // analytics - if (BuildConfig.DEBUG) Timber.d("Initializing matomo") - if (!isMatomoAllowed()) { - if (BuildConfig.DEBUG) Timber.d("Matomo is not allowed") - tracker!!.isOptOut = true - } else { - tracker!!.isOptOut = false - } - if (getSharedPreferences("matomo")!!.getBoolean("install_tracked", false)) { - TrackHelper.track().download().with(INSTANCE!!.tracker) - if (BuildConfig.DEBUG) Timber.d("Sent install event to matomo") - getSharedPreferences("matomo")!!.edit().putBoolean("install_tracked", true).apply() + if (BuildConfig.DEBUG) Timber.d("Initializing countly") + if (!analyticsAllowed()) { + if (BuildConfig.DEBUG) Timber.d("countly is not allowed") } else { - if (BuildConfig.DEBUG) Timber.d("Matomo already has install") + val config = CountlyConfig( + this, + "ff1dc022295f64a7a5f6a5ca07c0294400c71b60", + "https://ctly.androidacy.com" + ) + if (isCrashReportingEnabled) { + config.enableCrashReporting() + } + config.enableAutomaticViewTracking() + config.setPushIntentAddMetadata(true) + config.setLoggingEnabled(BuildConfig.DEBUG) + config.setRequiresConsent(false) + config.setRecordAppStartTime(true) + Countly.sharedInstance().init(config) + Countly.applicationOnCreate() } try { @Suppress("DEPRECATION") @SuppressLint("PackageManagerGetSignatures") val s = @@ -454,17 +463,12 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle // prepend [TAINTED] to the message message = "[TAINTED] $message" } - if (priority >= Log.WARN) { + if (priority >= Log.ERROR) { if (t != null) { Log.println(priority, tag, message) t.printStackTrace() - Sentry.captureException(t) } else { Log.println(priority, tag, message) - when (priority) { - Log.ERROR -> Sentry.captureMessage(message, SentryLevel.ERROR) - Log.WARN -> Sentry.captureMessage(message, SentryLevel.WARNING) - } } } } @@ -481,8 +485,7 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle // Is application wrapped, and therefore must reduce it's feature set. @SuppressLint("RestrictedApi") // Use FoxProcess wrapper helper. - @JvmField - val isWrapped = !FoxProcessExt.isRootLoader() + const val isWrapped = false private val callers = ArrayList() @JvmField @@ -656,7 +659,7 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle } val isCrashReportingEnabled: Boolean - get() = SentryMain.IS_SENTRY_INSTALLED && getSharedPreferences("mmm")!!.getBoolean( + get() = getSharedPreferences("mmm")!!.getBoolean( "pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING ) val bootSharedPreferences: SharedPreferences? @@ -670,11 +673,37 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle val isNotificationPermissionGranted: Boolean get() = NotificationManagerCompat.from((INSTANCE)!!).areNotificationsEnabled() - fun isMatomoAllowed(): Boolean { + fun analyticsAllowed(): Boolean { return getSharedPreferences("mmm")!!.getBoolean( "pref_analytics_enabled", BuildConfig.DEFAULT_ENABLE_ANALYTICS ) } + + fun shouldShowFeedback(): Boolean { + // should not have been shown in 30 days and only 1 in 5 chance + return if (getSharedPreferences("mmm")!!.getBoolean("pref_feedback_shown", false)) { + false + } else { + val random = Random() + val chance = random.nextInt(5) + if (chance == 0) { + val lastFeedback = getSharedPreferences("mmm")!!.getLong( + "pref_last_feedback", 0 + ) + val now = System.currentTimeMillis() + if (now - lastFeedback > 2592000000L) { + val editor = getSharedPreferences("mmm")!!.edit() + editor.putLong("pref_last_feedback", now) + editor.apply() + true + } else { + false + } + } else { + false + } + } + } } override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { @@ -683,6 +712,7 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle } override fun onActivityStarted(activity: Activity) { + Countly.sharedInstance().onStart(activity) } override fun onActivityResumed(activity: Activity) { @@ -694,6 +724,7 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle } override fun onActivityStopped(activity: Activity) { + Countly.sharedInstance().onStop() } override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { diff --git a/app/src/main/kotlin/com/fox2code/mmm/NotificationType.kt b/app/src/main/kotlin/com/fox2code/mmm/NotificationType.kt index e892d49..8c293ce 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/NotificationType.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/NotificationType.kt @@ -4,7 +4,6 @@ @file:Suppress( "KotlinConstantConditions", - "UNINITIALIZED_ENUM_COMPANION_WARNING", "ktConcatNullable" ) @@ -207,14 +206,15 @@ enum class NotificationType( compatActivity.cacheDir, "installer" + File.separator + "module.zip" ) IntentHelper.openFileTo(compatActivity, module) { d: File, u: Uri, s: Int -> + val companion = NotificationType.Companion if (s == IntentHelper.RESPONSE_FILE) { try { - if (needPatch(d)) { + if (companion.needPatch(d)) { patchModuleSimple( read(d), FileOutputStream(d) ) } - if (needPatch(d)) { + if (companion.needPatch(d)) { if (d.exists() && !d.delete()) Timber.w("Failed to delete non module zip") Toast.makeText( compatActivity, R.string.invalid_format, Toast.LENGTH_SHORT diff --git a/app/src/main/kotlin/com/fox2code/mmm/SetupActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/SetupActivity.kt index c884cf8..5c1c7a0 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/SetupActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/SetupActivity.kt @@ -150,18 +150,41 @@ class SetupActivity : AppCompatActivity(), LanguageActivity { } (Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check)) as MaterialSwitch).isChecked = BuildConfig.ENABLE_AUTO_UPDATER - (Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting)) as MaterialSwitch).isChecked = + val setupCrashReporting = view.findViewById(R.id.setup_crash_reporting) + val analyticsEnabled = view.findViewById(R.id.setup_app_analytics) + val crashReportingPii = view.findViewById(R.id.setup_crash_reporting_pii) + setupCrashReporting.isChecked = BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING // pref_crash_reporting_pii - (Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting_pii)) as MaterialSwitch).isChecked = + crashReportingPii.isChecked = BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING_PII // pref_analytics_enabled - (Objects.requireNonNull(view.findViewById(R.id.setup_app_analytics)) as MaterialSwitch).isChecked = + analyticsEnabled.isChecked = BuildConfig.DEFAULT_ENABLE_ANALYTICS + // if analytics is disabled, force disable crash reporting + if (!view.findViewById(R.id.setup_app_analytics).isChecked) { + setupCrashReporting.isEnabled = false + crashReportingPii.isEnabled = false + setupCrashReporting.isChecked = false + crashReportingPii.isChecked = false + } + // listen for changes to the analytics switch + analyticsEnabled.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + // if analytics is disabled, force disable crash reporting + if (!isChecked) { + setupCrashReporting.isChecked = false + crashReportingPii.isChecked = false + setupCrashReporting.isEnabled = false + crashReportingPii.isEnabled = false + } else { + setupCrashReporting.isEnabled = true + crashReportingPii.isEnabled = true + } + } // assert that both switches match the build config on debug builds if (BuildConfig.DEBUG) { assert((Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check)) as MaterialSwitch).isChecked == BuildConfig.ENABLE_AUTO_UPDATER) - assert((Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting)) as MaterialSwitch).isChecked == BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING) + assert(setupCrashReporting.isChecked == BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING) } // Repos are a little harder, as the enabled_repos build config is an arraylist val andRepoView = @@ -178,7 +201,7 @@ class SetupActivity : AppCompatActivity(), LanguageActivity { isChecked ) } - (Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting)) as MaterialSwitch).setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + setupCrashReporting.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> Timber.i( "Crash Reporting: %s", isChecked @@ -302,7 +325,7 @@ class SetupActivity : AppCompatActivity(), LanguageActivity { // Set the crash reporting pref editor.putBoolean( "pref_crash_reporting", - (Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting)) as MaterialSwitch).isChecked + setupCrashReporting.isChecked ) // Set the crash reporting PII pref editor.putBoolean( @@ -339,7 +362,7 @@ class SetupActivity : AppCompatActivity(), LanguageActivity { reposListDao.setEnabled(androidacyRepoRoomObj.id, androidacyRepoRoom) reposListDao.setEnabled(magiskAltRepoRoomObj.id, magiskAltRepoRoom) db.close() - editor.putString("last_shown_setup", "v4") + editor.putString("last_shown_setup", "v5") // Commit the changes editor.commit() // Log the changes diff --git a/app/src/main/kotlin/com/fox2code/mmm/UpdateActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/UpdateActivity.kt index a7666d2..9c43f57 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/UpdateActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/UpdateActivity.kt @@ -26,7 +26,6 @@ import com.google.android.material.button.MaterialButton import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.textview.MaterialTextView import org.json.JSONException -import org.matomo.sdk.extra.TrackHelper import timber.log.Timber import java.io.File import java.io.FileOutputStream @@ -64,9 +63,6 @@ class UpdateActivity : AppCompatActivity() { } } chgWv = findViewById(R.id.changelog_webview) - if (MainApplication.isMatomoAllowed()) { - TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker) - } val changelogWebView = chgWv!! val webSettings = changelogWebView.settings webSettings.userAgentString = Http.androidacyUA diff --git a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyActivity.kt index 638379c..11b296b 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyActivity.kt @@ -46,7 +46,6 @@ import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView import com.fox2code.mmm.utils.io.net.Http.Companion.markCaptchaAndroidacySolved import com.google.android.material.progressindicator.LinearProgressIndicator -import org.matomo.sdk.extra.TrackHelper import timber.log.Timber import java.io.ByteArrayInputStream import java.io.File @@ -76,7 +75,7 @@ class AndroidacyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { moduleFile = File(this.cacheDir, "module.zip") super.onCreate(savedInstanceState) - TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker) + val intent = this.intent var uri: Uri? = intent.data @Suppress("KotlinConstantConditions") diff --git a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyUtil.kt b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyUtil.kt index 0330566..5554b0e 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyUtil.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyUtil.kt @@ -8,15 +8,13 @@ package com.fox2code.mmm.androidacy import android.net.Uri import com.fox2code.mmm.BuildConfig -import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet -import java.io.IOException @Suppress("MemberVisibilityCanBePrivate", "MemberVisibilityCanBePrivate") enum class AndroidacyUtil { ; companion object { - const val REFERRER = "utm_source=FoxMMM&utm_medium=app" + const val REFERRER = "utm_source=AMMM&utm_medium=app" fun isAndroidacyLink(uri: Uri?): Boolean { return uri != null && isAndroidacyLink(uri.toString(), uri) } @@ -109,39 +107,5 @@ enum class AndroidacyUtil { } return null } - - /** - * Check if the url is a premium direct download link - * @param url url to check - * @return true if it is a premium direct download link - * @noinspection unused - */ - fun isPremiumDirectDownloadLink(url: String): Boolean { - return url.contains("/magisk/ddl/") - } - - /** - * Returns the markdown directly from the API for rendering. Premium only, and internal testing only currently. - * /#blocked-by: A#F-0815 - * @param url URL to get markdown from - * @return String of markdown - * @noinspection unused - */ - fun getMarkdownFromAPI(url: String?): String? { - val md: ByteArray = try { - doHttpGet(url!!, false) - } catch (ignored: IOException) { - return null - } - return String(md) - } - - fun getMarkdownForModule(moduleId: String): String? { - try { - return getMarkdownFromAPI("https://production-api.androidacy.com/magisk/$moduleId/markdown") - } catch (ignored: IOException) { - } - return null - } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt index 8c5ebb2..c213207 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt @@ -16,7 +16,6 @@ import android.webkit.JavascriptInterface import android.widget.Toast import androidx.annotation.Keep import androidx.core.content.ContextCompat -import com.fox2code.foxcompat.view.FoxDisplay import com.fox2code.mmm.BuildConfig import com.fox2code.mmm.MainApplication import com.fox2code.mmm.R @@ -158,7 +157,7 @@ class AndroidacyWebAPI( return@injectButton null } }, "androidacy_repo") - val dim5dp = FoxDisplay.dpToPixel(5f) + val dim5dp = activity.resources.getDimensionPixelSize(R.dimen.dim5dp) builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp) activity.runOnUiThread { val alertDialog = builder.show() diff --git a/app/src/main/kotlin/com/fox2code/mmm/background/BackgroundUpdateChecker.kt b/app/src/main/kotlin/com/fox2code/mmm/background/BackgroundUpdateChecker.kt index d0c3dfc..92fd25e 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/background/BackgroundUpdateChecker.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/background/BackgroundUpdateChecker.kt @@ -378,7 +378,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters) fun onMainActivityCreate(context: Context) { // Refuse to run if first_launch pref is not false if (MainApplication.getSharedPreferences("mmm")!! - .getString("last_shown_setup", null) != "v4" + .getString("last_shown_setup", null) != "v5" ) return // create notification channel group val groupName: CharSequence = context.getString(R.string.notification_group_updates) diff --git a/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt index dedcfca..ab8ed0e 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt @@ -46,8 +46,6 @@ import com.fox2code.mmm.utils.io.Files.Companion.write import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumMatch import com.fox2code.mmm.utils.io.PropUtils import com.fox2code.mmm.utils.io.net.Http -import com.fox2code.mmm.utils.sentry.SentryBreadcrumb -import com.fox2code.mmm.utils.sentry.SentryMain import com.google.android.material.bottomnavigation.BottomNavigationItemView import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -56,8 +54,8 @@ import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.io.SuFile +import ly.count.android.sdk.Countly import org.apache.commons.compress.archivers.zip.ZipFile -import org.matomo.sdk.extra.TrackHelper import timber.log.Timber import java.io.BufferedReader import java.io.File @@ -89,7 +87,7 @@ class InstallerActivity : AppCompatActivity() { moduleCache = File(this.cacheDir, "installer") if (!moduleCache!!.exists() && !moduleCache!!.mkdirs()) Timber.e("Failed to mkdir module cache dir!") super.onCreate(savedInstanceState) - TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker) + val intent = this.intent val target: String val name: String? @@ -131,15 +129,9 @@ class InstallerActivity : AppCompatActivity() { finish() return } - // Note: Sentry only send this info on crash. if (MainApplication.isCrashReportingEnabled) { - val breadcrumb = SentryBreadcrumb() - breadcrumb.setType("install") - breadcrumb.setData("target", target) - breadcrumb.setData("name", name) - breadcrumb.setData("checksum", checksum) - breadcrumb.setCategory("app.action.preinstall") - SentryMain.addSentryBreadcrumb(breadcrumb) + // set countly breadcrumb + Countly.sharedInstance().crashes().addCrashBreadcrumb("InstallerActivity.onCreate") } val urlMode = target.startsWith("http://") || target.startsWith("https://") window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -183,9 +175,18 @@ class InstallerActivity : AppCompatActivity() { wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Fox:Installer") prgInd?.visibility = View.VISIBLE if (urlMode) installerTerminal!!.addLine("- Downloading $name") - TrackHelper.track().event("installer_start", name).with(MainApplication.INSTANCE!!.tracker) + // track install + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("install", mapOf( + "name" to name, + "url" to target, + "checksum" to checksum, + "noExtensions" to noExtensions, + "rootless" to rootless, + "mmtReborn" to mmtReborn + )) + } Thread(Runnable { - // ensure module cache is is in our cache dir if (urlMode && !moduleCache!!.absolutePath.startsWith(MainApplication.INSTANCE!!.cacheDir.absolutePath)) throw SecurityException( "Module cache is not in cache dir!" @@ -533,18 +534,7 @@ class InstallerActivity : AppCompatActivity() { } // Note: Sentry only send this info on crash. if (MainApplication.isCrashReportingEnabled) { - val breadcrumb = SentryBreadcrumb() - breadcrumb.setType("install") - breadcrumb.setData("moduleId", if (moduleId == null) "" else moduleId) - breadcrumb.setData("mmtReborn", if (mmtReborn) "true" else "false") - breadcrumb.setData("isAnyKernel3", if (anyKernel3) "true" else "false") - breadcrumb.setData("noExtensions", if (noExtensions) "true" else "false") - breadcrumb.setData("magiskCmdLine", if (magiskCmdLine) "true" else "false") - breadcrumb.setData( - "ansi", if (installerTerminal!!.isAnsiEnabled) "enabled" else "disabled" - ) - breadcrumb.setCategory("app.action.install") - SentryMain.addSentryBreadcrumb(breadcrumb) + Countly.sharedInstance().crashes().addCrashBreadcrumb("InstallerActivity.doInstall") } if (mmtReborn && magiskCmdLine) { Timber.w("mmtReborn and magiskCmdLine may not work well together") diff --git a/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerInitializer.kt b/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerInitializer.kt index ee0f6a1..574f795 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerInitializer.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerInitializer.kt @@ -11,6 +11,7 @@ import com.fox2code.mmm.NotificationType import com.fox2code.mmm.utils.io.Files.Companion.existsSU import com.topjohnwu.superuser.NoShellException import com.topjohnwu.superuser.Shell +import ly.count.android.sdk.Countly import timber.log.Timber import java.io.File @@ -125,7 +126,7 @@ class InstallerInitializer { if (Shell.isAppGrantedRoot() == null || !Shell.isAppGrantedRoot()!!) { // if Shell.isAppGrantedRoot() == null loop until it's not null return if (Shell.isAppGrantedRoot() == null) { - Thread.sleep(100) + Thread.sleep(150) tryGetMagiskPath(forceCheck) } else { null @@ -162,33 +163,53 @@ class InstallerInitializer { if (BuildConfig.DEBUG) { Timber.i("Magisk path 1: %s", mgskPth) } - } else if (Shell.cmd("if [ -d /data/adb/ksu ] && [ -f /data/adb/ksud ]; then echo true; else echo false; fi", "su -V").to( + } else if (Shell.cmd("if [ -d /data/adb/ksu ]; then echo true; else echo false; fi", "su -V").to( output ).exec().isSuccess && "true" == output[0] ) { - mgskPth = "/data/adb" - isKsu = true + // check su -v for kernelsu + val suVer: ArrayList = ArrayList() + Shell.cmd("su -v").to(suVer).exec() + if (suVer.size > 0 && suVer[0].contains("ksu") || suVer[0].contains("Kernelsu", true)) { + if (BuildConfig.DEBUG) { + Timber.i("Kernelsu detected") + } + mgskPth = "/data/adb" + isKsu = true + // if analytics enabled, set breadcrumb for countly + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().crashes().addCrashBreadcrumb("ksu detected") + } + } else { + if (BuildConfig.DEBUG) { + Timber.e("[ANOMALY] Kernelsu not detected but /data/adb/ksu exists") + } + return null + } + } else { + if (BuildConfig.DEBUG) { + Timber.e("Failed to get Magisk path") + } + return null } Timber.i("Magisk runtime path: %s", mgskPth) mgskVerCode = output[1].toInt() Timber.i("Magisk version code: %s", mgskVerCode) - if (mgskPth != null) { - if (mgskVerCode >= Constants.MAGISK_VER_CODE_FLAT_MODULES && mgskVerCode < Constants.MAGISK_VER_CODE_PATH_SUPPORT && (mgskPth.isEmpty() || !File( - mgskPth - ).exists()) - ) { - mgskPth = "/sbin" - } + if (mgskVerCode >= Constants.MAGISK_VER_CODE_FLAT_MODULES && mgskVerCode < Constants.MAGISK_VER_CODE_PATH_SUPPORT && (mgskPth.isEmpty() || !File( + mgskPth + ).exists()) + ) { + mgskPth = "/sbin" } - if (mgskPth != null) { - if (mgskPth.isNotEmpty() && existsSU(File(mgskPth))) { - Companion.mgskPth = mgskPth - } else { - Timber.e("Failed to get Magisk path (Got $mgskPth)") - mgskPth = null - } + if (mgskPth.isNotEmpty() && existsSU(File(mgskPth))) { + Companion.mgskPth = mgskPth } else { - Timber.e("Failed to get Magisk path (Got null or other)") + Timber.e("Failed to get Magisk path (Got $mgskPth)") + mgskPth = null + } + // if mgskPth is null, but we're granted root, log an error + if (mgskPth == null && Shell.isAppGrantedRoot() == true) { + Timber.e("[ANOMALY] Failed to get Magisk path but granted root") } Companion.mgskVerCode = mgskVerCode return mgskPth diff --git a/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt b/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt index cc827d3..f0bff13 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt @@ -17,7 +17,7 @@ import com.fox2code.mmm.utils.room.ModuleListCacheDatabase import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.io.SuFileInputStream -import org.matomo.sdk.extra.TrackHelper +import ly.count.android.sdk.Countly import timber.log.Timber import java.io.BufferedReader import java.io.IOException @@ -30,9 +30,9 @@ class ModuleManager private constructor() : SyncManager() { private var updatableModuleCount = 0 override fun scanInternal(updateListener: UpdateListener) { - // if last_shown_setup is not "v4", then refuse to continue + // if last_shown_setup is not "v5", then refuse to continue if (MainApplication.getSharedPreferences("mmm")!! - .getString("last_shown_setup", "") != "v4" + .getString("last_shown_setup", "") != "v5" ) { return } @@ -145,9 +145,12 @@ class ModuleManager private constructor() : SyncManager() { if (modulesList.isNotEmpty()) { modulesList.deleteCharAt(modulesList.length - 1) } - // send list to matomo - TrackHelper.track().event("installed_modules", modulesList.toString()) - .with(MainApplication.INSTANCE!!.tracker) + // send list to countly if analytics is enabled + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("modules", HashMap().apply { + put("modules", modulesList.toString()) + }) + } if (BuildConfig.DEBUG) if (BuildConfig.DEBUG) Timber.d("Scan update") val modulesUpdate = SuFile("/data/adb/modules_update").list() if (modulesUpdate != null) { diff --git a/app/src/main/kotlin/com/fox2code/mmm/markdown/MarkdownActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/markdown/MarkdownActivity.kt index 5e98e57..78325dd 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/markdown/MarkdownActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/markdown/MarkdownActivity.kt @@ -25,7 +25,6 @@ import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.matomo.sdk.extra.TrackHelper import timber.log.Timber import java.io.IOException import java.nio.charset.StandardCharsets @@ -35,7 +34,7 @@ class MarkdownActivity : AppCompatActivity() { private var footer: TextView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker) + val intent = this.intent if (!MainApplication.checkSecret(intent)) { Timber.e("Impersonation detected!") diff --git a/app/src/main/kotlin/com/fox2code/mmm/module/ActionButtonType.kt b/app/src/main/kotlin/com/fox2code/mmm/module/ActionButtonType.kt index a4ff066..0f5cd73 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/module/ActionButtonType.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/module/ActionButtonType.kt @@ -11,7 +11,6 @@ import android.text.Spanned import android.widget.TextView import android.widget.Toast import androidx.annotation.DrawableRes -import com.fox2code.foxcompat.view.FoxDisplay import com.fox2code.mmm.BuildConfig import com.fox2code.mmm.MainApplication import com.fox2code.mmm.MainApplication.Companion.INSTANCE @@ -31,7 +30,7 @@ import com.fox2code.mmm.utils.IntentHelper.Companion.openUrlAndroidacy import com.google.android.material.chip.Chip import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.noties.markwon.Markwon -import org.matomo.sdk.extra.TrackHelper +import ly.count.android.sdk.Countly import timber.log.Timber import java.util.Objects @@ -50,7 +49,12 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("view_notes", name).with(INSTANCE!!.tracker) + // if analytics is enabled, track the event + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_description", HashMap().apply { + put("module", name ?: "null") + }) + } val notesUrl = moduleHolder.repoModule?.notesUrl if (isAndroidacyLink(notesUrl)) { try { @@ -145,7 +149,10 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("view_update_install", name).with(INSTANCE!!.tracker) + // send event to countly + Countly.sharedInstance().events().recordEvent("view_update_install", HashMap().apply { + put("module", name ?: "null") + }) // if text is reinstall, we need to uninstall first - warn the user but don't proceed if (moduleHolder.moduleInfo != null) { // get the text @@ -216,7 +223,9 @@ enum class ActionButtonType { { Uri.parse(updateZipUrl) }, moduleHolder.updateZipRepo ) - val dim5dp = FoxDisplay.dpToPixel(5f) + val dim5dp = INSTANCE!!.lastActivity?.resources!!.getDimensionPixelSize( + R.dimen.dim5dp + ) builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp) val alertDialog = builder.show() for (i in -3..-1) { @@ -255,7 +264,12 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("uninstall_module", name).with(INSTANCE!!.tracker) + // if analytics is enabled, track the event + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_uninstall", HashMap().apply { + put("module", name ?: "null") + }) + } Timber.i(Integer.toHexString(moduleHolder.moduleInfo?.flags ?: 0)) if (!instance!!.setUninstallState( moduleHolder.moduleInfo!!, !moduleHolder.hasFlag( @@ -310,7 +324,11 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("config_module", name).with(INSTANCE!!.tracker) + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_config", HashMap().apply { + put("module", name ?: "null") + }) + } if (isAndroidacyLink(config)) { openUrlAndroidacy(button.context, config, true) } else { @@ -333,7 +351,11 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("support_module", name).with(INSTANCE!!.tracker) + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_support", HashMap().apply { + put("module", name ?: "null") + }) + } openUrl(button.context, Objects.requireNonNull(moduleHolder.mainModuleInfo.support)) } }, @@ -352,7 +374,11 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("donate_module", name).with(INSTANCE!!.tracker) +if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_donate", HashMap().apply { + put("module", name ?: "null") + }) + } openUrl(button.context, moduleHolder.mainModuleInfo.donate) } }, @@ -368,7 +394,11 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("warning_module", name).with(INSTANCE!!.tracker) + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_warning", HashMap().apply { + put("module", name ?: "null") + }) + } MaterialAlertDialogBuilder(button.context).setTitle(R.string.warning) .setMessage(R.string.warning_message).setPositiveButton( R.string.understand @@ -390,7 +420,11 @@ enum class ActionButtonType { } else { moduleHolder.repoModule?.moduleInfo?.name } - TrackHelper.track().event("safe_module", name).with(INSTANCE!!.tracker) + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_safe", HashMap().apply { + put("module", name ?: "null") + }) + } MaterialAlertDialogBuilder(button.context).setTitle(R.string.safe_module) .setMessage(R.string.safe_message).setPositiveButton( R.string.understand @@ -409,7 +443,11 @@ enum class ActionButtonType { moduleHolder.repoModule?.moduleInfo?.name } // positive button executes install logic and says reinstall. negative button does nothing - TrackHelper.track().event("remote_module", name).with(INSTANCE!!.tracker) + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_update_install", HashMap().apply { + put("module", name ?: "null") + }) + } val madb = MaterialAlertDialogBuilder(button.context) madb.setTitle(R.string.remote_module) val moduleInfo: ModuleInfo = if (moduleHolder.mainModuleInfo != null) { @@ -462,8 +500,11 @@ enum class ActionButtonType { moduleHolder.repoModule?.moduleInfo?.name } if (BuildConfig.DEBUG) Timber.d("doAction: remote module for %s", name) - TrackHelper.track().event("view_update_install", name) - .with(INSTANCE!!.tracker) + if (MainApplication.analyticsAllowed()) { + Countly.sharedInstance().events().recordEvent("view_update_install", HashMap().apply { + put("module", name ?: "null") + }) + } // Androidacy manage the selection between download and install if (isAndroidacyLink(updateZipUrl)) { if (BuildConfig.DEBUG) Timber.d("Androidacy link detected") @@ -518,7 +559,9 @@ enum class ActionButtonType { { Uri.parse(updateZipUrl) }, moduleHolder.updateZipRepo ) - val dim5dp = FoxDisplay.dpToPixel(5f) + val dim5dp = INSTANCE!!.lastActivity?.resources!!.getDimensionPixelSize( + R.dimen.dim5dp + ) builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp) val alertDialog = builder.show() for (i in -3..-1) { diff --git a/app/src/main/kotlin/com/fox2code/mmm/module/ModuleViewAdapter.kt b/app/src/main/kotlin/com/fox2code/mmm/module/ModuleViewAdapter.kt index fc14398..7a3ae44 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/module/ModuleViewAdapter.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/module/ModuleViewAdapter.kt @@ -26,7 +26,6 @@ import androidx.annotation.StringRes import androidx.cardview.widget.CardView import androidx.core.graphics.ColorUtils import androidx.recyclerview.widget.RecyclerView -import com.fox2code.foxcompat.view.FoxDisplay import com.fox2code.mmm.BuildConfig import com.fox2code.mmm.MainApplication import com.fox2code.mmm.R @@ -74,7 +73,6 @@ class ModuleViewAdapter : RecyclerView.Adapter() { private val creditText: TextView private val descriptionText: TextView private val moduleOptionsHolder: HorizontalScrollView - private val moduleLayoutHelper: TextView private val updateText: TextView private val actionsButtons: Array private val actionButtonsTypes: ArrayList @@ -93,7 +91,6 @@ class ModuleViewAdapter : RecyclerView.Adapter() { creditText = itemView.findViewById(R.id.credit_text) descriptionText = itemView.findViewById(R.id.description_text) moduleOptionsHolder = itemView.findViewById(R.id.module_options_holder) - moduleLayoutHelper = itemView.findViewById(R.id.module_layout_helper) updateText = itemView.findViewById(R.id.updated_text) actionsButtons = arrayOfNulls(6) actionsButtons[0] = itemView.findViewById(R.id.button_action1) @@ -194,7 +191,6 @@ class ModuleViewAdapter : RecyclerView.Adapter() { } creditText.visibility = View.VISIBLE moduleOptionsHolder.visibility = View.VISIBLE - moduleLayoutHelper.visibility = View.VISIBLE descriptionText.visibility = View.VISIBLE val moduleInfo = moduleHolder.mainModuleInfo moduleInfo.verify() @@ -249,7 +245,6 @@ class ModuleViewAdapter : RecyclerView.Adapter() { descriptionText.text = moduleInfo.description } val updateText = moduleHolder.updateTimeText - var hasUpdateText = true if (updateText.isNotEmpty()) { val repoModule = moduleHolder.repoModule this.updateText.visibility = View.VISIBLE @@ -267,7 +262,6 @@ ${getString(R.string.module_repo)} ${moduleHolder.repoName}""" + if ((repoModule this.updateText.setText(R.string.substratum_builtin_module) } else { this.updateText.visibility = View.GONE - hasUpdateText = false } actionButtonsTypes.clear() moduleHolder.getButtons(itemView.context, actionButtonsTypes, showCaseMode) @@ -290,12 +284,6 @@ ${getString(R.string.module_repo)} ${moduleHolder.repoName}""" + if ((repoModule } if (actionButtonsTypes.isEmpty()) { moduleOptionsHolder.visibility = View.GONE - moduleLayoutHelper.visibility = View.GONE - } else if (actionButtonsTypes.size > 2 || !hasUpdateText) { - moduleLayoutHelper.minHeight = FoxDisplay.dpToPixel(36f) - .coerceAtLeast(moduleOptionsHolder.height - FoxDisplay.dpToPixel(14f)) - } else { - moduleLayoutHelper.minHeight = FoxDisplay.dpToPixel(4f) } cardView.isClickable = false if (moduleHolder.isModuleHolder && moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)) { @@ -316,7 +304,6 @@ ${getString(R.string.module_repo)} ${moduleHolder.repoName}""" + if ((repoModule switchMaterial.visibility = View.GONE creditText.visibility = View.GONE moduleOptionsHolder.visibility = View.GONE - moduleLayoutHelper.visibility = View.GONE descriptionText.visibility = View.GONE updateText.visibility = View.GONE titleText.text = " " @@ -331,7 +318,9 @@ ${getString(R.string.module_repo)} ${moduleHolder.repoName}""" + if ((repoModule } if (type === ModuleHolder.Type.NOTIFICATION) { val notificationType = moduleHolder.notificationType - titleText.setText(notificationType?.textId ?: 0) + if (notificationType?.textId != null) { + titleText.setText(notificationType.textId) + } // set title text appearance titleText.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge) if (notificationType != null) { @@ -353,7 +342,9 @@ ${getString(R.string.module_repo)} ${moduleHolder.repoName}""" + if ((repoModule } } if (type === ModuleHolder.Type.SEPARATOR) { - titleText.setText(if (moduleHolder.separator != null) moduleHolder.separator.title else 0) + if (moduleHolder.separator != null) { + titleText.text = getString(moduleHolder.separator.title) + } } if (DEBUG) { if (vType != null) { diff --git a/app/src/main/kotlin/com/fox2code/mmm/repo/CustomRepoManager.kt b/app/src/main/kotlin/com/fox2code/mmm/repo/CustomRepoManager.kt index fe69014..d35964a 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/repo/CustomRepoManager.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/repo/CustomRepoManager.kt @@ -29,7 +29,7 @@ class CustomRepoManager internal constructor( init { repoCount = 0 // refuse to load if setup is not complete - if (getSharedPreferences("mmm")!!.getString("last_shown_setup", "") == "v4") { + if (getSharedPreferences("mmm")!!.getString("last_shown_setup", "") == "v5") { val i = 0 val lastFilled = intArrayOf(0) // now the same as above but for room database diff --git a/app/src/main/kotlin/com/fox2code/mmm/repo/RepoManager.kt b/app/src/main/kotlin/com/fox2code/mmm/repo/RepoManager.kt index 0382a0c..d3fe7e4 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/repo/RepoManager.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/repo/RepoManager.kt @@ -56,7 +56,7 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa repoData = LinkedHashMap() modules = HashMap() // refuse to load if setup is not complete - if (getSharedPreferences("mmm")!!.getString("last_shown_setup", "") == "v4") { + if (getSharedPreferences("mmm")!!.getString("last_shown_setup", "") == "v5") { // We do not have repo list config yet. androidacyRepoData = addAndroidacyRepoData() val altRepo = addRepoData(MAGISK_ALT_REPO, "Magisk Modules Alt Repo") @@ -82,8 +82,8 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa } private fun populateDefaultCache(repoData: RepoData?) { - // if last_shown_setup is not "v4", them=n refuse to continue - if (getSharedPreferences("mmm")!!.getString("last_shown_setup", "") != "v4") { + // if last_shown_setup is not "v5", them=n refuse to continue + if (getSharedPreferences("mmm")!!.getString("last_shown_setup", "") != "v5") { return } // make sure repodata is not null diff --git a/app/src/main/kotlin/com/fox2code/mmm/settings/DebugFragment.kt b/app/src/main/kotlin/com/fox2code/mmm/settings/DebugFragment.kt index bf2d597..65c88ac 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/settings/DebugFragment.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/settings/DebugFragment.kt @@ -16,7 +16,6 @@ import com.fox2code.mmm.Constants import com.fox2code.mmm.MainApplication import com.fox2code.mmm.R import com.fox2code.mmm.installer.InstallerInitializer -import com.fox2code.mmm.utils.sentry.SentryMain import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.apache.commons.io.FileUtils import timber.log.Timber @@ -90,7 +89,7 @@ class DebugFragment : PreferenceFragmentCompat() { }.setNegativeButton(R.string.no) { _: DialogInterface?, _: Int -> }.show() true } - if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG || InstallerInitializer.peekMagiskPath() == null) { + if (!BuildConfig.DEBUG || InstallerInitializer.peekMagiskPath() == null) { // Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app Timber.i(InstallerInitializer.peekMagiskPath()) findPreference("pref_test_crash")!!.isVisible = false diff --git a/app/src/main/kotlin/com/fox2code/mmm/settings/InfoFragment.kt b/app/src/main/kotlin/com/fox2code/mmm/settings/InfoFragment.kt index 156da00..c5b81a1 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/settings/InfoFragment.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/settings/InfoFragment.kt @@ -152,7 +152,7 @@ class InfoFragment : PreferenceFragmentCompat() { // open androidacy IntentHelper.openUrl( MainApplication.INSTANCE!!.lastActivity!!, - "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate" + "https://www.androidacy.com/membership-join/?utm_source=AMMM&utm_medium=app&utm_campaign=donate" ) true } @@ -164,7 +164,7 @@ class InfoFragment : PreferenceFragmentCompat() { clipboard.setPrimaryClip( ClipData.newPlainText( toastText, - "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate" + "https://www.androidacy.com/membership-join/?utm_source=AMMM&utm_medium=app&utm_campaign=donate" ) ) Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show() diff --git a/app/src/main/kotlin/com/fox2code/mmm/settings/PrivacyFragment.kt b/app/src/main/kotlin/com/fox2code/mmm/settings/PrivacyFragment.kt index 7b1e20e..7777d83 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/settings/PrivacyFragment.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/settings/PrivacyFragment.kt @@ -17,7 +17,6 @@ import com.fox2code.mmm.BuildConfig import com.fox2code.mmm.MainActivity import com.fox2code.mmm.MainApplication import com.fox2code.mmm.R -import com.fox2code.mmm.utils.sentry.SentryMain import com.google.android.material.dialog.MaterialAlertDialogBuilder import timber.log.Timber import kotlin.system.exitProcess @@ -53,7 +52,6 @@ class PrivacyFragment : PreferenceFragmentCompat() { // Crash reporting val crashReportingPreference = findPreference("pref_crash_reporting") - if (!SentryMain.IS_SENTRY_INSTALLED) crashReportingPreference!!.isVisible = false crashReportingPreference!!.isChecked = MainApplication.isCrashReportingEnabled val initialValue: Any = MainApplication.isCrashReportingEnabled crashReportingPreference.onPreferenceChangeListener = @@ -86,5 +84,16 @@ class PrivacyFragment : PreferenceFragmentCompat() { materialAlertDialogBuilder.show() true } + // on pref_analytics_enabled change, update pref_crash_reporting (switch must be off and disabled if analytics is off) + val analyticsPreference = findPreference("pref_analytics_enabled") + analyticsPreference!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> + if (initialValue === newValue) return@OnPreferenceChangeListener true + crashReportingPreference.isEnabled = newValue as Boolean + if (!newValue) crashReportingPreference.isChecked = false + true + } + // now, disable pref_crash_reporting if analytics is off + crashReportingPreference.isEnabled = analyticsPreference.isChecked } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/fox2code/mmm/settings/RepoFragment.kt b/app/src/main/kotlin/com/fox2code/mmm/settings/RepoFragment.kt index 81d6a01..c8edea2 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/settings/RepoFragment.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/settings/RepoFragment.kt @@ -24,8 +24,6 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import androidx.preference.TwoStatePreference import androidx.room.Room -import com.fox2code.foxcompat.view.FoxDisplay -import com.fox2code.foxcompat.view.FoxViewCompat import com.fox2code.mmm.BuildConfig import com.fox2code.mmm.MainActivity import com.fox2code.mmm.MainApplication @@ -177,7 +175,7 @@ class RepoFragment : PreferenceFragmentCompat() { // User clicked OK button. Open GitHub releases page val browserIntent = Intent( Intent.ACTION_VIEW, - Uri.parse("https://www.androidacy.com/downloads/?view=FoxMMM&utm_source=FoxMMM&utm_medium=app&utm_campaign=FoxMMM") + Uri.parse("https://www.androidacy.com/downloads/?view=AMMM&utm_source=AMMM&utm_medium=app&utm_campaign=AMMM") ) startActivity(browserIntent) }.show() @@ -623,9 +621,17 @@ class RepoFragment : PreferenceFragmentCompat() { override fun afterTextChanged(s: Editable) {} }) positiveButton.isEnabled = false - val dp10 = FoxDisplay.dpToPixel(10f) - val dp20 = FoxDisplay.dpToPixel(20f) - FoxViewCompat.setMargin(input, dp20, dp10, dp20, dp10) + val dp10 = MainApplication.INSTANCE!!.lastActivity?.resources?.getDimensionPixelSize( + R.dimen.dp10 + ) ?: 0 + val dp20 = MainApplication.INSTANCE!!.lastActivity?.resources?.getDimensionPixelSize( + R.dimen.dp20 + ) ?: 0 + alertDialog.window!!.setSoftInputMode(20) + alertDialog.window!!.setLayout( + dp20, + dp10 + ) true } } diff --git a/app/src/main/kotlin/com/fox2code/mmm/settings/SettingsActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/settings/SettingsActivity.kt index f34129f..ecde152 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/settings/SettingsActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/settings/SettingsActivity.kt @@ -33,7 +33,6 @@ import com.fox2code.rosettax.LanguageActivity import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.navigation.NavigationBarView import com.mikepenz.aboutlibraries.LibsBuilder -import org.matomo.sdk.extra.TrackHelper import timber.log.Timber import java.sql.Timestamp @@ -89,7 +88,7 @@ class SettingsActivity : AppCompatActivity(), LanguageActivity, .commit() true } - TrackHelper.track().screen(this).with(INSTANCE!!.tracker) + setContentView(R.layout.settings_activity) setTitle(R.string.app_name_v2) val ts = Timestamp(System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000) diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/ExternalHelper.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/ExternalHelper.kt index 18a7828..35a133f 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/ExternalHelper.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/utils/ExternalHelper.kt @@ -15,7 +15,6 @@ import android.net.Uri import android.os.Build import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.core.app.ActivityOptionsCompat import androidx.core.util.Supplier import com.fox2code.mmm.Constants import com.topjohnwu.superuser.internal.UiThreadHandler @@ -57,13 +56,6 @@ class ExternalHelper private constructor() { fun openExternal(context: Context, uri: Uri?, repoId: String?): Boolean { if (label == null) return false - val param = - ActivityOptionsCompat.makeCustomAnimation( - context, - rikka.core.R.anim.fade_in, - rikka.core.R.anim.fade_out - ) - .toBundle() var intent = Intent(FOX_MMM_OPEN_EXTERNAL, uri) intent.flags = IntentHelper.FLAG_GRANT_URI_PERMISSION intent.putExtra(FOX_MMM_EXTRA_REPO_ID, repoId) @@ -76,7 +68,7 @@ class ExternalHelper private constructor() { if (multi) { context.startActivity(intent) } else { - context.startActivity(intent, param) + context.startActivity(intent, null) } return true } catch (e: ActivityNotFoundException) { @@ -90,7 +82,7 @@ class ExternalHelper private constructor() { } intent.component = fallback try { - context.startActivity(intent, param) + context.startActivity(intent, null) return true } catch (e: ActivityNotFoundException) { Timber.e(e) diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt index b60ba72..13b9196 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt @@ -159,7 +159,7 @@ class RuntimeUtils { if (BuildConfig.DEBUG) Timber.i("Checking if we need to run setup") // Check if context is the first launch using prefs and if doSetupRestarting was passed in the intent val prefs = MainApplication.getSharedPreferences("mmm")!! - var firstLaunch = prefs.getString("last_shown_setup", null) != "v4" + var firstLaunch = prefs.getString("last_shown_setup", null) != "v5" // First launch // context is intentionally separate from the above if statement, because it needs to be checked even if the first launch check is true due to some weird edge cases if (activity.intent.getBooleanExtra("doSetupRestarting", false)) { @@ -264,7 +264,7 @@ class RuntimeUtils { snackbar.setAction(R.string.upgrade_now) { val intent = Intent(Intent.ACTION_VIEW) intent.data = - Uri.parse("https://androidacy.com/membership-join/#utm_source=foxmmm&utm_medium=app&utm_campaign=upgrade_snackbar") + Uri.parse("https://androidacy.com/membership-join/#utm_source=AMMM&utm_medium=app&utm_campaign=upgrade_snackbar") activity.startActivity(intent) } snackbar.setAnchorView(R.id.bottom_navigation) diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt index 3d6b08b..f176e51 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt @@ -29,7 +29,7 @@ import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskPath import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskVersion import com.fox2code.mmm.utils.io.Files.Companion.makeBuffer import com.google.net.cronet.okhttptransport.CronetInterceptor -import io.sentry.android.okhttp.SentryOkHttpInterceptor +import ly.count.android.sdk.Countly import okhttp3.Cache import okhttp3.Dns import okhttp3.HttpUrl.* @@ -53,11 +53,13 @@ import org.chromium.net.CronetEngine import timber.log.Timber import java.io.File import java.io.IOException +import java.lang.Long.* import java.net.InetAddress import java.net.Proxy import java.net.UnknownHostException import java.nio.charset.StandardCharsets import java.util.Objects +import java.util.UUID.randomUUID import java.util.concurrent.TimeUnit import kotlin.system.exitProcess @@ -290,9 +292,9 @@ enum class Http {; // User-Agent format was agreed on telegram androidacyUA = if (hasWebView) { WebSettings.getDefaultUserAgent(mainApplication) - .replace("wv", "") + " FoxMMM/" + BuildConfig.VERSION_CODE + .replace("wv", "") + " AMMM/" + BuildConfig.VERSION_CODE } else { - "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE + ")" + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" + " FoxMmm/" + BuildConfig.VERSION_CODE + "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE + ")" + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" + " AMMM/" + BuildConfig.VERSION_CODE } httpclientBuilder.addInterceptor(Interceptor { chain: Interceptor.Chain? -> val request: Request.Builder = chain!!.request().newBuilder() @@ -340,9 +342,6 @@ enum class Http {; chain.proceed(request.build()) }) - // add sentry interceptor - httpclientBuilder.addInterceptor(SentryOkHttpInterceptor()) - // Add cronet interceptor // init cronet try { @@ -457,6 +456,10 @@ enum class Http {; if (url.isEmpty()) { throw IOException("Empty URL") } + val uniqid = randomUUID().toString() + if (MainApplication.analyticsAllowed() && MainApplication.isCrashReportingEnabled) { + Countly.sharedInstance().apm().startNetworkRequest(uniqid, url) + } var response: Response? response = try { (if (allowCache) getHttpClientWithCache() else getHttpClient())!!.newCall( @@ -465,7 +468,7 @@ enum class Http {; ).get().build() ).execute() } catch (e: IOException) { - Timber.e(e, "Failed to post %s", url) + Timber.e(e, "Failed to get %s", url) // detect ssl errors, i.e., cert authority invalid by looking at the message if (e.message != null && e.message!!.contains("_CERT_")) { MainApplication.INSTANCE!!.lastActivity!!.runOnUiThread { @@ -540,6 +543,11 @@ enum class Http {; if (BuildConfig.DEBUG) Timber.d("doHttpGet: returning " + responseBody.contentLength() + " bytes") } } + if (MainApplication.analyticsAllowed() && MainApplication.isCrashReportingEnabled) { + Countly.sharedInstance().apm().endNetworkRequest(uniqid, url, response!!.code, + 0, responseBody!!.contentLength().toInt() + ) + } return responseBody?.bytes() ?: ByteArray(0) } @@ -552,6 +560,11 @@ enum class Http {; @Throws(IOException::class) private fun doHttpPostRaw(url: String, data: String, allowCache: Boolean): Any { if (BuildConfig.DEBUG) Timber.d("POST %s", url) + + val uniqid = randomUUID().toString() + if (MainApplication.analyticsAllowed() && MainApplication.isCrashReportingEnabled) { + Countly.sharedInstance().apm().startNetworkRequest(uniqid, url) + } var response: Response? try { response = @@ -625,11 +638,20 @@ enum class Http {; response = response.cacheResponse if (response != null) responseBody = response.body } + if (MainApplication.analyticsAllowed() && MainApplication.isCrashReportingEnabled) { + Countly.sharedInstance().apm().endNetworkRequest(uniqid, url, response!!.code, + data.toByteArray().size.toLong().toInt(), responseBody.contentLength().toInt() + ) + } return responseBody.bytes() } @Throws(IOException::class) fun doHttpGet(url: String, progressListener: ProgressListener): ByteArray { + val uniqid = randomUUID().toString() + if (MainApplication.analyticsAllowed() && MainApplication.isCrashReportingEnabled) { + Countly.sharedInstance().apm().startNetworkRequest(uniqid, url) + } val response: Response try { response = @@ -713,6 +735,11 @@ enum class Http {; progressListener.onUpdate( (downloaded / divider).toInt(), (target / divider).toInt(), true ) + if (MainApplication.analyticsAllowed() && MainApplication.isCrashReportingEnabled) { + Countly.sharedInstance().apm().endNetworkRequest(uniqid, url, response.code, + 0, responseBody.contentLength().toInt() + ) + } return byteArrayOutputStream.toByteArray() } diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/sentry/SentryBreadcrumb.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/sentry/SentryBreadcrumb.kt deleted file mode 100644 index a0482d5..0000000 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/sentry/SentryBreadcrumb.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information. - */ - -package com.fox2code.mmm.utils.sentry - -import io.sentry.Breadcrumb -import io.sentry.SentryLevel -import java.util.Objects - -class SentryBreadcrumb { - val breadcrumb: Breadcrumb = Breadcrumb() - - init { - breadcrumb.level = SentryLevel.INFO - } - - fun setType(type: String?) { - breadcrumb.type = type - } - - fun setData(key: String, value: Any?) { - @Suppress("NAME_SHADOWING") var value = value - if (value == null) value = "null" - Objects.requireNonNull(key) - breadcrumb.setData(key, value) - } - - fun setCategory(category: String?) { - breadcrumb.category = category - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/sentry/SentryMain.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/sentry/SentryMain.kt deleted file mode 100644 index ebe18e2..0000000 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/sentry/SentryMain.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information. - */ - -package com.fox2code.mmm.utils.sentry - -import android.annotation.SuppressLint -import android.content.Intent -import android.content.SharedPreferences -import android.net.Uri -import android.os.Process -import com.fox2code.mmm.BuildConfig -import com.fox2code.mmm.CrashHandler -import com.fox2code.mmm.MainApplication -import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.hideToken -import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.isAndroidacyLink -import com.fox2code.mmm.utils.io.net.HttpException -import io.sentry.Breadcrumb -import io.sentry.Hint -import io.sentry.Sentry -import io.sentry.SentryEvent -import io.sentry.SentryLevel -import io.sentry.SentryOptions.BeforeBreadcrumbCallback -import io.sentry.SentryOptions.BeforeSendCallback -import io.sentry.android.core.SentryAndroid -import io.sentry.android.core.SentryAndroidOptions -import io.sentry.android.fragment.FragmentLifecycleIntegration -import io.sentry.android.timber.SentryTimberIntegration -import io.sentry.protocol.SentryId -import org.matomo.sdk.extra.TrackHelper -import timber.log.Timber - -object SentryMain { - const val IS_SENTRY_INSTALLED = true - private var isCrashing = false - private var isSentryEnabled = false - private var crashExceptionId: SentryId? = null - - /** - * Initialize Sentry - * Sentry is used for crash reporting and performance monitoring. - */ - @JvmStatic - @SuppressLint("RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref") - fun initialize(mainApplication: MainApplication) { - Thread.setDefaultUncaughtExceptionHandler { _: Thread?, throwable: Throwable -> - isCrashing = true - MainApplication.clearCachedSharedPrefs() - TrackHelper.track().exception(throwable).with(MainApplication.INSTANCE!!.tracker) - // open crash handler and exit - val intent = Intent(mainApplication, CrashHandler::class.java) - // pass the entire exception to the crash handler - intent.putExtra("exception", throwable) - // add stacktrace as string - intent.putExtra("stacktrace", throwable.stackTrace) - // serialize Sentry.captureException and pass it to the crash handler - intent.putExtra("sentryException", throwable) - // pass crashReportingEnabled to crash handler - intent.putExtra("crashReportingEnabled", isSentryEnabled) - // add isCrashing to intent - intent.putExtra("isCrashing", true) - // add crashExceptionId to intent - if (crashExceptionId != null) { - intent.putExtra("lastEventId", crashExceptionId!!.toString()) - } else { - intent.putExtra("lastEventId", "") - } - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - Timber.e("Starting crash handler") - mainApplication.startActivity(intent) - Timber.e("Exiting") - Process.killProcess(Process.myPid()) - } - // If first_launch pref is not false, refuse to initialize Sentry - val sharedPreferences = MainApplication.getSharedPreferences("mmm")!! - if (sharedPreferences.getString("last_shown_setup", null) != "v4") { - return - } - isSentryEnabled = sharedPreferences.getBoolean("pref_crash_reporting_enabled", false) - // set sentryEnabled on preference change of pref_crash_reporting_enabled - sharedPreferences.registerOnSharedPreferenceChangeListener { sharedPreferences1: SharedPreferences, s: String? -> - if (s !== null && s == "pref_crash_reporting_enabled") { - isSentryEnabled = sharedPreferences1.getBoolean(s, false) - } - } - SentryAndroid.init(mainApplication) { options: SentryAndroidOptions -> - // If crash reporting is disabled, stop here. - if (!MainApplication.isCrashReportingEnabled) { - isSentryEnabled = false // Set sentry state to disabled - options.dsn = "" - } else { - // get pref_crash_reporting_pii pref - val crashReportingPii = sharedPreferences.getBoolean("crashReportingPii", false) - isSentryEnabled = true // Set sentry state to enabled - options.addIntegration( - FragmentLifecycleIntegration( - mainApplication, - enableFragmentLifecycleBreadcrumbs = true, - enableAutoFragmentLifecycleTracing = true - ) - ) - // Enable automatic activity lifecycle breadcrumbs - options.isEnableActivityLifecycleBreadcrumbs = true - // Enable automatic fragment lifecycle breadcrumbs - options.addIntegration(SentryTimberIntegration()) - options.isCollectAdditionalContext = true - options.isAttachThreads = true - options.isAttachStacktrace = true - options.isEnableNdk = true - options.addInAppInclude("com.fox2code.mmm") - options.addInAppInclude("com.fox2code.mmm.debug") - options.addInAppInclude("com.fox2code.mmm.fdroid") - options.addInAppExclude("com.fox2code.mmm.utils.sentry.SentryMain") - options.addInAppInclude("com.fox2code.mmm.utils") - // Respect user preference for sending PII. default is true on non fdroid builds, false on fdroid builds - options.isSendDefaultPii = 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.isAttachScreenshot = true - // It just tell if sentry should ping the sentry dsn to tell the app is running. Useful for performance and profiling. - options.isEnableAutoSessionTracking = true - // disable crash tracking - we handle that ourselves - options.isEnableUncaughtExceptionHandler = true - // 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.environment = BuildConfig.BUILD_TYPE - options.beforeSend = BeforeSendCallback { event: SentryEvent?, _: 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 (!isSentryEnabled) { - return@BeforeSendCallback null - } - crashExceptionId = event?.eventId - // if debug build, log everything, but for release only log errors - if (!BuildConfig.DEBUG) { - if (event?.level == SentryLevel.DEBUG || event?.level == SentryLevel.INFO || event?.level == SentryLevel.WARNING) { - return@BeforeSendCallback null - } - } - // remove all failed to fetch data messages - if (event?.message?.message?.contains("Failed to fetch") == true || event?.message?.message?.contains( - "Failed to load" - ) == true - ) { - return@BeforeSendCallback null - } - // for httpexception, do not send if error is 401, 403, 404, 429 - // get exception from event - val exception = event?.throwable ?: return@BeforeSendCallback event - // check status code - if (exception is HttpException) { - if (exception.errorCode in intArrayOf(401, 403, 404, 429)) { - return@BeforeSendCallback null - } - } - // if exception is null, return event - event - } - // Filter breadcrumb content from crash report. - options.beforeBreadcrumb = - BeforeBreadcrumbCallback { breadcrumb: Breadcrumb, _: Hint? -> - if (!isSentryEnabled) { - return@BeforeBreadcrumbCallback null - } - val url = breadcrumb.getData("url") as String? - if ("cloudflare-dns.com" == Uri.parse(url).host) { - return@BeforeBreadcrumbCallback null - } - if (isAndroidacyLink(url)) { - url?.let { hideToken(it) }?.let { breadcrumb.setData("url", it) } - } - breadcrumb - } - } - } - } - - fun addSentryBreadcrumb(sentryBreadcrumb: SentryBreadcrumb) { - if (MainApplication.isCrashReportingEnabled) { - Sentry.addBreadcrumb(sentryBreadcrumb.breadcrumb) - } - } -} diff --git a/app/src/main/res/layout/activity_crash_handler.xml b/app/src/main/res/layout/activity_crash_handler.xml index 9029d5c..dd8c48a 100644 --- a/app/src/main/res/layout/activity_crash_handler.xml +++ b/app/src/main/res/layout/activity_crash_handler.xml @@ -2,20 +2,29 @@ ~ Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information. --> - + android:layout_height="match_parent" + android:layout_gravity="center" + android:padding="4dp"> - + + - - + @@ -79,110 +88,36 @@ android:padding="4dp" /> - + + + + + android:layout_gravity="center|bottom" + android:orientation="horizontal"> - - + - - - - - - - - - - - + android:text="@string/restart" /> - - - - - - - - - - - - - - - - - - - - - - + android:layout_margin="10dp" + android:text="@string/reset_app" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml index 104da18..48057d9 100644 --- a/app/src/main/res/layout/activity_setup.xml +++ b/app/src/main/res/layout/activity_setup.xml @@ -11,7 +11,6 @@ android:layout_height="match_parent" android:layout_margin="0dp" android:padding="0dp" - app:fitsSystemWindowsInsets="start|end|bottom|top" tools:context=".SetupActivity"> - - - + + - - + + + android:padding="1dp" + app:layout_constraintTop_toTopOf="parent"> + + android:gravity="fill" + android:orientation="horizontal"> - - - - - - - - - - - - - - + + + android:textSize="19sp" /> - + - + android:layout_margin="2dp" + android:background="@null" + android:importantForAccessibility="no" + android:src="@drawable/ic_baseline_delete_forever_24" + android:textSize="16sp" + tools:ignore="RtlHardcoded,TouchTargetSizeCheck" /> - - - - - + + + android:layout_marginVertical="2dp" + android:autoLink="all" + android:padding="4dp" + android:text="@string/loading" + android:textAppearance="?attr/textAppearanceBodyMedium" + android:textColor="?android:attr/textColorSecondary" + android:textSize="16sp" /> + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + android:text="@string/loading" + android:visibility="gone" + app:chipIcon="@drawable/ic_baseline_error_24" /> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/webview.xml b/app/src/main/res/layout/webview.xml index d031877..40efd99 100644 --- a/app/src/main/res/layout/webview.xml +++ b/app/src/main/res/layout/webview.xml @@ -6,8 +6,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" - app:fitsSystemWindowsInsets="start|end|bottom|top"> + android:orientation="vertical"> السماح بإرسال معلومات إضافية في تقارير الأعطال ، والتي قد يحتوي بعضها على معلومات تعريف شخصية مثل عنوان IP ومعرفات الجهاز. قد يشمل ذلك معرفات الجهاز وعناوين IP. لن يتم استخدام أي بيانات لأي غرض آخر بخلاف تحليل الأعطال وتحسين الأداء. خطأ في الوصول إلى WebView. قد تتأثر الوظيفة. - اسمح لنا بتتبع استخدام التطبيق وعمليات التثبيت. متوافق تمامًا مع إجمالي الناتج المحلي ويستخدم Matomo ، الذي مخزن عن طريق Androidacy. + اسمح لنا بتتبع استخدام التطبيق وعمليات التثبيت. متوافق تمامًا مع إجمالي الناتج المحلي ويستخدم Countly ، الذي مخزن عن طريق Androidacy. لم تتم ترجمة اللغة %s. أتود ترجمتها ؟ إنشاء تأثير تمويه (Blur) خلف بعض الحوارات والعناصر. لاحظ أن التمويه (Blur) قد لا يعمل بشكل جيد على بعض الأجهزة وقد لا يعمل مع الجميع. حدث خطأ أثناء قراءة الخصائص المشتركة. الرجاء إعادة تعيين التطبيق. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index abfcaac..7de68d7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -304,7 +304,7 @@ Je vyžadováná URL Repozitář už existuje. K aktivaci režimu prezentace je vyžadován restart aplikace. - Umožněte nám sledovat používání a instalace aplikací. Plně kompatibilní s GDPR a používá Matomo, hostované společností Androidacy. + Umožněte nám sledovat používání a instalace aplikací. Plně kompatibilní s GDPR a používá Countly, hostované společností Androidacy. Novinky a aktualizace Ladění Zpátky diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 4ef7cbc..b0dee6f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -319,7 +319,7 @@ Απαίτηση wi-fi ή δικτύου χωρίς μετρήσεις για έλεγχο ενημερώσεων. Προτείνεται να είναι σε λειτουργία αν έχετε περιορισμένα δεδομένα κινητής τηλεφωνίας. Συνέχισε να πατάς, ώστε να γίνεις δεκτός στο Hogwarts! Η γλώσσα %s δεν έχει μεταφραστεί. Μπορείτε να μας βοηθήσετε στην μετάφρασή της; - Επιτρέψτε μας να παρακολουθούμε τη χρήση και τις εγκαταστάσεις εφαρμογών. Πλήρως συμβατό με το GDPR και χρησιμοποιεί το Matomo, που φιλοξενείται από το Androidacy. + Επιτρέψτε μας να παρακολουθούμε τη χρήση και τις εγκαταστάσεις εφαρμογών. Πλήρως συμβατό με το GDPR και χρησιμοποιεί το Countly, που φιλοξενείται από το Androidacy. Προσδιορίστε μια έκδοση από ενημερώσεις modules που θα θέλατε να αγνοήσετε. Παρακαλείσθε να χρησιμοποιήσετε μόνο τον κωδικό έκδοσης. Μπορείτε να βρείτε αυτόν τον κωδικό κρατώντας πατημένη την έκδοση στην λίστα με τα modules. Χρησιμοποιήστε ^ στην αρχή για να αναφερθείτε στην καθορισμένη έκδοση και σε όλες τις νεότερες. Χρησιμοποιήστε $ στο τέλος για να αναφερθείτε σε όλες τις εκδόσεις πριν την καθορισμένη. Το stacktrace μπορεί να βρεθεί παρακάτω. Ωστόσο, ανεπιφύλακτα σας συνιστούμε να χρησιμοποιήσετε την παρακάτω φόρμα σχολίων για να υποβάλετε σχόλια. Με αυτόν τον τρόπο, αντί να αντιγράψετε χειροκίνητα το stacktrace, θα μας αποσταλεί αυτόματα. Επίσης, συμβάλλετε στην αποσυμφόρηση με αυτόν τον τρόπο και επιπλέον λεπτομέρειες αναφέρονται αυτόματα. Επανεγκατάσταση diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml index b8b503c..46cd969 100644 --- a/app/src/main/res/values-es-rMX/strings.xml +++ b/app/src/main/res/values-es-rMX/strings.xml @@ -313,7 +313,7 @@ Permite enviar información adicional en reportes de fallos, algunos de los cuales pueden contener información de identificación personal, como la dirección IP y los identificadores de dispositivo. Esto puede incluir identificadores de dispositivo y direcciones IP. No se utilizarán datos para ningún otro propósito que no sea analizar fallas y mejorar el rendimiento. Se requiere reiniciar la aplicación para activar el modo de exhibición. - Permítanos rastrear el uso y las instalaciones de la aplicación. Totalmente compatible con GDPR y utiliza Matomo, alojado por Androidacy. + Permítanos rastrear el uso y las instalaciones de la aplicación. Totalmente compatible con GDPR y utiliza Countly, alojado por Androidacy. Reinstalar Tenga en cuenta que es posible que algunas configuraciones no surtan efecto hasta que reinicie la aplicación. ¡Use el código copiado para obtener la mitad de descuento en su primer mes! diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1221239..5811a14 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -292,7 +292,7 @@ Se requiere una URL Este repositorio ya existe. Se requiere reiniciar la aplicación para habilitar el modo de presentación. - Permítenos usar el seguimiento de uso e instalaciones. Totalmente compatible con el RGPD y utiliza Matomo, alojado por Androidacy. + Permítenos usar el seguimiento de uso e instalaciones. Totalmente compatible con el RGPD y utiliza Countly, alojado por Androidacy. Novedades y actualizaciones Depuración Atrás diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index ef8bc6a..73c14df 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -309,7 +309,7 @@ URL dibutuhkan Repo sudah ada. Mulai ulang aplikasi dibutuhkan untuk mengaktifkan mode showcase. - Mengizinkan kami untuk mencatat penggunaan aplikasi dan pemasangan. Sepenuhnya mengikuti GDPR dan menggunakan Matomo, di hosting oleh Androidacy. + Mengizinkan kami untuk mencatat penggunaan aplikasi dan pemasangan. Sepenuhnya mengikuti GDPR dan menggunakan Countly, di hosting oleh Androidacy. Men-debug Berita dan pembaruan Kembali diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 21137bc..103fa83 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -310,7 +310,7 @@ Errore nell\'accesso a WebView. La funzionalità potrebbe non essere disponibile. L\'URL è richiesto La repository esiste già. - Ci consente di tracciare l\'utilizzo della app e la quantità di installazioni. Conforme al GDPR, usa Matomo, un servizio offerto da Androidacy. + Ci consente di tracciare l\'utilizzo della app e la quantità di installazioni. Conforme al GDPR, usa Countly, un servizio offerto da Androidacy. Debug Notizie e aggiornamenti Indietro diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 37835aa..ac1a19a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -299,7 +299,7 @@ URL が必要です リポジトリがすでに存在します。 ショーケースモードを有効化するにはアプリを再起動してください。 - アプリの使用状況とインストール済みモジュールの追跡を許可します。GDPR に完全に準拠し、Androidacy によって運営されている Matomo を使用します。 + アプリの使用状況とインストール済みモジュールの追跡を許可します。GDPR に完全に準拠し、Androidacy によって運営されている Countly を使用します。 デバッグ ニュースとアップデート 戻る diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4b5195a..baf400b 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -295,7 +295,7 @@ URL is vereist Repo bestaat al. Een herstart van de app is vereist om de showcase-modus in te schakelen. - Sta ons toe app-gebruik en installaties bij te houden. Volledig GDPR-compatibel en maakt gebruik van Matomo, gehost door Androidacy. + Sta ons toe app-gebruik en installaties bij te houden. Volledig GDPR-compatibel en maakt gebruik van Countly, gehost door Androidacy. Nieuws en updates Debugging Ga terug diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8b32a9f..b1e7298 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -307,7 +307,7 @@ Tworzy efekt rozmycia za niektórymi oknami dialogowymi i elementami. Może nie działać dobrze na niektórych urządzeniach. Wystąpił błąd podczas odczytywania preferencji współdzielonych. Proszę zresetować aplikację. Aby włączyć tryb blokady, wymagany jest restart aplikacji. - Pozwól nam śledzić wykorzystanie aplikacji i instalacje. W pełni zgodne z GDPR i używa Matomo, hostowanego przez Androidacy. + Pozwól nam śledzić wykorzystanie aplikacji i instalacje. W pełni zgodne z GDPR i używa Countly, hostowanego przez Androidacy. Wiadomości i aktualizacje Wróć Debugowanie diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index b5c53d6..26190f3 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -311,7 +311,7 @@ Exigir wifi ou uma conexão ilimitada para buscar por atualizações Isso irá limpar o cache do aplicativo. Suas preferências serão salvas, mas o aplicativo poderá demorar para realizar algumas operações temporariamente. Uma reinicialização é necessária para ativar o modo showcase. - Nos permite monitorar o uso e instalação do app. Totalmente conforme o GDPR, utilizando Matomo, hospedado pelo Androidacy. + Nos permite monitorar o uso e instalação do app. Totalmente conforme o GDPR, utilizando Countly, hospedado pelo Androidacy. Depuração Novidades e atualizações Voltar diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bc8ca2e..f8207f7 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -300,7 +300,7 @@ Você está configurando o aplicativo para usar um servidor de testes do Androidacy. Isso pode resultar em instabilidade no aplicativo e falha ao carregar o repositório online. NÃO reporte falhas se você tiver esta opção habilitada. O aplicativo será reinicializado para recarregar os repositórios. Fox2Code é o desenvolvedor original do aplicativo. Sem ele, isso nunca seria possível. Uma reinicialização é necessária para ativar o modo showcase. - Nos permite monitorar o uso e instalação do app. Totalmente conforme o GDPR, utilizando Matomo, hospedado pelo Androidacy. + Nos permite monitorar o uso e instalação do app. Totalmente conforme o GDPR, utilizando Countly, hospedado pelo Androidacy. Depuração Novidades e atualizações Voltar diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 100de41..8d30fe8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -302,7 +302,7 @@ Создаёт эффект размытия позади некоторых диалоговых окон и элементов. Обратите внимание, что размытие может на некоторых устройствах работать плохо и не во всех местах. Произошла ошибка при чтении общих настроек. Пожалуйста, перезагрузите приложение. Для включения режима демонстрации требуется перезапуск приложения. - Разрешите нам отслеживать использование и установки приложения. Полностью соответсвует GDPR и использует Matomo, размещенный на Androidacy. + Разрешите нам отслеживать использование и установки приложения. Полностью соответсвует GDPR и использует Countly, размещенный на Androidacy. Отладка Новости и обновления Назад diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 9fc9dd4..7af387e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -302,7 +302,7 @@ Відладка Новини та оновлення Назад - Дозвольте нам відстежувати використання та встановлення додатку. Повністю відповідає GDPR і використовує Matomo, розміщений на Androidacy. + Дозвольте нам відстежувати використання та встановлення додатку. Повністю відповідає GDPR і використовує Countly, розміщений на Androidacy. Донат на Fox2Code Дотан на Androidacy Придбайте преміальну підписку на Androidacy, щоб підтримувати додаток і репозиторій. diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml index bcf58b6..a18e803 100644 --- a/app/src/main/res/values-v31/themes.xml +++ b/app/src/main/res/values-v31/themes.xml @@ -22,7 +22,7 @@