allow reinstall for local modules

fixes #50

also, lots of fixes yours truly forgot to commit separately

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/89/head
androidacy-user 2 years ago
parent 2c77c95b51
commit afb62c040a

@ -392,7 +392,7 @@ sentry {
autoInstallation {
enabled.set(true)
sentryVersion.set("6.18.1")
sentryVersion.set("6.25.0")
}
includeDependenciesReport.set(true)

@ -328,6 +328,10 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
// clear search view
searchView.setQuery("", false)
searchView.clearFocus()
// reset reboot and search card
searchCard.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f))
.start()
rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f))
}
R.id.installed_menu_item -> {
@ -346,6 +350,10 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
// set search view to cleared
searchView.setQuery("", false)
searchView.clearFocus()
// reset reboot and search card
searchCard.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f))
.start()
rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f))
}
}
true
@ -712,6 +720,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
progressIndicator!!.visibility = View.VISIBLE
progressIndicator!!.setProgressCompat(0, false)
swipeRefreshBlocker = System.currentTimeMillis() + 5000L
MainApplication.INSTANCE!!.repoModules.clear()
// this.swipeRefreshLayout.setRefreshing(true); ??
Thread({
cleanDnsCache() // Allow DNS reload from network

@ -29,6 +29,7 @@ import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStreamReader
import java.util.Date
import java.util.zip.ZipFile
@ -45,8 +46,27 @@ enum class NotificationType constructor(
DEBUG(
R.string.debug_build,
R.drawable.ic_baseline_bug_report_24,
com.google.android.material.R.attr.colorSecondary,
com.google.android.material.R.attr.colorOnSecondary
com.google.android.material.R.attr.colorPrimary, com.google.android.material.R.attr.colorOnPrimary,
// on click show a toast formatted with commit hash and build date, plus number of days before expiration (builds expire 30 days after build date)
View.OnClickListener { v: View ->
val buildTime = BuildConfig.BUILD_TIME
val buildTimeDays = (System.currentTimeMillis() - buildTime) / 86400000
// builddatepretty is created from build_time as YYYY-MM-DD
val sdf = android.icu.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US)
val netDate = Date(buildTime)
val buildDatePretty = sdf.format(netDate)
val toastText = v.context.getString(
R.string.debug_build_toast,
BuildConfig.COMMIT_HASH,
buildDatePretty,
30 - buildTimeDays
)
Toast.makeText(
v.context,
toastText,
Toast.LENGTH_LONG
).show()
}
) {
override fun shouldRemove(): Boolean {
return !BuildConfig.DEBUG
@ -56,7 +76,7 @@ enum class NotificationType constructor(
@JvmStatic
SHOWCASE_MODE(
R.string.showcase_mode, R.drawable.ic_baseline_lock_24,
androidx.appcompat.R.attr.colorPrimary, com.google.android.material.R.attr.colorOnPrimary
com.google.android.material.R.attr.colorPrimary, com.google.android.material.R.attr.colorOnPrimary
) {
override fun shouldRemove(): Boolean {
return !MainApplication.isShowcaseMode
@ -175,7 +195,7 @@ enum class NotificationType constructor(
compatActivity.cacheDir,
"installer" + File.separator + "module.zip"
)
IntentHelper.openFileTo(compatActivity, module, { d: File, u: Uri, s: Int ->
IntentHelper.openFileTo(compatActivity, module) { d: File, u: Uri, s: Int ->
if (s == IntentHelper.RESPONSE_FILE) {
try {
if (needPatch(d)) {
@ -217,7 +237,7 @@ enum class NotificationType constructor(
InstallerInitializer.peekMagiskPath() == null
)
}
})
}
},
false
) {

@ -4,8 +4,9 @@
package com.fox2code.mmm.manager
import com.fox2code.mmm.MainApplication
import com.fox2code.mmm.markdown.MarkdownUrlLinker.Companion.urlLinkify
import com.fox2code.mmm.utils.FastException
import com.fox2code.mmm.repo.RepoModule
import com.fox2code.mmm.utils.io.PropUtils
import com.fox2code.mmm.utils.io.net.Http
import org.json.JSONObject
@ -24,6 +25,18 @@ class LocalModuleInfo(id: String?) : ModuleInfo(id!!) {
var updateZipUrl: String? = null
private var updateChangeLogUrl: String? = null
var remoteModuleInfo: RepoModule? = null
get() {
if (field == null) {
try {
field = MainApplication.INSTANCE!!.repoModules[id]
} catch (e: IOException) {
// ignore
}
}
return field
}
@JvmField
var updateChangeLog = ""
@ -53,12 +66,20 @@ class LocalModuleInfo(id: String?) : ModuleInfo(id!!) {
desc = desc.substring(0, 1000)
}
updateChangeLog = desc
} catch (ioe: IOException) {
} catch (ioe: Exception) {
updateChangeLog = ""
updateChecksum = null
updateVersion = null
updateVersionCode = Long.MIN_VALUE
}
updateChecksum = jsonUpdate.optString("checksum")
val updateZipUrlForReals = updateZipUrl
if (updateZipUrlForReals!!.isEmpty()) throw FastException.INSTANCE
if (updateZipUrlForReals!!.isEmpty()) {
updateVersion = null
updateVersionCode = Long.MIN_VALUE
updateZipUrl = null
updateChangeLog = ""
}
updateVersion = PropUtils.shortenVersionName(
updateZipUrlForReals.trim { it <= ' ' }, updateVersionCode
)

@ -6,6 +6,7 @@ package com.fox2code.mmm.module
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.net.Uri
import android.text.Html
import android.text.Spanned
import android.widget.TextView
import android.widget.Toast
@ -382,6 +383,154 @@ enum class ActionButtonType {
) { _: DialogInterface?, _: Int -> }
.create().show()
}
},
REMOTE {
@Suppress("NAME_SHADOWING")
override fun doAction(button: Chip, moduleHolder: ModuleHolder) {
Timber.d("doAction: remote module for %s", moduleHolder.moduleInfo?.name ?: "null")
// that module is from remote repo
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
moduleHolder.repoModule?.moduleInfo?.name
}
// positive button executes install logic and says reinstall. negative button does nothing
TrackHelper.track().event("remote_module", name).with(INSTANCE!!.getTracker())
val madb = MaterialAlertDialogBuilder(button.context)
madb.setTitle(R.string.remote_module)
val moduleInfo: ModuleInfo = if (moduleHolder.mainModuleInfo != null) {
moduleHolder.mainModuleInfo
} else {
moduleHolder.repoModule?.moduleInfo ?: return
}
var updateZipUrl = moduleHolder.updateZipUrl
if (updateZipUrl.isNullOrEmpty()) {
// try repoModule.zipUrl
val repoModule = moduleHolder.repoModule
if (repoModule?.zipUrl.isNullOrEmpty()) {
Timber.e("No repo update zip url for %s", moduleInfo.name)
} else {
updateZipUrl = repoModule?.zipUrl
}
// next try localModuleInfo.updateZipUrl
if (updateZipUrl.isNullOrEmpty()) {
val localModuleInfo = moduleHolder.moduleInfo
if (localModuleInfo != null) {
updateZipUrl = localModuleInfo.updateZipUrl
} else {
Timber.e("No local update zip url for %s", moduleInfo.name)
}
}
if (updateZipUrl.isNullOrEmpty()) {
Timber.e("No update zip url at all for %s", moduleInfo.name)
// last ditch effort try moduleinfo
updateZipUrl = moduleHolder.moduleInfo?.updateZipUrl
}
// LAST LAST ditch effort try localModuleInfo.remoteModuleInfo.zipUrl
if (updateZipUrl.isNullOrEmpty()) {
val localModuleInfo = moduleHolder.moduleInfo
if (localModuleInfo != null) {
updateZipUrl = localModuleInfo.remoteModuleInfo?.zipUrl
} else {
Timber.e("No local update zip url for %s", moduleInfo.name)
}
}
}
if (!updateZipUrl.isNullOrEmpty()) {
madb.setMessage(Html.fromHtml(button.context.getString(R.string.remote_message, name), Html.FROM_HTML_MODE_COMPACT))
madb.setPositiveButton(
R.string.reinstall
) { _: DialogInterface?, _: Int ->
Timber.d("Set moduleinfo to %s", moduleInfo.name)
val name: String? = if (moduleHolder.moduleInfo != null) {
moduleHolder.moduleInfo!!.name
} else {
moduleHolder.repoModule?.moduleInfo?.name
}
Timber.d("doAction: remote module for %s", name)
TrackHelper.track().event("view_update_install", name)
.with(INSTANCE!!.getTracker())
// Androidacy manage the selection between download and install
if (isAndroidacyLink(updateZipUrl)) {
Timber.d("Androidacy link detected")
openUrlAndroidacy(
button.context,
updateZipUrl,
true,
moduleInfo.name,
moduleInfo.config
)
return@setPositiveButton
}
val hasRoot = peekMagiskPath() != null && !isShowcaseMode
val builder = MaterialAlertDialogBuilder(button.context)
builder.setTitle(moduleInfo.name).setCancelable(true)
.setIcon(R.drawable.ic_baseline_extension_24)
var desc: CharSequence? = moduleInfo.description
var markwon: Markwon? = null
val localModuleInfo = moduleHolder.moduleInfo
if (localModuleInfo != null && localModuleInfo.updateChangeLog.isNotEmpty()) {
markwon = INSTANCE!!.getMarkwon()
// Re-render each time in cse of config changes
desc = markwon!!.toMarkdown(localModuleInfo.updateChangeLog)
}
if (desc.isNullOrEmpty()) {
builder.setMessage(R.string.no_desc_found)
} else {
builder.setMessage(desc)
}
Timber.i("URL: %s", updateZipUrl)
builder.setNegativeButton(R.string.download_module) { _: DialogInterface?, _: Int ->
openCustomTab(
button.context,
updateZipUrl
)
}
if (hasRoot) {
builder.setPositiveButton(if (moduleHolder.hasUpdate()) R.string.update_module else R.string.install_module) { _: DialogInterface?, _: Int ->
val updateZipChecksum = moduleHolder.updateZipChecksum
openInstaller(
button.context,
updateZipUrl,
moduleInfo.name,
moduleInfo.config,
updateZipChecksum,
moduleInfo.mmtReborn
)
}
}
ExternalHelper.INSTANCE.injectButton(
builder,
{ Uri.parse(updateZipUrl) },
moduleHolder.updateZipRepo
)
val dim5dp = FoxDisplay.dpToPixel(5f)
builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp)
val alertDialog = builder.show()
for (i in -3..-1) {
val alertButton = alertDialog.getButton(i)
if (alertButton != null && alertButton.paddingStart > dim5dp) {
alertButton.setPadding(dim5dp, dim5dp, dim5dp, dim5dp)
}
}
if (markwon != null) {
val messageView =
alertDialog.window!!.findViewById<TextView>(android.R.id.message)
markwon.setParsedMarkdown(messageView, (desc as Spanned?)!!)
}
}
} else {
madb.setMessage(button.context.getString(R.string.remote_message_no_update, name))
}
madb.setNegativeButton(R.string.cancel, null)
madb.create().show()
}
override fun update(button: Chip, moduleHolder: ModuleHolder) {
val icon: Int = R.drawable.baseline_cloud_download_24
button.chipIcon = button.context.getDrawable(icon)
button.setText(R.string.online)
}
};
@DrawableRes

@ -23,7 +23,7 @@ import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView
import timber.log.Timber
import java.util.Objects
@Suppress("unused", "KotlinConstantConditions")
@Suppress("unused", "KotlinConstantConditions", "RedundantSetter")
class ModuleHolder : Comparable<ModuleHolder?> {
val moduleId: String
val notificationType: NotificationType?
@ -68,8 +68,9 @@ class ModuleHolder : Comparable<ModuleHolder?> {
get() = notificationType == null && separator == null && footerPx == -1
val mainModuleInfo: ModuleInfo
get() = if (repoModule != null && (moduleInfo == null || moduleInfo!!.versionCode < repoModule!!.moduleInfo.versionCode)) repoModule!!.moduleInfo else moduleInfo!!
val updateZipUrl: String?
var updateZipUrl: String? = null
get() = if (moduleInfo == null || repoModule != null && moduleInfo!!.updateVersionCode < repoModule!!.moduleInfo.versionCode) repoModule!!.zipUrl else moduleInfo!!.updateZipUrl
set
val updateZipRepo: String?
get() = if (moduleInfo == null || repoModule != null && moduleInfo!!.updateVersionCode < repoModule!!.moduleInfo.versionCode) repoModule!!.repoData.preferenceId else "update_json"
val updateZipChecksum: String?
@ -117,17 +118,23 @@ class ModuleHolder : Comparable<ModuleHolder?> {
Timber.i("Module %s is updateable", moduleId)
var ignoreUpdate = false
try {
if (getSharedPreferences("mmm")?.getStringSet("pref_background_update_check_excludes", HashSet())!!
if (getSharedPreferences("mmm")?.getStringSet(
"pref_background_update_check_excludes",
HashSet()
)!!
.contains(
moduleInfo!!.id
)
moduleInfo!!.id
)
) ignoreUpdate = true
} catch (ignored: Exception) {
}
// now, we just had to make it more fucking complicated, didn't we?
// we now have pref_background_update_check_excludes_version, which is a id:version stringset of versions the user may want to "skip"
// oh, and because i hate myself, i made ^ at the beginning match that version and newer, and $ at the end match that version and older
val stringSetT = getSharedPreferences("mmm")?.getStringSet("pref_background_update_check_excludes_version", HashSet())
val stringSetT = getSharedPreferences("mmm")?.getStringSet(
"pref_background_update_check_excludes_version",
HashSet()
)
var version = ""
Timber.d(stringSetT.toString())
// unfortunately, stringset.contains() doesn't work for partial matches
@ -231,9 +238,29 @@ class ModuleHolder : Comparable<ModuleHolder?> {
if (repoModule != null && repoModule!!.notesUrl != null) {
buttonTypeList.add(ActionButtonType.INFO)
}
if (repoModule != null || localModuleInfo?.updateZipUrl != null) {
// in below case, module cannot be in both repo and local if version codes are the same (if same, add online button, otherwise add update button)
if (repoModule != null || localModuleInfo?.updateZipUrl != null && localModuleInfo.updateVersionCode > localModuleInfo.versionCode) {
buttonTypeList.add(ActionButtonType.UPDATE_INSTALL)
}
if (localModuleInfo != null && localModuleInfo.updateVersionCode <= localModuleInfo.versionCode) {
buttonTypeList.add(ActionButtonType.REMOTE)
// set updatezipurl on moduleholder
if (localModuleInfo.updateZipUrl != null) {
Timber.d("localModuleInfo: %s", localModuleInfo.updateZipUrl)
updateZipUrl = localModuleInfo.updateZipUrl
}
if (repoModule != null) {
Timber.d("repoModule: %s", repoModule!!.zipUrl)
updateZipUrl = repoModule!!.zipUrl
}
// last ditch effort, try to get remoteModuleInfo from localModuleInfo
if (localModuleInfo.remoteModuleInfo != null) {
Timber.d("remoteModuleInfo: %s", localModuleInfo.remoteModuleInfo!!.zipUrl)
updateZipUrl = localModuleInfo.remoteModuleInfo!!.zipUrl
moduleInfo?.updateZipUrl = localModuleInfo.remoteModuleInfo!!.zipUrl
}
}
val config = mainModuleConfig
if (config != null) {
if (config.startsWith("https://www.androidacy.com/") && hasWebView()) {
@ -261,8 +288,6 @@ class ModuleHolder : Comparable<ModuleHolder?> {
}
if (moduleInfo.safe) {
buttonTypeList.add(ActionButtonType.SAFE)
} else {
Timber.d("Module %s is not safe", moduleId)
}
}
@ -303,7 +328,8 @@ class ModuleHolder : Comparable<ModuleHolder?> {
}
return 0
}
}, SEPARATOR(R.string.loading, false, false) {
},
SEPARATOR(R.string.loading, false, false) {
override fun compare(o1: ModuleHolder?, o2: ModuleHolder?): Int {
if (o1 != null && o2 != null) {
return o1.separator!!.compareTo(o2.separator!!)
@ -378,7 +404,8 @@ class ModuleHolder : Comparable<ModuleHolder?> {
}
return 0
}
}, INSTALLABLE(
},
INSTALLABLE(
R.string.online_repo,
true,
true

@ -5,6 +5,7 @@
package com.fox2code.mmm.repo
import androidx.annotation.StringRes
import com.fox2code.mmm.MainApplication
import com.fox2code.mmm.manager.ModuleInfo
class RepoModule {
@ -78,7 +79,11 @@ class RepoModule {
moduleInfo.support = support
moduleInfo.version = version
moduleInfo.versionCode = versionCode.toLong()
moduleInfo.flags = moduleInfo.flags or ModuleInfo.FLAG_METADATA_INVALID
moduleInfo.flags = moduleInfo.flags or ModuleInfo.FLAG_METADATA_INVALID and ModuleInfo.FLAG_MM_REMOTE_MODULE
safe = moduleInfo.safe
// if mainapplication.repomodules has this module, set the flag for remote module
if (MainApplication.INSTANCE!!.repoModules.containsKey(id)) {
moduleInfo.flags = moduleInfo.flags or ModuleInfo.FLAG_MM_REMOTE_MODULE
}
}
}

@ -36,6 +36,19 @@ class RepoUpdater(repoData2: RepoData) {
toApply = emptySet()
return 0
}
// if MainApplication.repoModules is not empty, return it
if (MainApplication.INSTANCE!!.repoModules.isNotEmpty()) {
Timber.d("Returning MainApplication.repoModules for %s", repoData.preferenceId)
// convert to list for toUpdate
val toUpdateList = ArrayList<RepoModule>()
for (module in MainApplication.INSTANCE!!.repoModules) {
toUpdateList.add(module.value)
}
toUpdate = toUpdateList
// toapply is a collection of RepoModule, so we need to convert the list to a set
toApply = HashSet(MainApplication.INSTANCE!!.repoModules.values)
return toUpdate!!.size
}
// if we shouldn't update, get the values from the ModuleListCache realm
if (!repoData.shouldUpdate() && repoData.preferenceId == "androidacy_repo") { // for now, only enable cache reading for androidacy repo, until we handle storing module prop file values in cache
Timber.d("Fetching index from cache for %s", repoData.preferenceId)
@ -127,13 +140,13 @@ class RepoUpdater(repoData2: RepoData) {
}
fun finish(): Boolean {
val success = AtomicBoolean(false)
Timber.d("Finishing update for %s", repoData.preferenceId)
// If repo is not enabled we don't need to do anything, just return true
if (!repoData.isEnabled) {
Timber.d("Repo %s is disabled, skipping", repoData.preferenceId)
return true
}
val success = AtomicBoolean(false)
Timber.d("Finishing update for %s", repoData.preferenceId)
if (indexRaw != null) {
val tmpIndexRaw = indexRaw!!
Timber.d("Updating database for %s", repoData.preferenceId)

@ -1395,6 +1395,7 @@ class SettingsActivity : FoxActivity(), LanguageActivity {
"magisk_alt_repo",
java.lang.Boolean.parseBoolean(newValue.toString())
)
INSTANCE!!.repoModules.clear()
true
}
// Disable toggling the pref_androidacy_repo_enabled on builds without an
@ -1433,6 +1434,7 @@ class SettingsActivity : FoxActivity(), LanguageActivity {
val enabled = androidacyRepoEnabled.isChecked
// save the new state
db.reposListDao().setEnabled("androidacy_repo", enabled)
INSTANCE!!.repoModules.clear()
true
}
if (androidacyRepoEnabledPref) {
@ -1961,6 +1963,7 @@ class SettingsActivity : FoxActivity(), LanguageActivity {
R.string.repo_enabled_changed,
BaseTransientBottomBar.LENGTH_LONG
).show()
INSTANCE!!.repoModules.clear()
true
}
}

@ -798,7 +798,6 @@ enum class Http {;
val hasInternet = try {
val resp = doHttpGet("https://production-api.androidacy.com/cdn-cgi/trace", false)
val respString = String(resp)
Timber.d("Ping response: $respString")
// 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")
} catch (e: HttpException) {

@ -201,6 +201,14 @@
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
<com.google.android.material.chip.Chip
android:id="@+id/button_action8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loading"
android:visibility="gone"
app:chipIcon="@drawable/ic_baseline_error_24" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -387,5 +387,9 @@
<string name="error_opening_notes">Error in opening module notes. Logs may have the reason.</string>
<string name="submit_feedback">Submit feedback</string>
<string name="ssl_error">Potential SSL interception detected.</string>
<string name="no_sentry_id">Unable to sumbit feedback - no event ID from Sentry. The last event may not have sent.</string>
<string name="no_sentry_id">Unable to submit feedback - no event ID from Sentry. The last event may not have sent.</string>
<string name="remote_module">Available from an online repo</string>
<string name="remote_message">%s is available from a online repo. <b>We strongly advise you to clean install it because most modules do not handle re-installation gracefully,</b> but you can reinstall instead at your own risk.</string>
<string name="debug_build_toast">AMM debug build %s built on %s with %d days remaining</string>
<string name="remote_message_no_update">%s is available from a online repo. Please uninstall it to see it in the online tab.</string>
</resources>

@ -19,7 +19,7 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath("io.sentry:sentry-android-gradle-plugin:3.11.0")
classpath("io.sentry:sentry-android-gradle-plugin:3.11.1")
classpath("org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin:2.7.2")
}
}

Loading…
Cancel
Save