Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/89/head
androidacy-user 3 years ago
parent d64efa6b58
commit f6da136bf0

@ -363,7 +363,7 @@ dependencies {
implementation("androidx.webkit:webkit:1.8.0") implementation("androidx.webkit:webkit:1.8.0")
implementation("com.google.android.material:material:1.9.0") implementation("com.google.android.material:material:1.9.0")
implementation("com.mikepenz:aboutlibraries:10.8.3") implementation("com.mikepenz:aboutlibraries:10.9.1")
// Utils // Utils
implementation("androidx.work:work-runtime:2.8.1") implementation("androidx.work:work-runtime:2.8.1")
@ -405,7 +405,7 @@ dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha06") implementation("androidx.security:security-crypto:1.1.0-alpha06")
// some utils // some utils
implementation("commons-io:commons-io:2.13.0") implementation("commons-io:commons-io:2.14.0")
implementation("org.apache.commons:commons-compress:1.24.0") implementation("org.apache.commons:commons-compress:1.24.0")
// analytics // analytics

@ -8,6 +8,7 @@ import android.annotation.SuppressLint
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
@ -32,20 +33,25 @@ class CrashHandler : AppCompatActivity() {
val crashDetails = findViewById<MaterialTextView>(R.id.crash_details) val crashDetails = findViewById<MaterialTextView>(R.id.crash_details)
crashDetails.text = "" crashDetails.text = ""
// get the exception from the intent // get the exception from the intent
val exception = intent.getSerializableExtra("exception") as Throwable? val exceptionFromIntent = intent.getSerializableExtra("exception") as Throwable?
// get the crashReportingEnabled from the intent var exception: String? = null
intent.getBooleanExtra("crashReportingEnabled", false) // parse the exception from the intent into exception if it is not null
if (exceptionFromIntent != null) {
val stringWriter = StringWriter()
exceptionFromIntent.printStackTrace(PrintWriter(stringWriter))
var stacktrace = stringWriter.toString()
stacktrace = stacktrace.replace(",", "\n ")
exception = stacktrace
}
val sharedPreferences = MainApplication.getPreferences("mmm")
if (exception == null && sharedPreferences != null) {
exception = sharedPreferences.getString("pref_crash_stacktrace", null)
}
// if the exception is null, set the crash details to "Unknown" // if the exception is null, set the crash details to "Unknown"
if (exception == null) { if (exception == null) {
crashDetails.setText(R.string.crash_details) crashDetails.setText(R.string.crash_details)
} else { } else {
// if the exception is not null, set the crash details to the exception and stacktrace crashDetails.text = getString(R.string.crash_full_stacktrace, exception)
// stacktrace is an StacktraceElement, so convert it to a string and replace the commas with newlines
val stringWriter = StringWriter()
exception.printStackTrace(PrintWriter(stringWriter))
var stacktrace = stringWriter.toString()
stacktrace = stacktrace.replace(",", "\n ")
crashDetails.text = getString(R.string.crash_full_stacktrace, stacktrace)
} }
// handle reset button // handle reset button
findViewById<View>(R.id.reset).setOnClickListener { _: View? -> findViewById<View>(R.id.reset).setOnClickListener { _: View? ->
@ -60,6 +66,16 @@ class CrashHandler : AppCompatActivity() {
builder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int -> } builder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int -> }
builder.show() builder.show()
} }
// restart button
findViewById<View>(R.id.restart).setOnClickListener { _: View? ->
// restart the app
val intent = packageManager.getLaunchIntentForPackage(packageName)
intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
finish()
}
// remove pref_crashed from shared preferences
sharedPreferences?.edit()?.remove("pref_crashed")?.apply()
} }
fun copyCrashDetails(view: View) { fun copyCrashDetails(view: View) {

@ -80,6 +80,7 @@ import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.nio.file.Files.* import java.nio.file.Files.*
import java.sql.Timestamp import java.sql.Timestamp
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper { class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
@ -203,6 +204,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
override fun onResume() { override fun onResume() {
super.onResume()
onMainActivityResume(this) onMainActivityResume(this)
// check that installed or online is selected depending on which recyclerview is visible // check that installed or online is selected depending on which recyclerview is visible
if (moduleList!!.visibility == View.VISIBLE) { if (moduleList!!.visibility == View.VISIBLE) {
@ -211,8 +213,16 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
bottomNavigationView.selectedItemId = R.id.online_menu_item bottomNavigationView.selectedItemId = R.id.online_menu_item
} }
// rescan modules // rescan modules
instance!!.scanAsync() if (!MainApplication.dirty) {
super.onResume() instance!!.scanAsync()
} else {
MainApplication.dirty = false
// same as onRefresh
// call onrefresh
swipeRefreshLayout!!.post { swipeRefreshLayout!!.isRefreshing = true }
this.onRefresh()
}
} }
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
@ -224,6 +234,14 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
onMainActivityCreate(this) onMainActivityCreate(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
INSTANCE = this INSTANCE = this
// check for pref_crashed and if so start crash handler
val sharedPreferences = MainApplication.getPreferences("mmm")
if (sharedPreferences?.getBoolean("pref_crashed", false) == true) {
val intent = Intent(this, CrashHandler::class.java)
startActivity(intent)
finish()
return
}
// hide this behind a buildconfig flag for now, but crash the app if it's not an official build and not debug // 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) { if (BuildConfig.ENABLE_PROTECTION && !MainApplication.o && !BuildConfig.DEBUG) {
@ -252,10 +270,11 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
// use countly to track enabled repos // use countly to track enabled repos
val repoMap = HashMap<String, String>() val repoMap = HashMap<String, String>()
repoMap["repos"] = enabledRepos.toString() repoMap["repos"] = enabledRepos.toString()
Countly.sharedInstance().events().recordEvent( if (MainApplication.analyticsAllowed()) Countly.sharedInstance().events()
"enabled_repos", .recordEvent(
repoMap as Map<String, Any>?, 1 "enabled_repos",
) repoMap as Map<String, Any>?, 1
)
} }
}.start() }.start()
val ts = Timestamp(System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000) val ts = Timestamp(System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000)
@ -284,6 +303,9 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
// set navigation bar color based on surfacecolors // set navigation bar color based on surfacecolors
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this) window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
progressIndicator = findViewById(R.id.progress_bar) progressIndicator = findViewById(R.id.progress_bar)
progressIndicator?.max = PRECISION
progressIndicator?.min = 0
progressIndicator?.setProgress(2, true)
swipeRefreshLayout = findViewById(R.id.swipe_refresh) swipeRefreshLayout = findViewById(R.id.swipe_refresh)
val swipeRefreshLayout = swipeRefreshLayout!! val swipeRefreshLayout = swipeRefreshLayout!!
swipeRefreshLayoutOrigStartOffset = swipeRefreshLayout.progressViewStartOffset swipeRefreshLayoutOrigStartOffset = swipeRefreshLayout.progressViewStartOffset
@ -346,7 +368,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
// filter the appropriate list based on visibility // filter the appropriate list based on visibility
if (initMode) return if (initMode) return
val query = s.toString() val query = s.toString()
Countly.sharedInstance().events() if (MainApplication.analyticsAllowed()) Countly.sharedInstance().events()
.recordEvent("search", HashMap<String, String>().apply { .recordEvent("search", HashMap<String, String>().apply {
put("query", query) put("query", query)
} as Map<String, Any>?, 1) } as Map<String, Any>?, 1)
@ -381,7 +403,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (actionId == EditorInfo.IME_ACTION_SEARCH) { if (actionId == EditorInfo.IME_ACTION_SEARCH) {
// filter the appropriate list based on visibility // filter the appropriate list based on visibility
val query = textInputEditText.text.toString() val query = textInputEditText.text.toString()
Countly.sharedInstance().events() if (MainApplication.analyticsAllowed()) Countly.sharedInstance().events()
.recordEvent("search", HashMap<String, String>().apply { .recordEvent("search", HashMap<String, String>().apply {
put("query", query) put("query", query)
} as Map<String, Any>?, 1) } as Map<String, Any>?, 1)
@ -614,6 +636,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
instance!!.scan() instance!!.scan()
instance!!.runAfterScan { moduleViewListBuilder.appendInstalledModules() } instance!!.runAfterScan { moduleViewListBuilder.appendInstalledModules() }
instance!!.runAfterScan { moduleViewListBuilderOnline.appendRemoteModules() } instance!!.runAfterScan { moduleViewListBuilderOnline.appendRemoteModules() }
progressIndicator?.setProgress(10, true)
commonNext() commonNext()
} }
@ -628,14 +651,18 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
moduleViewListBuilder.addNotification(NotificationType.DEBUG) moduleViewListBuilder.addNotification(NotificationType.DEBUG)
} }
NotificationType.NO_INTERNET.autoAdd(moduleViewListBuilderOnline) NotificationType.NO_INTERNET.autoAdd(moduleViewListBuilderOnline)
val progressIndicator = progressIndicator!! val progressIndicator = progressIndicator!!
runOnUiThread {
progressIndicator.isIndeterminate = false
progressIndicator.setProgress(30, true)
}
// hide progress bar is repo-manager says we have no internet // hide progress bar is repo-manager says we have no internet
if (!RepoManager.getINSTANCE()!!.hasConnectivity()) { if (!RepoManager.getINSTANCE()!!.hasConnectivity()) {
if (MainApplication.forceDebugLogging) Timber.i("No connection, hiding progress") if (MainApplication.forceDebugLogging) Timber.i("No connection, hiding progress")
runOnUiThread { runOnUiThread {
progressIndicator.visibility = View.GONE progressIndicator.visibility = View.GONE
progressIndicator.isIndeterminate = false
progressIndicator.max = PRECISION progressIndicator.max = PRECISION
} }
} }
@ -670,18 +697,22 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (MainApplication.forceDebugLogging) Timber.i("Check Update Compat") if (MainApplication.forceDebugLogging) Timber.i("Check Update Compat")
appUpdateManager.checkUpdateCompat() appUpdateManager.checkUpdateCompat()
if (MainApplication.forceDebugLogging) Timber.i("Check Update") if (MainApplication.forceDebugLogging) Timber.i("Check Update")
// update repos // update repos. progress is from 30 to 80, so subtract 20 from max
if (hasWebView()) { if (hasWebView()) {
val updateListener: SyncManager.UpdateListener = val updateListener: SyncManager.UpdateListener =
object : SyncManager.UpdateListener { object : SyncManager.UpdateListener {
override fun update(value: Int) { override fun update(value: Int) {
Timber.i("Update progress: %d", value)
// progress is out of a hundred (Int) and starts at 30 once we've reached this point
runOnUiThread(if (max == 0) Runnable { runOnUiThread(if (max == 0) Runnable {
progressIndicator.setProgressCompat( progressIndicator.setProgress(
value, true 80,
true
) )
} else Runnable { } else Runnable {
progressIndicator.setProgressCompat( progressIndicator.setProgress(
value, true 30 + value,
true
) )
}) })
} }
@ -698,7 +729,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} else { } else {
if (!hasWebView()) { if (!hasWebView()) {
runOnUiThread { runOnUiThread {
progressIndicator.setProgressCompat(PRECISION, true) progressIndicator.setProgress(PRECISION, true)
progressIndicator.visibility = View.GONE progressIndicator.visibility = View.GONE
} }
return return
@ -726,15 +757,26 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
current++ current++
val currentTmp = current val currentTmp = current
// progress starts at 80 and goes to 99. each module should add a equal amount of progress to the bar, rounded up to the nearest integer
runOnUiThread { runOnUiThread {
progressIndicator.setProgressCompat( progressIndicator.setProgress(
currentTmp / max, true 80 + (currentTmp / max.toFloat() * 20).roundToInt(),
true
) )
if (BuildConfig.DEBUG) {
Timber.i(
"Progress: %d",
80 + (currentTmp / max.toFloat() * 20).roundToInt()
)
}
} }
} }
} }
} }
} }
runOnUiThread {
progressIndicator.isIndeterminate = true
}
if (MainApplication.forceDebugLogging) Timber.i("Apply") if (MainApplication.forceDebugLogging) Timber.i("Apply")
RepoManager.getINSTANCE() RepoManager.getINSTANCE()
?.runAfterUpdate { moduleViewListBuilderOnline.appendRemoteModules() } ?.runAfterUpdate { moduleViewListBuilderOnline.appendRemoteModules() }
@ -752,12 +794,13 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (MainApplication.forceDebugLogging) Timber.i("Badge applied") if (MainApplication.forceDebugLogging) Timber.i("Badge applied")
} }
} }
maybeShowUpgrade()
if (MainApplication.forceDebugLogging) Timber.i("Finished app opening state!")
runOnUiThread { runOnUiThread {
progressIndicator.setProgressCompat(PRECISION, true) progressIndicator.isIndeterminate = false
progressIndicator.setProgress(PRECISION, true)
progressIndicator.visibility = View.GONE progressIndicator.visibility = View.GONE
} }
maybeShowUpgrade()
if (MainApplication.forceDebugLogging) Timber.i("Finished app opening state!")
} }
}, true) }, true)
// if system lang is not in MainApplication.supportedLocales, show a snackbar to ask user to help translate // if system lang is not in MainApplication.supportedLocales, show a snackbar to ask user to help translate
@ -772,7 +815,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
ExternalHelper.INSTANCE.refreshHelper(this) ExternalHelper.INSTANCE.refreshHelper(this)
initMode = false initMode = false
if (MainApplication.shouldShowFeedback()) { if (MainApplication.shouldShowFeedback() && !doSetupNowRunning) {
// wait a bit before showing feedback // wait a bit before showing feedback
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
showFeedback() showFeedback()
@ -784,7 +827,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
private fun showFeedback() { private fun showFeedback() {
Countly.sharedInstance().feedback() if (MainApplication.analyticsAllowed()) Countly.sharedInstance().feedback()
.getAvailableFeedbackWidgets { retrievedWidgets, error -> .getAvailableFeedbackWidgets { retrievedWidgets, error ->
if (MainApplication.forceDebugLogging) Timber.i( if (MainApplication.forceDebugLogging) Timber.i(
"Got feedback widgets: %s", "Got feedback widgets: %s",
@ -793,33 +836,34 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (error == null) { if (error == null) {
if (retrievedWidgets.size > 0) { if (retrievedWidgets.size > 0) {
val feedbackWidget = retrievedWidgets[0] val feedbackWidget = retrievedWidgets[0]
Countly.sharedInstance().feedback().presentFeedbackWidget( if (MainApplication.analyticsAllowed()) Countly.sharedInstance().feedback()
feedbackWidget, .presentFeedbackWidget(
this@MainActivity, feedbackWidget,
"Close", this@MainActivity,
object : ModuleFeedback.FeedbackCallback { "Close",
override fun onClosed() { object : ModuleFeedback.FeedbackCallback {
} override fun onClosed() {
}
// maybe show a toast when the widget is closed // maybe show a toast when the widget is closed
override fun onFinished(error: String?) { override fun onFinished(error: String?) {
// error handling here // error handling here
if (!error.isNullOrEmpty()) { if (!error.isNullOrEmpty()) {
Toast.makeText( Toast.makeText(
this@MainActivity, this@MainActivity,
"Error: $error", "Error: $error",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
Timber.e(error, "Feedback error") Timber.e(error, "Feedback error")
} else { } else {
Toast.makeText( Toast.makeText(
this@MainActivity, this@MainActivity,
"Feedback sent", "Feedback sent",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
}
} }
} })
})
// update last feedback time // update last feedback time
MainApplication.getPreferences("mmm")?.edit() MainApplication.getPreferences("mmm")?.edit()
?.putLong("last_feedback", System.currentTimeMillis())?.apply() ?.putLong("last_feedback", System.currentTimeMillis())?.apply()
@ -852,7 +896,8 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
if (MainApplication.forceDebugLogging) Timber.i("Refresh") if (MainApplication.forceDebugLogging) Timber.i("Refresh")
progressIndicator!!.visibility = View.VISIBLE progressIndicator!!.visibility = View.VISIBLE
progressIndicator!!.setProgressCompat(0, false) // progress starts at 30 and ends at 80
progressIndicator!!.setProgress(20, true)
swipeRefreshBlocker = System.currentTimeMillis() + 5000L swipeRefreshBlocker = System.currentTimeMillis() + 5000L
MainApplication.INSTANCE!!.repoModules.clear() MainApplication.INSTANCE!!.repoModules.clear()
@ -863,13 +908,21 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener { val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener {
override fun update(value: Int) { override fun update(value: Int) {
runOnUiThread(if (max == 0) Runnable { runOnUiThread(if (max == 0) Runnable {
progressIndicator!!.setProgressCompat( progressIndicator!!.setProgress(
value, true 80, true
) )
} else Runnable { } else Runnable {
progressIndicator!!.setProgressCompat( progressIndicator!!.setProgress(
value, true // going from 30 to 80 as evenly as possible
30 + value,
true
) )
if (BuildConfig.DEBUG) {
Timber.i(
"Progress: %d",
30 + value
)
}
}) })
} }
} }
@ -891,6 +944,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (MainApplication.forceDebugLogging) Timber.i("Check Json Update") if (MainApplication.forceDebugLogging) Timber.i("Check Json Update")
if (max != 0) { if (max != 0) {
var current = 0 var current = 0
val totalLocalModules = instance!!.modules.size
for (localModuleInfo in instance!!.modules.values) { for (localModuleInfo in instance!!.modules.values) {
if (localModuleInfo.updateJson != null && localModuleInfo.flags and ModuleInfo.FLAG_MM_REMOTE_MODULE == 0) { if (localModuleInfo.updateJson != null && localModuleInfo.flags and ModuleInfo.FLAG_MM_REMOTE_MODULE == 0) {
if (MainApplication.forceDebugLogging) Timber.i(localModuleInfo.id) if (MainApplication.forceDebugLogging) Timber.i(localModuleInfo.id)
@ -902,8 +956,10 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
current++ current++
val currentTmp = current val currentTmp = current
runOnUiThread { runOnUiThread {
progressIndicator!!.setProgressCompat( progressIndicator!!.setProgress(
currentTmp / max, true // from 80 to 99, divided by total modules
80 + (currentTmp / totalLocalModules.toFloat() * 20).roundToInt(),
true
) )
} }
} }
@ -911,10 +967,6 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
} }
if (MainApplication.forceDebugLogging) Timber.i("Apply") if (MainApplication.forceDebugLogging) Timber.i("Apply")
runOnUiThread {
progressIndicator!!.visibility = View.GONE
swipeRefreshLayout!!.isRefreshing = false
}
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder) NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder)
RepoManager.getINSTANCE()!!.updateEnabledStates() RepoManager.getINSTANCE()!!.updateEnabledStates()
RepoManager.getINSTANCE() RepoManager.getINSTANCE()
@ -923,6 +975,11 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
?.runAfterUpdate { moduleViewListBuilderOnline.appendRemoteModules() } ?.runAfterUpdate { moduleViewListBuilderOnline.appendRemoteModules() }
moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!)
moduleViewListBuilderOnline.applyTo(moduleListOnline!!, moduleViewAdapterOnline!!) moduleViewListBuilderOnline.applyTo(moduleListOnline!!, moduleViewAdapterOnline!!)
runOnUiThread {
progressIndicator!!.setProgress(PRECISION, true)
progressIndicator!!.visibility = View.GONE
swipeRefreshLayout!!.isRefreshing = false
}
}, "Repo update thread").start() }, "Repo update thread").start()
} }
@ -943,15 +1000,17 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
} }
} }
// if it's still null, but it's enabled, throw an error // if it's still null, but it's enabled, throw an error
if (AndroidacyRepoData.instance.isEnabled && AndroidacyRepoData.instance.memberLevel == null) { if (AndroidacyRepoData.instance.memberLevel == null) {
Timber.e("AndroidacyRepoData is enabled, but member level is null") Timber.e("AndroidacyRepoData is enabled, but member level is null")
} }
if (AndroidacyRepoData.instance.isEnabled && AndroidacyRepoData.instance.memberLevel == "Guest") { if (AndroidacyRepoData.instance.isEnabled && AndroidacyRepoData.instance.memberLevel == "Guest") {
runtimeUtils!!.showUpgradeSnackbar(this, this) runtimeUtils!!.showUpgradeSnackbar(this, this)
} else { } else {
if (!AndroidacyRepoData.instance.isEnabled) { if (AndroidacyRepoData.instance.memberLevel == null || !AndroidacyRepoData.instance.memberLevel.equals(
if (MainApplication.forceDebugLogging) Timber.i("AndroidacyRepoData is disabled, not showing upgrade snackbar 1") "Guest",
} else if (AndroidacyRepoData.instance.memberLevel != "Guest") { ignoreCase = true
)
) {
if (MainApplication.forceDebugLogging) Timber.i( if (MainApplication.forceDebugLogging) Timber.i(
"AndroidacyRepoData is not Guest, not showing upgrade snackbar 1. Level: %s", "AndroidacyRepoData is not Guest, not showing upgrade snackbar 1. Level: %s",
AndroidacyRepoData.instance.memberLevel AndroidacyRepoData.instance.memberLevel
@ -960,7 +1019,7 @@ class MainActivity : AppCompatActivity(), OnRefreshListener, OverScrollHelper {
if (MainApplication.forceDebugLogging) Timber.i("Unknown error, not showing upgrade snackbar 1") if (MainApplication.forceDebugLogging) Timber.i("Unknown error, not showing upgrade snackbar 1")
} }
} }
} else if (AndroidacyRepoData.instance.isEnabled && AndroidacyRepoData.instance.memberLevel == "Guest") { } else if (AndroidacyRepoData.instance.memberLevel.equals("Guest", ignoreCase = true)) {
runtimeUtils!!.showUpgradeSnackbar(this, this) runtimeUtils!!.showUpgradeSnackbar(this, this)
} else { } else {
if (!AndroidacyRepoData.instance.isEnabled) { if (!AndroidacyRepoData.instance.isEnabled) {

@ -4,12 +4,14 @@
package com.fox2code.mmm package com.fox2code.mmm
import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo import android.app.ActivityManager.RunningAppProcessInfo
import android.app.Application import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks import android.app.Application.ActivityLifecycleCallbacks
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
@ -17,12 +19,13 @@ import android.content.pm.PackageManager
import android.content.res.Resources import android.content.res.Resources
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Process
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.emoji2.text.DefaultEmojiCompatConfig import androidx.emoji2.text.DefaultEmojiCompatConfig
import androidx.emoji2.text.EmojiCompat import androidx.emoji2.text.EmojiCompat
@ -48,6 +51,8 @@ import ly.count.android.sdk.Countly
import ly.count.android.sdk.CountlyConfig import ly.count.android.sdk.CountlyConfig
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.security.SecureRandom import java.security.SecureRandom
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -202,23 +207,49 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle
Thread.setDefaultUncaughtExceptionHandler { _: Thread?, throwable: Throwable -> Thread.setDefaultUncaughtExceptionHandler { _: Thread?, throwable: Throwable ->
clearCachedSharedPrefs() clearCachedSharedPrefs()
// open crash handler and exit // send high importance notification with pending intent to open CrashHandler activity with stacktrace
val intent = Intent(this, CrashHandler::class.java) val intent = Intent(this, CrashHandler::class.java)
// pass the entire exception to the crash handler
intent.putExtra("exception", throwable) 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.putExtra("isCrashing", true)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
Timber.e("Starting crash handler") val pendingIntent = PendingIntent.getActivity(
startActivity(intent) this, 0, intent,
Timber.e("Exiting") PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
Process.killProcess(Process.myPid()) )
// set pref
val sharedPreferences = getPreferences("mmm")
val editor = sharedPreferences!!.edit()
editor.putBoolean("pref_crashed", true)
val stringWriter = StringWriter()
throwable.printStackTrace(PrintWriter(stringWriter))
val stacktrace = stringWriter.toString()
editor.putString("pref_crash_stacktrace", stacktrace)
editor.apply()
val crashreportingenabled = sharedPreferences.getBoolean(
"pref_crashreportingenabled",
true
)
// send notification
val notificationManagerCompat = NotificationManagerCompat.from(this)
val notifBody = if (crashreportingenabled) getString(R.string.crash_notification_body) else getString(
R.string.crash_notification_body_noreport
)
val notification = NotificationCompat.Builder(this, "crash")
.setSmallIcon(R.drawable.ic_baseline_error_24)
.setContentTitle(getString(R.string.crash_notification_title))
.setContentText(notifBody)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_ERROR)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
notificationManagerCompat.notify(0, notification)
}
} }
supportedLocales.addAll( supportedLocales.addAll(
listOf( listOf(
@ -604,7 +635,6 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle
} catch (e: Exception) { } catch (e: Exception) {
// try again five times, with a 250ms delay between each try. if we still can't get the shared preferences, throw an exception // try again five times, with a 250ms delay between each try. if we still can't get the shared preferences, throw an exception
var i = 0 var i = 0
var s = false
while (i < 5) { while (i < 5) {
try { try {
Thread.sleep(250) Thread.sleep(250)
@ -623,7 +653,6 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
) )
mSharedPrefs!![name] = sharedPreferences mSharedPrefs!![name] = sharedPreferences
s = true
return sharedPreferences return sharedPreferences
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Failed to get shared preferences") Timber.e(e, "Failed to get shared preferences")
@ -714,7 +743,7 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle
} }
val isCrashReportingEnabled: Boolean val isCrashReportingEnabled: Boolean
get() = getPreferences("mmm")!!.getBoolean( get() = analyticsAllowed() && getPreferences("mmm")!!.getBoolean(
"pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING "pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING
) )
val bootSharedPreferences: SharedPreferences? val bootSharedPreferences: SharedPreferences?
@ -736,6 +765,9 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle
fun shouldShowFeedback(): Boolean { fun shouldShowFeedback(): Boolean {
// should not have been shown in 14 days and only 1 in 5 chance // should not have been shown in 14 days and only 1 in 5 chance
if (!analyticsAllowed()) {
return false
}
val randChance = Random().nextInt(5) val randChance = Random().nextInt(5)
val lastShown = getPreferences("mmm")!!.getLong("last_feedback", 0) val lastShown = getPreferences("mmm")!!.getLong("last_feedback", 0)
if (forceDebugLogging) Timber.d( if (forceDebugLogging) Timber.d(
@ -745,6 +777,8 @@ class MainApplication : Application(), Configuration.Provider, ActivityLifecycle
) )
return System.currentTimeMillis() - lastShown > 1209600000 && randChance == 0 return System.currentTimeMillis() - lastShown > 1209600000 && randChance == 0
} }
var dirty = false
} }
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {

@ -35,6 +35,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.checkbox.MaterialCheckBox import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.textview.MaterialTextView
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import timber.log.Timber import timber.log.Timber
@ -54,6 +55,9 @@ class SetupActivity : AppCompatActivity(), LanguageActivity {
this.window.navigationBarColor = this.getColor(R.color.black_transparent) this.window.navigationBarColor = this.getColor(R.color.black_transparent)
createFiles() createFiles()
disableUpdateActivityForFdroidFlavor() disableUpdateActivityForFdroidFlavor()
if (BuildConfig.DEBUG) {
Timber.d("Starting SetupActivity")
}
// Set theme // Set theme
val prefs = MainApplication.getPreferences("mmm")!! val prefs = MainApplication.getPreferences("mmm")!!
when (prefs.getString("theme", "system")) { when (prefs.getString("theme", "system")) {
@ -155,12 +159,6 @@ class SetupActivity : AppCompatActivity(), LanguageActivity {
val crashReportingPii = view.findViewById<MaterialSwitch>(R.id.setup_crash_reporting_pii) val crashReportingPii = view.findViewById<MaterialSwitch>(R.id.setup_crash_reporting_pii)
setupCrashReporting.isChecked = setupCrashReporting.isChecked =
BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING
// pref_crash_reporting_pii
crashReportingPii.isChecked =
BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING_PII
// pref_analytics_enabled
analyticsEnabled.isChecked =
BuildConfig.DEFAULT_ENABLE_ANALYTICS
// if analytics is disabled, force disable crash reporting // if analytics is disabled, force disable crash reporting
if (!view.findViewById<MaterialSwitch>(R.id.setup_app_analytics).isChecked) { if (!view.findViewById<MaterialSwitch>(R.id.setup_app_analytics).isChecked) {
setupCrashReporting.isEnabled = false setupCrashReporting.isEnabled = false
@ -168,24 +166,31 @@ class SetupActivity : AppCompatActivity(), LanguageActivity {
setupCrashReporting.isChecked = false setupCrashReporting.isChecked = false
crashReportingPii.isChecked = false crashReportingPii.isChecked = false
} }
// switch summary for setup_app_analytics_summary
val setupAppAnalyticsSummary = view.findViewById<MaterialTextView>(R.id.setup_app_analytics_summary)
// listen for changes to the analytics switch // listen for changes to the analytics switch
analyticsEnabled.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> analyticsEnabled.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (BuildConfig.DEBUG) Timber.i(
"Analytics: %s",
isChecked)
// if analytics is disabled, force disable crash reporting // if analytics is disabled, force disable crash reporting
if (!isChecked) { if (!isChecked) {
setupCrashReporting.isChecked = false setupCrashReporting.isChecked = false
crashReportingPii.isChecked = false
setupCrashReporting.isEnabled = false setupCrashReporting.isEnabled = false
crashReportingPii.isEnabled = false
} else { } else {
setupCrashReporting.isEnabled = true setupCrashReporting.isEnabled = true
crashReportingPii.isEnabled = true setupCrashReporting.isChecked =
BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING
}
if (!isChecked) {
setupAppAnalyticsSummary.setText(R.string.analytics_disabled_desc)
} else {
setupAppAnalyticsSummary.setText(R.string.analytics_enabled_desc)
} }
} }
// assert that both switches match the build config on debug builds // pref_analytics_enabled
if (BuildConfig.DEBUG) { analyticsEnabled.isChecked =
assert((Objects.requireNonNull<Any>(view.findViewById(R.id.setup_background_update_check)) as MaterialSwitch).isChecked == BuildConfig.ENABLE_AUTO_UPDATER) BuildConfig.DEFAULT_ENABLE_ANALYTICS
assert(setupCrashReporting.isChecked == BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING)
}
// Repos are a little harder, as the enabled_repos build config is an arraylist // Repos are a little harder, as the enabled_repos build config is an arraylist
val andRepoView = val andRepoView =
Objects.requireNonNull<Any>(view.findViewById(R.id.setup_androidacy_repo)) as MaterialSwitch Objects.requireNonNull<Any>(view.findViewById(R.id.setup_androidacy_repo)) as MaterialSwitch
@ -362,7 +367,7 @@ class SetupActivity : AppCompatActivity(), LanguageActivity {
reposListDao.setEnabled(androidacyRepoRoomObj.id, androidacyRepoRoom) reposListDao.setEnabled(androidacyRepoRoomObj.id, androidacyRepoRoom)
reposListDao.setEnabled(magiskAltRepoRoomObj.id, magiskAltRepoRoom) reposListDao.setEnabled(magiskAltRepoRoomObj.id, magiskAltRepoRoom)
db.close() db.close()
editor.putString("last_shown_setup", "v5") editor.putString("last_shown_setup", "v6")
// Commit the changes // Commit the changes
editor.commit() editor.commit()
// Log the changes // Log the changes
@ -395,6 +400,8 @@ class SetupActivity : AppCompatActivity(), LanguageActivity {
// close the app // close the app
finish() finish()
} }
// log finish
if (MainApplication.forceDebugLogging) Timber.d("SetupActivity finished oncreate")
} }
override fun getTheme(): Theme { override fun getTheme(): Theme {

@ -478,8 +478,22 @@ class AndroidacyRepoData(cacheRoot: File?, testMode: Boolean) : RepoData(
OK_HTTP_URL_BUILDER.build() OK_HTTP_URL_BUILDER.build()
} }
private var realInstance: AndroidacyRepoData? = null
get() {
if (field === null) {
field = AndroidacyRepoData(INSTANCE!!.cacheDir, false)
}
return field
}
val instance: AndroidacyRepoData val instance: AndroidacyRepoData
get() = RepoManager.getINSTANCE()!!.androidacyRepoData!! get() {
return if (RepoManager.getINSTANCE()!!.androidacyRepoData !== null) {
RepoManager.getINSTANCE()!!.androidacyRepoData!!
} else {
realInstance!!
}
}
private fun filterURL(url: String?): String? { private fun filterURL(url: String?): String? {
return if (url.isNullOrEmpty() || isInvalidURL(url)) { return if (url.isNullOrEmpty() || isInvalidURL(url)) {

@ -378,7 +378,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
fun onMainActivityCreate(context: Context) { fun onMainActivityCreate(context: Context) {
// Refuse to run if first_launch pref is not false // Refuse to run if first_launch pref is not false
if (MainApplication.getPreferences("mmm")!! if (MainApplication.getPreferences("mmm")!!
.getString("last_shown_setup", null) != "v5" .getString("last_shown_setup", null) != "v6"
) return ) return
// create notification channel group // create notification channel group
val groupName: CharSequence = context.getString(R.string.notification_group_updates) val groupName: CharSequence = context.getString(R.string.notification_group_updates)

@ -589,6 +589,8 @@ class InstallerActivity : AppCompatActivity() {
if (suFile.exists() && !suFile.delete()) Timber.w("Failed to delete zip file") else toDelete = if (suFile.exists() && !suFile.delete()) Timber.w("Failed to delete zip file") else toDelete =
null null
} else toDelete = null } else toDelete = null
// set dirty in mainapp
MainApplication.dirty = true
runOnUiThread { runOnUiThread {
this.window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0) this.window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0)
// release wakelock // release wakelock

@ -75,7 +75,6 @@ class InstallerInitializer {
} }
fun tryGetMagiskPathAsync(callback: Callback, forceCheck: Boolean = false) { fun tryGetMagiskPathAsync(callback: Callback, forceCheck: Boolean = false) {
val mgskPth = mgskPth
val thread: Thread = object : Thread("Magisk GetPath Thread") { val thread: Thread = object : Thread("Magisk GetPath Thread") {
override fun run() { override fun run() {
if (mgskPth != null && !forceCheck) { if (mgskPth != null && !forceCheck) {
@ -83,7 +82,6 @@ class InstallerInitializer {
return return
} }
var error: Int var error: Int
@Suppress("NAME_SHADOWING") var mgskPth: String? = null
try { try {
mgskPth = tryGetMagiskPath(forceCheck) mgskPth = tryGetMagiskPath(forceCheck)
error = ERROR_NO_PATH error = ERROR_NO_PATH
@ -95,12 +93,14 @@ class InstallerInitializer {
Timber.e(e) Timber.e(e)
} }
if (forceCheck) { if (forceCheck) {
Companion.mgskPth = mgskPth
if (mgskPth == null) { if (mgskPth == null) {
mgskVerCode = 0 mgskVerCode = 0
} }
} }
if (mgskPth != null) { if (mgskPth != null) {
if (MainApplication.forceDebugLogging) {
Timber.i("Magisk path async: %s", mgskPth)
}
MainApplication.setHasGottenRootAccess(true) MainApplication.setHasGottenRootAccess(true)
callback.onPathReceived(mgskPth) callback.onPathReceived(mgskPth)
} else { } else {

@ -29,9 +29,9 @@ class ModuleManager private constructor() : SyncManager() {
private var updatableModuleCount = 0 private var updatableModuleCount = 0
override fun scanInternal(updateListener: UpdateListener) { override fun scanInternal(updateListener: UpdateListener) {
// if last_shown_setup is not "v5", then refuse to continue // if last_shown_setup is not "v6", then refuse to continue
if (MainApplication.getPreferences("mmm")!! if (MainApplication.getPreferences("mmm")!!
.getString("last_shown_setup", "") != "v5" .getString("last_shown_setup", "") != "v6"
) { ) {
return return
} }

@ -50,9 +50,10 @@ enum class ActionButtonType {
} }
// if analytics is enabled, track the event // if analytics is enabled, track the event
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_description", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_description", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
val notesUrl = moduleHolder.repoModule?.notesUrl val notesUrl = moduleHolder.repoModule?.notesUrl
if (isAndroidacyLink(notesUrl)) { if (isAndroidacyLink(notesUrl)) {
@ -124,9 +125,12 @@ enum class ActionButtonType {
} }
override fun doAction(button: Chip, moduleHolder: ModuleHolder) { override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
if (MainApplication.getPreferences("mmm")?.getBoolean("pref_require_security", false) == true) { if (MainApplication.getPreferences("mmm")
?.getBoolean("pref_require_security", false) == true
) {
// get safe status from either mainmoduleinfo or repo module // get safe status from either mainmoduleinfo or repo module
val safe = moduleHolder.mainModuleInfo.safe || moduleHolder.repoModule?.moduleInfo?.safe ?: false val safe =
moduleHolder.mainModuleInfo.safe || moduleHolder.repoModule?.moduleInfo?.safe ?: false
if (!safe) { if (!safe) {
// block local install for safety // block local install for safety
MaterialAlertDialogBuilder(button.context) MaterialAlertDialogBuilder(button.context)
@ -149,26 +153,30 @@ enum class ActionButtonType {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
// send event to countly // send event to countly
Countly.sharedInstance().events().recordEvent("view_update_install", HashMap<String, Any>().apply { if (MainApplication.analyticsAllowed()) Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_update_install", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
// if text is reinstall, we need to uninstall first - warn the user but don't proceed // if text is reinstall, we need to uninstall first - warn the user but don't proceed
if (moduleHolder.moduleInfo != null) { if (moduleHolder.moduleInfo != null && moduleHolder.repoModule == null && button.text == button.context.getString(R.string.reinstall)) {
// get the text val builder = MaterialAlertDialogBuilder(button.context)
val text = button.text builder.setTitle(R.string.reinstall)
// if the text is reinstall, warn the user .setMessage(R.string.reinstall_warning_v2)
if (text == button.context.getString(R.string.reinstall)) { .setCancelable(true)
val builder = MaterialAlertDialogBuilder(button.context) // ok button that does nothing
builder.setTitle(R.string.reinstall) .setPositiveButton(R.string.ok, null)
.setMessage(R.string.reinstall_warning) .show()
.setCancelable(true) return
// ok button that does nothing }
.setPositiveButton(R.string.ok, null) // prefer repomodule if possible
.show() var updateZipUrl = ""
return if (moduleHolder.repoModule != null && moduleHolder.repoModule!!.zipUrl != null) {
} updateZipUrl = moduleHolder.repoModule!!.zipUrl!!
}
// if repomodule is null, try localmoduleinfo
if (updateZipUrl.isEmpty() && moduleHolder.moduleInfo != null && moduleHolder.moduleInfo!!.updateZipUrl != null) {
updateZipUrl = moduleHolder.moduleInfo!!.updateZipUrl!!
} }
val updateZipUrl = moduleHolder.updateZipUrl ?: return
// Androidacy manage the selection between download and install // Androidacy manage the selection between download and install
if (isAndroidacyLink(updateZipUrl)) { if (isAndroidacyLink(updateZipUrl)) {
openUrlAndroidacy( openUrlAndroidacy(
@ -268,11 +276,16 @@ enum class ActionButtonType {
} }
// if analytics is enabled, track the event // if analytics is enabled, track the event
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_uninstall", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_uninstall", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
} })
if (MainApplication.forceDebugLogging) Timber.i(Integer.toHexString(moduleHolder.moduleInfo?.flags ?: 0)) }
if (MainApplication.forceDebugLogging) Timber.i(
Integer.toHexString(
moduleHolder.moduleInfo?.flags ?: 0
)
)
if (!instance!!.setUninstallState( if (!instance!!.setUninstallState(
moduleHolder.moduleInfo!!, !moduleHolder.hasFlag( moduleHolder.moduleInfo!!, !moduleHolder.hasFlag(
ModuleInfo.FLAG_MODULE_UNINSTALLING ModuleInfo.FLAG_MODULE_UNINSTALLING
@ -327,9 +340,10 @@ enum class ActionButtonType {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_config", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_config", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
if (isAndroidacyLink(config)) { if (isAndroidacyLink(config)) {
openUrlAndroidacy(button.context, config, true) openUrlAndroidacy(button.context, config, true)
@ -354,9 +368,10 @@ enum class ActionButtonType {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_support", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_support", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
openUrl(button.context, Objects.requireNonNull(moduleHolder.mainModuleInfo.support)) openUrl(button.context, Objects.requireNonNull(moduleHolder.mainModuleInfo.support))
} }
@ -376,10 +391,11 @@ enum class ActionButtonType {
} else { } else {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_donate", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_donate", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
openUrl(button.context, moduleHolder.mainModuleInfo.donate) openUrl(button.context, moduleHolder.mainModuleInfo.donate)
} }
@ -397,14 +413,15 @@ if (MainApplication.analyticsAllowed()) {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_warning", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_warning", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
MaterialAlertDialogBuilder(button.context).setTitle(R.string.warning) MaterialAlertDialogBuilder(button.context).setTitle(R.string.warning)
.setMessage(R.string.warning_message).setPositiveButton( .setMessage(R.string.warning_message).setPositiveButton(
R.string.understand R.string.understand
) { _: DialogInterface?, _: Int -> } ) { _: DialogInterface?, _: Int -> }
.create().show() .create().show()
} }
}, },
@ -423,21 +440,25 @@ if (MainApplication.analyticsAllowed()) {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_safe", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_safe", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
MaterialAlertDialogBuilder(button.context).setTitle(R.string.safe_module) MaterialAlertDialogBuilder(button.context).setTitle(R.string.safe_module)
.setMessage(R.string.safe_message).setPositiveButton( .setMessage(R.string.safe_message).setPositiveButton(
R.string.understand R.string.understand
) { _: DialogInterface?, _: Int -> } ) { _: DialogInterface?, _: Int -> }
.create().show() .create().show()
} }
}, },
REMOTE { REMOTE {
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
override fun doAction(button: Chip, moduleHolder: ModuleHolder) { override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
if (MainApplication.forceDebugLogging) Timber.d("doAction: remote module for %s", moduleHolder.moduleInfo?.name ?: "null") if (MainApplication.forceDebugLogging) Timber.d(
"doAction: remote module for %s",
moduleHolder.moduleInfo?.name ?: "null"
)
// that module is from remote repo // that module is from remote repo
val name: String? = if (moduleHolder.moduleInfo != null) { val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name moduleHolder.moduleInfo!!.name
@ -446,9 +467,10 @@ if (MainApplication.analyticsAllowed()) {
} }
// positive button executes install logic and says reinstall. negative button does nothing // positive button executes install logic and says reinstall. negative button does nothing
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_update_install", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_update_install", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
val madb = MaterialAlertDialogBuilder(button.context) val madb = MaterialAlertDialogBuilder(button.context)
madb.setTitle(R.string.remote_module) madb.setTitle(R.string.remote_module)
@ -491,21 +513,35 @@ if (MainApplication.analyticsAllowed()) {
} }
} }
if (!updateZipUrl.isNullOrEmpty()) { if (!updateZipUrl.isNullOrEmpty()) {
madb.setMessage(Html.fromHtml(button.context.getString(R.string.remote_message, name), Html.FROM_HTML_MODE_COMPACT)) madb.setMessage(
Html.fromHtml(
button.context.getString(
R.string.remote_message,
name
), Html.FROM_HTML_MODE_COMPACT
)
)
madb.setPositiveButton( madb.setPositiveButton(
R.string.reinstall R.string.reinstall
) { _: DialogInterface?, _: Int -> ) { _: DialogInterface?, _: Int ->
if (MainApplication.forceDebugLogging) Timber.d("Set moduleinfo to %s", moduleInfo.name) if (MainApplication.forceDebugLogging) Timber.d(
"Set moduleinfo to %s",
moduleInfo.name
)
val name: String? = if (moduleHolder.moduleInfo != null) { val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name moduleHolder.moduleInfo!!.name
} else { } else {
moduleHolder.repoModule?.moduleInfo?.name moduleHolder.repoModule?.moduleInfo?.name
} }
if (MainApplication.forceDebugLogging) Timber.d("doAction: remote module for %s", name) if (MainApplication.forceDebugLogging) Timber.d(
"doAction: remote module for %s",
name
)
if (MainApplication.analyticsAllowed()) { if (MainApplication.analyticsAllowed()) {
Countly.sharedInstance().events().recordEvent("view_update_install", HashMap<String, Any>().apply { Countly.sharedInstance().events()
put("module", name ?: "null") .recordEvent("view_update_install", HashMap<String, Any>().apply {
}) put("module", name ?: "null")
})
} }
// Androidacy manage the selection between download and install // Androidacy manage the selection between download and install
if (isAndroidacyLink(updateZipUrl)) { if (isAndroidacyLink(updateZipUrl)) {

@ -29,7 +29,7 @@ class CustomRepoManager internal constructor(
init { init {
repoCount = 0 repoCount = 0
// refuse to load if setup is not complete // refuse to load if setup is not complete
if (getPreferences("mmm")!!.getString("last_shown_setup", "") == "v5") { if (getPreferences("mmm")!!.getString("last_shown_setup", "") == "v6") {
val i = 0 val i = 0
val lastFilled = intArrayOf(0) val lastFilled = intArrayOf(0)
// now the same as above but for room database // now the same as above but for room database

@ -31,6 +31,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kotlin.math.roundToInt
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
class RepoManager private constructor(mainApplication: MainApplication) : SyncManager() { class RepoManager private constructor(mainApplication: MainApplication) : SyncManager() {
@ -55,7 +56,7 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa
repoData = LinkedHashMap() repoData = LinkedHashMap()
modules = HashMap() modules = HashMap()
// refuse to load if setup is not complete // refuse to load if setup is not complete
if (getPreferences("mmm")!!.getString("last_shown_setup", "") == "v5") { if (getPreferences("mmm")!!.getString("last_shown_setup", "") == "v6") {
// We do not have repo list config yet. // We do not have repo list config yet.
androidacyRepoData = addAndroidacyRepoData() androidacyRepoData = addAndroidacyRepoData()
val altRepo = addRepoData(MAGISK_ALT_REPO, "Magisk Modules Alt Repo") val altRepo = addRepoData(MAGISK_ALT_REPO, "Magisk Modules Alt Repo")
@ -81,8 +82,8 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa
} }
private fun populateDefaultCache(repoData: RepoData?) { private fun populateDefaultCache(repoData: RepoData?) {
// if last_shown_setup is not "v5", them=n refuse to continue // if last_shown_setup is not "v6", them=n refuse to continue
if (getPreferences("mmm")!!.getString("last_shown_setup", "") != "v5") { if (getPreferences("mmm")!!.getString("last_shown_setup", "") != "v6") {
return return
} }
// make sure repodata is not null // make sure repodata is not null
@ -146,15 +147,16 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa
val repoUpdaters = arrayOfNulls<RepoUpdater>(repoDatas.size) val repoUpdaters = arrayOfNulls<RepoUpdater>(repoDatas.size)
var moduleToUpdate = 0 var moduleToUpdate = 0
if (!this.hasConnectivity()) { if (!this.hasConnectivity()) {
updateListener.update(STEP3) updateListener.update(50)
return return
} }
for (i in repoDatas.indices) { for (i in repoDatas.indices) {
updateListener.update(STEP1 * (i / repoDatas.size)) // we have 50% to work with, so divvy it up. getting ready to update should get 40% of the 50% we have
updateListener.update(50 * ((i / repoDatas.size) / 3))
if (MainApplication.forceDebugLogging) Timber.d("Preparing to fetch: %s", repoDatas[i].name) if (MainApplication.forceDebugLogging) Timber.d("Preparing to fetch: %s", repoDatas[i].name)
moduleToUpdate += RepoUpdater(repoDatas[i]).also { repoUpdaters[i] = it }.fetchIndex() moduleToUpdate += RepoUpdater(repoDatas[i]).also { repoUpdaters[i] = it }.fetchIndex()
// divvy the 40 of step1 to each repo // divvy the 40 of step1 to each repo
updateListener.update(STEP1 * ((i + 1) / repoDatas.size)) updateListener.update(50 * ((i / repoDatas.size) / 2))
} }
if (MainApplication.forceDebugLogging) Timber.d("Updating meta-data") if (MainApplication.forceDebugLogging) Timber.d("Updating meta-data")
var updatedModules = 0 var updatedModules = 0
@ -208,9 +210,12 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa
Timber.e(e) Timber.e(e)
} }
updatedModules++ updatedModules++
val repoProgressIncrement = STEP2 / repoDatas.size.toDouble() val repoProgressIncrement = 50 / repoDatas.size.toDouble()
val moduleProgressIncrement = repoProgressIncrement / repoModules.size.toDouble() val moduleProgressIncrement = repoProgressIncrement / repoModules.size.toDouble()
updateListener.update((STEP1 + moduleProgressIncrement * updatedModules).toInt()) // we've already used half of our 50 steps, so we have 25 left. divide out the 25 by the number of modules we have to update and round to the nearest int
updateListener.update(
(50 + (repoProgressIncrement * i) + (moduleProgressIncrement * updatedModules).toInt()).roundToInt()
)
} }
for (repoModule in repoUpdaters[i]!!.toApply()!!) { for (repoModule in repoUpdaters[i]!!.toApply()!!) {
if (repoModule.moduleInfo.flags and ModuleInfo.FLAG_METADATA_INVALID == 0) { if (repoModule.moduleInfo.flags and ModuleInfo.FLAG_METADATA_INVALID == 0) {
@ -276,11 +281,11 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa
} }
repoLastErrorName = repoUpdaters[i]!!.repoData.name repoLastErrorName = repoUpdaters[i]!!.repoData.name
} }
updateListener.update(STEP1 + (STEP2 * (i / repoUpdaters.size))) updateListener.update(50 + (50 / repoDatas.size.toDouble() * i).roundToInt())
} }
} }
if (MainApplication.forceDebugLogging) Timber.i("Got " + modules.size + " modules!") if (MainApplication.forceDebugLogging) Timber.i("Got " + modules.size + " modules!")
updateListener.update(STEP1 + STEP2 + STEP3) updateListener.update(50)
} }
fun updateEnabledStates() { fun updateEnabledStates() {
@ -350,9 +355,9 @@ class RepoManager private constructor(mainApplication: MainApplication) : SyncMa
private const val MAGISK_REPO_MANAGER = private const val MAGISK_REPO_MANAGER =
"https://magisk-modules-repo.github.io/submission/modules.json" "https://magisk-modules-repo.github.io/submission/modules.json"
private val lock = Any() private val lock = Any()
private const val STEP1 = 20 private const val STEP1 = 30
private const val STEP2 = 60 private const val STEP2 = 80
private const val STEP3 = 20 private const val STEP3 = 99
@Volatile @Volatile
private var INSTANCE: RepoManager? = null private var INSTANCE: RepoManager? = null

@ -10,7 +10,7 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference import androidx.preference.SwitchPreferenceCompat
import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey import androidx.security.crypto.MasterKey
import com.fox2code.mmm.MainActivity import com.fox2code.mmm.MainActivity
@ -50,7 +50,7 @@ class PrivacyFragment : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.privacy_preferences, rootKey) setPreferencesFromResource(R.xml.privacy_preferences, rootKey)
// Crash reporting // Crash reporting
val crashReportingPreference = val crashReportingPreference =
findPreference<TwoStatePreference>("pref_crash_reporting") findPreference<SwitchPreferenceCompat>("pref_crash_reporting")
crashReportingPreference!!.isChecked = MainApplication.isCrashReportingEnabled crashReportingPreference!!.isChecked = MainApplication.isCrashReportingEnabled
val initialValue: Any = MainApplication.isCrashReportingEnabled val initialValue: Any = MainApplication.isCrashReportingEnabled
crashReportingPreference.onPreferenceChangeListener = crashReportingPreference.onPreferenceChangeListener =
@ -78,18 +78,51 @@ class PrivacyFragment : PreferenceFragmentCompat() {
if (MainApplication.forceDebugLogging) Timber.d("Restarting app to save crash reporting preference: %s", newValue) if (MainApplication.forceDebugLogging) Timber.d("Restarting app to save crash reporting preference: %s", newValue)
exitProcess(0) // Exit app process exitProcess(0) // Exit app process
} }
// Do not reverse the change if the user cancels the dialog // reverse the change if the user cancels the dialog
materialAlertDialogBuilder.setNegativeButton(R.string.no) { _: DialogInterface?, _: Int -> } materialAlertDialogBuilder.setNegativeButton(R.string.no) { _: DialogInterface?, _: Int ->
crashReportingPreference.isChecked = initialValue as Boolean
}
materialAlertDialogBuilder.show() materialAlertDialogBuilder.show()
true true
} }
// on pref_analytics_enabled change, update pref_crash_reporting (switch must be off and disabled if analytics is off) // on pref_analytics_enabled change, update pref_crash_reporting (switch must be off and disabled if analytics is off)
val analyticsPreference = findPreference<TwoStatePreference>("pref_analytics_enabled") val analyticsPreference = findPreference<SwitchPreferenceCompat>("pref_analytics_enabled")
analyticsPreference!!.onPreferenceChangeListener = analyticsPreference!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
if (initialValue === newValue) return@OnPreferenceChangeListener true @Suppress("NAME_SHADOWING") val newValue = newValue as Boolean
crashReportingPreference.isEnabled = newValue as Boolean crashReportingPreference.isEnabled = newValue
if (!newValue) crashReportingPreference.isChecked = false if (!newValue) {
crashReportingPreference.isChecked = false
crashReportingPreference.isEnabled = false
}
// restart dialog
val materialAlertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
materialAlertDialogBuilder.setTitle(R.string.crash_reporting_restart_title)
materialAlertDialogBuilder.setMessage(R.string.crash_reporting_restart_message)
materialAlertDialogBuilder.setPositiveButton(R.string.restart) { _: DialogInterface?, _: Int ->
val mStartActivity = Intent(requireContext(), MainActivity::class.java)
mStartActivity.flags =
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val mPendingIntentId = 123456
// If < 23, FLAG_IMMUTABLE is not available
val mPendingIntent: PendingIntent = PendingIntent.getActivity(
requireContext(),
mPendingIntentId,
mStartActivity,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val mgr =
requireContext().getSystemService(AppCompatActivity.ALARM_SERVICE) as AlarmManager
mgr[AlarmManager.RTC, System.currentTimeMillis() + 100] = mPendingIntent
if (MainApplication.forceDebugLogging) Timber.d("Restarting app to save analytics preference: %s", newValue)
exitProcess(0) // Exit app process
}
// reverse the change if the user cancels the dialog
materialAlertDialogBuilder.setNegativeButton(R.string.no) { _: DialogInterface?, _: Int ->
analyticsPreference.isChecked = initialValue as Boolean
crashReportingPreference.isEnabled = initialValue
}
materialAlertDialogBuilder.show()
true true
} }
// now, disable pref_crash_reporting if analytics is off // now, disable pref_crash_reporting if analytics is off

@ -21,6 +21,7 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey import androidx.security.crypto.MasterKey
import com.fox2code.mmm.BuildConfig import com.fox2code.mmm.BuildConfig
import com.fox2code.mmm.CrashHandler
import com.fox2code.mmm.ExpiredActivity import com.fox2code.mmm.ExpiredActivity
import com.fox2code.mmm.MainActivity import com.fox2code.mmm.MainActivity
import com.fox2code.mmm.MainApplication import com.fox2code.mmm.MainApplication
@ -75,6 +76,15 @@ class SettingsActivity : AppCompatActivity(), LanguageActivity,
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
devModeStep = 0 devModeStep = 0
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// check for pref_crashed and if so start crash handler
val sharedPreferences = MainApplication.getPreferences("mmm")
if (sharedPreferences?.getBoolean("pref_crashed", false) == true) {
val intent = Intent(this, CrashHandler::class.java)
startActivity(intent)
finish()
return
}
// get the active tab from the intent // get the active tab from the intent
activeTabFromIntent = intent.getStringExtra("activeTab") ?: "installed" activeTabFromIntent = intent.getStringExtra("activeTab") ?: "installed"
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { preferenceFragmentCompat: PreferenceFragmentCompat, preference: Preference -> PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { preferenceFragmentCompat: PreferenceFragmentCompat, preference: Preference ->

@ -162,7 +162,7 @@ class RuntimeUtils {
if (MainApplication.forceDebugLogging) Timber.i("Checking if we need to run setup") if (MainApplication.forceDebugLogging) 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 // Check if context is the first launch using prefs and if doSetupRestarting was passed in the intent
val prefs = MainApplication.getPreferences("mmm")!! val prefs = MainApplication.getPreferences("mmm")!!
var firstLaunch = prefs.getString("last_shown_setup", null) != "v5" var firstLaunch = prefs.getString("last_shown_setup", null) != "v6"
// First launch // 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 // 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)) { if (activity.intent.getBooleanExtra("doSetupRestarting", false)) {
@ -258,20 +258,17 @@ class RuntimeUtils {
val prefs = MainApplication.getPreferences("mmm")!! val prefs = MainApplication.getPreferences("mmm")!!
// if last shown < 7 days ago // if last shown < 7 days ago
if (prefs.getLong("ugsns4", 0) > System.currentTimeMillis() - 604800000) return if (prefs.getLong("ugsns4", 0) > System.currentTimeMillis() - 604800000) return
val snackbar: Snackbar = Snackbar.make( // rewrite that to use a material alert dialog
context, val builder = MaterialAlertDialogBuilder(context)
activity.findViewById(R.id.blur_frame), builder.setTitle(R.string.upgrade_now)
activity.getString(R.string.upgrade_snackbar), builder.setMessage(R.string.upgrade_dialog_message)
7000 builder.setPositiveButton(R.string.upgrade_now) { dialog, _ ->
)
snackbar.setAction(R.string.upgrade_now) {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.data = intent.data =
Uri.parse("https://androidacy.com/membership-join/#utm_source=AMMM&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) activity.startActivity(intent)
dialog.dismiss()
} }
snackbar.setAnchorView(R.id.bottom_navigation)
snackbar.show()
// do not show for another 7 days // do not show for another 7 days
prefs.edit().putLong("ugsns4", System.currentTimeMillis()).apply() prefs.edit().putLong("ugsns4", System.currentTimeMillis()).apply()
if (MainApplication.forceDebugLogging) Timber.i("showUpgradeSnackbar done") if (MainApplication.forceDebugLogging) Timber.i("showUpgradeSnackbar done")

@ -467,7 +467,7 @@ enum class Http {;
url url
).get().build() ).get().build()
).execute() ).execute()
} catch (e: IOException) { } catch (e: Exception) {
Timber.e(e, "Failed to get %s", url) Timber.e(e, "Failed to get %s", url)
// detect ssl errors, i.e., cert authority invalid by looking at the message // detect ssl errors, i.e., cert authority invalid by looking at the message
if (e.message != null && e.message!!.contains("_CERT_")) { if (e.message != null && e.message!!.contains("_CERT_")) {
@ -478,6 +478,7 @@ enum class Http {;
).show() ).show()
} }
} }
// check if retrying is allowed
throw HttpException(e.message, 0) throw HttpException(e.message, 0)
} }
if (BuildConfig.DEBUG_HTTP) { if (BuildConfig.DEBUG_HTTP) {
@ -805,7 +806,7 @@ enum class Http {;
val respString = String(resp) val respString = String(resp)
// resp should include that scheme is https and h is production-api.androidacy.com // resp should include that scheme is https and h is production-api.androidacy.com
respString.contains("scheme=https") && respString.contains("h=production-api.androidacy.com") respString.contains("scheme=https") && respString.contains("h=production-api.androidacy.com")
} catch (e: HttpException) { } catch (e: Exception) {
Timber.e(e, "Failed to check internet connection") Timber.e(e, "Failed to check internet connection")
false false
} }

@ -196,7 +196,7 @@
android:layout_margin="5dp" android:layout_margin="5dp"
android:checked="false" android:checked="false"
android:key="pref_crash_reporting_enabled" android:key="pref_crash_reporting_enabled"
android:text="@string/setup_crash_reporting" android:text="@string/setup_crash_reporting_generic"
android:textAppearance="@android:style/TextAppearance.Material.Subhead" android:textAppearance="@android:style/TextAppearance.Material.Subhead"
android:textSize="18sp" /> android:textSize="18sp" />
@ -215,6 +215,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:visibility="gone"
android:checked="false" android:checked="false"
android:key="pref_crash_reporting_pii" android:key="pref_crash_reporting_pii"
android:text="@string/setup_crash_reporting_pii" android:text="@string/setup_crash_reporting_pii"
@ -227,6 +228,7 @@
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:drawableStart="@drawable/ic_baseline_info_24" android:drawableStart="@drawable/ic_baseline_info_24"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:visibility="gone"
android:text="@string/setup_crash_reporting_pii_summary" android:text="@string/setup_crash_reporting_pii_summary"
android:textAppearance="@android:style/TextAppearance.Material.Small" /> android:textAppearance="@android:style/TextAppearance.Material.Small" />
@ -244,6 +246,7 @@
android:visibility="visible" /> android:visibility="visible" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/setup_app_analytics_summary"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="2dp" android:layout_margin="2dp"
@ -318,6 +321,7 @@
android:layout_marginHorizontal="2dp" android:layout_marginHorizontal="2dp"
android:layout_marginVertical="4dp" android:layout_marginVertical="4dp"
android:checked="false" android:checked="false"
android:autoLink="web"
android:text="@string/eula_agree_v2" android:text="@string/eula_agree_v2"
android:textAppearance="@style/TextAppearance.Material3.BodySmall" /> android:textAppearance="@style/TextAppearance.Material3.BodySmall" />

@ -327,6 +327,7 @@
<string name="error_encrypted_shared_preferences">An error occurred reading shared preferences. Please reset the app.</string> <string name="error_encrypted_shared_preferences">An error occurred reading shared preferences. Please reset the app.</string>
<string name="showcase_mode_dialogue_message">An app restart is required to enable showcase mode.</string> <string name="showcase_mode_dialogue_message">An app restart is required to enable showcase mode.</string>
<string name="eula_agree_v2">You agree to be bound by the LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.en.html) license and the EULA (https://www.androidacy.com/foxmmm-eula/), in addition to any third party terms, and that the authors of this app bear no responsibility of your usage of it, nor do we offer any warranties express or implied.</string> <string name="eula_agree_v2">You agree to be bound by the LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.en.html) license and the EULA (https://www.androidacy.com/foxmmm-eula/), in addition to any third party terms, and that the authors of this app bear no responsibility of your usage of it, nor do we offer any warranties express or implied.</string>
<string name="eula_agree_v3">By using this app, you agree to the EULA (https://www.androidacy.com/foxmmm-eula/) and the Androidacy Terms of Service (https://www.androidacy.com/terms-of-service/). You understand your data will be processed in accordance with the Androidacy Privacy Policy (https://www.androidacy.com/privacy-policy/). The source code of this app is available under the LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.en.html) license.</string>
<string name="analytics_desc">Allow us to track app usage and installs. Fully GDPR compliant and uses Countly, hosted by Androidacy.</string> <string name="analytics_desc">Allow us to track app usage and installs. Fully GDPR compliant and uses Countly, hosted by Androidacy.</string>
<string name="debug_cat">Debugging</string> <string name="debug_cat">Debugging</string>
<string name="announcements">News and updates</string> <string name="announcements">News and updates</string>
@ -412,4 +413,15 @@
<string name="download_failed">Failed to download!</string> <string name="download_failed">Failed to download!</string>
<string name="download_finished">Finished downloading and saved to downloads folder</string> <string name="download_finished">Finished downloading and saved to downloads folder</string>
<string name="file_picker_not_zip">The file you picked is not a valid zip file.</string> <string name="file_picker_not_zip">The file you picked is not a valid zip file.</string>
<string name="crash_notification_title">AMM crashed!</string>
<string name="crash_notification_text">AMM has encountered an error and has crashed. %s</string>
<string name="crash_notification_body">AMM has crashed. The developers have been notified. Tap here to see details or restart the app.</string>
<string name="crash_notification_body_noreport">AMM has crashed. If you keep seeing this, please report a bug. If you enabled crash reporting in settings, you can have this done for you.</string>
<string name="crash_reporting_disabled_desc">You have disabled crash reporting. This may make it harder for us to find bugs and fix crashes.</string>
<string name="crash_reporting_enabled_desc">Crash reporting is on, and the app will automatically send a report when it crashes or freezes.</string>
<string name="analytics_disabled_desc">You have analytics off. This may make it harder to improve and develop the app. If you enable this setting, no personal info will be sent!</string>
<string name="analytics_enabled_desc">You\'ve opted into analytics. We will use this data to develop and improve the app. No personal info is sent.</string>
<string name="setup_crash_reporting_generic">Report crashes</string>
<string name="upgrade_dialog_message">Purchasing a premium subscription helps support this app and our other services, and unlocks cool features like ad removal, fast unlimited downloads, and so much more! Plans start at $3.49 USD a month (subject to change). Cancel anytime.\n\n<i>Note: The core features of this app remain free but may be limited.</i></string>
<string name="reinstall_warning_v2">This module is available from an online source and a local one. For security, you\'ll need to head over to the online tab to update. If there\'s no update available, you may need to remove this module before attemtping to reinstall.</string>
</resources> </resources>

@ -11,12 +11,14 @@
app:icon="@drawable/ic_baseline_bug_report_24" app:icon="@drawable/ic_baseline_bug_report_24"
app:key="pref_crash_reporting" app:key="pref_crash_reporting"
app:singleLineTitle="false" app:singleLineTitle="false"
app:summary="@string/crash_reporting_desc" app:summaryOff="@string/crash_reporting_disabled_desc"
app:summaryOn="@string/crash_reporting_enabled_desc"
app:title="@string/crash_reporting" /> app:title="@string/crash_reporting" />
<!-- allow pii in crash reports --> <!-- allow pii in crash reports -->
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preference_material_switch" android:widgetLayout="@layout/preference_material_switch"
app:defaultValue="false" app:defaultValue="false"
app:isPreferenceVisible="false"
app:dependency="pref_crash_reporting" app:dependency="pref_crash_reporting"
app:icon="@drawable/ic_baseline_bug_report_24" app:icon="@drawable/ic_baseline_bug_report_24"
app:key="pref_crash_reporting_pii" app:key="pref_crash_reporting_pii"
@ -30,7 +32,8 @@
app:icon="@drawable/ic_baseline_info_24" app:icon="@drawable/ic_baseline_info_24"
app:key="pref_analytics_enabled" app:key="pref_analytics_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:summary="@string/analytics_desc" app:summaryOff="@string/analytics_disabled_desc"
app:summaryOn="@string/analytics_enabled_desc"
app:title="@string/setup_app_analytics" /> app:title="@string/setup_app_analytics" />
</PreferenceCategory> </PreferenceCategory>

@ -13,7 +13,7 @@ buildscript {
gradlePluginPortal() gradlePluginPortal()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:8.1.1") classpath("com.android.tools.build:gradle:8.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10")
classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:10.8.3") classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:10.8.3")
} }

Loading…
Cancel
Save