[App] Implement basic app validation and related UI.

This commit is contained in:
Kuba Szczodrzyński 2021-03-27 16:28:09 +01:00
parent 726a37d5d6
commit 6824960731
8 changed files with 253 additions and 1 deletions

View File

@ -147,6 +147,7 @@
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/Base.Theme.AppCompat" />
<activity android:name=".ui.modules.base.BuildInvalidActivity" />
<!-- _____ _
| __ \ (_)

View File

@ -290,6 +290,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
setLanguage(it)
}
app.buildManager.validateBuild(this)
if (App.profileId == 0) {
onProfileListEmptyEvent(ProfileListEmptyEvent())
return

View File

@ -110,6 +110,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mArchiverEnabled = mArchiverEnabled ?: values.get("archiverEnabled", true); return mArchiverEnabled ?: true }
set(value) { set("archiverEnabled", value); mArchiverEnabled = value }
private var mValidation: String? = null
var validation: String?
get() { mValidation = mValidation ?: values["buildValidation"]; return mValidation }
set(value) { set("buildValidation", value); mValidation = value }
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
init {

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-3-27.
*/
package pl.szczodrzynski.edziennik.ui.modules.base
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.mikepenz.iconics.utils.colorInt
import pl.szczodrzynski.edziennik.databinding.ActivityBuildInvalidBinding
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.utils.Themes
class BuildInvalidActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(Themes.themeInt)
val b = ActivityBuildInvalidBinding.inflate(layoutInflater, null, false)
setContentView(b.root)
setSupportActionBar(b.toolbar)
b.icon.icon?.colorInt = intent.getIntExtra("color", Color.GREEN)
b.message.text = intent.getStringExtra("message")
b.closeButton.isVisible = !intent.getBooleanExtra("isCritical", true)
b.closeButton.onClick {
finish()
}
}
}

View File

@ -96,6 +96,8 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
setContentView(b.root)
errorSnackbar.setCoordinator(b.coordinator, b.snackbarAnchor)
app.buildManager.validateBuild(this)
launch {
app.config.loginFinished = app.db.profileDao().count > 0
if (!app.config.loginFinished) {

View File

@ -4,12 +4,17 @@
package pl.szczodrzynski.edziennik.utils.managers
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.ui.modules.base.BuildInvalidActivity
class BuildManager(val app: App) {
class BuildManager(val app: App) : CoroutineScope {
override val coroutineContext = Job() + Dispatchers.Main
val buildFlavor = BuildConfig.FLAVOR
val buildType = BuildConfig.BUILD_TYPE
@ -27,6 +32,8 @@ class BuildManager(val app: App) {
val gitTag = BuildConfig.GIT_INFO["tag"]
val gitIsDirty = BuildConfig.GIT_INFO["dirty"] !== "false"
val gitRemotes = BuildConfig.GIT_INFO["remotes"]?.split("; ")
var gitRemote: String? = ""
var gitAuthor: String? = ""
val isSigned = Signing.appCertificate.md5() == "d8bab5259fda7d72121fe5db526a3d4d"
@ -85,4 +92,132 @@ class BuildManager(val app: App) {
.setPositiveButton(R.string.ok, null)
.show()
}
enum class InvalidBuildReason(
val message: Int,
val color: Int,
val isCritical: Boolean = true
) {
NO_REMOTE_REPO(R.string.build_invalid_no_remote_repo, R.color.md_orange_500),
NO_COMMIT_HASH(R.string.build_invalid_no_commit_hash, R.color.md_orange_500),
REMOTE_NO_COMMIT(R.string.build_invalid_remote_no_commit, R.color.md_red_500),
OFFICIAL_UNSIGNED(R.string.build_invalid_official_unsigned, R.color.md_red_500),
UNSTAGED_CHANGES(R.string.build_invalid_unstaged_changes, R.color.md_amber_800),
DEBUG(R.string.build_invalid_debug, R.color.md_yellow_500, false),
VALID(R.string.build_valid_unofficial, R.color.md_yellow_500, false)
}
private fun getRemoteRepo(): String? {
if (gitRemotes == null)
return null
return gitRemotes.map {
it.substringAfter("(").substringBefore(")")
}.firstOrNull {
it != "szkolny-eu/szkolny-android"
}
}
private suspend fun validateRepo(
repo: String,
commitHash: String
) = withContext(Dispatchers.IO) {
true
}
fun validateBuild(activity: AppCompatActivity) {
launch {
gitRemote = getRemoteRepo()
if (gitRemote == null && !isDebug) {
invalidateBuild(activity, null, InvalidBuildReason.NO_REMOTE_REPO)
return@launch
}
if (gitHash == null) {
invalidateBuild(activity, null, InvalidBuildReason.NO_COMMIT_HASH)
return@launch
}
// official, signed package
if (isOfficial)
return@launch
// seems official, but unsigned
if (isPlayRelease || isApkRelease) {
invalidateBuild(activity, null, InvalidBuildReason.OFFICIAL_UNSIGNED)
return@launch
}
// debug build, invalidate once
if (isDebug) {
if (app.config.validation != "debug${Signing.appCertificate}".md5()) {
app.config.validation = "debug${Signing.appCertificate}".md5()
invalidateBuild(activity, null, InvalidBuildReason.DEBUG)
}
return@launch
}
// release version with unstaged changes
if (gitIsDirty) {
invalidateBuild(activity, null, InvalidBuildReason.UNSTAGED_CHANGES)
return@launch
}
val validation = Signing.appCertificate + gitHash + gitRemotes?.join(";")
// app already validated
if (app.config.validation == validation.md5())
return@launch
val dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.build_validate_progress)
.setCancelable(false)
.show()
val isRepoValid = if (app.config.validation == "invalid$gitRemote$gitHash".md5())
false
else
validateRepo(gitRemote!!, gitHash)
// release build with no public repository or not published changes
if (!isRepoValid) {
app.config.validation = "invalid$gitRemote$gitHash".md5()
invalidateBuild(activity, dialog, InvalidBuildReason.REMOTE_NO_COMMIT)
return@launch
}
// release, unofficial, published build
app.config.validation = validation.md5()
invalidateBuild(activity, dialog, InvalidBuildReason.VALID)
}
}
private fun invalidateBuild(
activity: AppCompatActivity,
progressDialog: AlertDialog?,
reason: InvalidBuildReason
) {
progressDialog?.dismiss()
val message = activity.getString(
reason.message,
gitRemote,
gitBranch,
gitAuthor
)
val color = reason.color.resolveColor(activity)
val intent = Intent(
activity,
BuildInvalidActivity::class.java,
"message" to message,
"color" to color,
"isCritical" to reason.isCritical
)
activity.startActivity(intent)
if (reason.isCritical)
activity.finish()
}
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-3-27.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:gravity="center_horizontal"
app:title="@string/app_name" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="32dp">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/icon"
android:layout_width="100dp"
android:layout_height="100dp"
app:iiv_icon="cmd-alert-outline" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/build_invalid_title"
android:textAppearance="@style/NavView.TextView.Title" />
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:textAlignment="center"
tools:text="@string/build_invalid_remote_no_commit" />
<Button
android:id="@+id/closeButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/close" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -1400,4 +1400,13 @@
<string name="build_rev_count">Rewizje od ostatniego tagu</string>
<string name="build_remote">Repozytorium zdalne</string>
<string name="build_details">Informacje o kompilacji</string>
<string name="build_validate_progress">Trwa weryfikowanie kompilacji…</string>
<string name="build_invalid_no_remote_repo">Nie znaleziono odniesienia do repozytorium zdalnego. Upewnij się, że korzystasz z fork\'a oficjalnego repozytorium oraz zweryfikuj konfigurację Gradle.</string>
<string name="build_invalid_no_commit_hash">Nie znaleziono wartości skrótu aktualnej rewizji. Sprawdź konfigurację Gradle.</string>
<string name="build_invalid_remote_no_commit">Posiadasz kompilację aplikacji zawierającą nieopublikowane zmiany. Kompilacja znajduje się w repozytorium %1$s (%2$s), które jest prywatne lub nie zawiera najnowszych zmian.\n\nDla bezpieczeństwa oraz ze względów zgodności z licencją, korzystanie z aplikacji zostało zablokowane.</string>
<string name="build_invalid_official_unsigned">Nie możesz modyfikować tego rodzaju kompilacji aplikacji Szkolny.eu.\n\nAby wprowadzić własne zmiany, skorzystaj z kodu źródłowego dostępnego na GitHubie oraz zapoznaj się z README i informacją o licencji.\n\nhttps://szkolny.eu/github/android</string>
<string name="build_invalid_unstaged_changes">Ta kompilacja zawiera zmiany niezatwierdzone do żadnej rewizji. Zapisz oraz opublikuj wszystkie zmiany przed wydaniem wersji release.\n\nDla bezpieczeństwa oraz ze względów zgodności z licencją, korzystanie z aplikacji zostało zablokowane.</string>
<string name="build_invalid_debug">Korzystasz z kompilacji typu \"debug\". Ta informacja zostanie wyświetlona tylko jeden raz dla aktualnego urządzenia.</string>
<string name="build_valid_unofficial">Korzystasz z nieoficjalnej kompilacji aplikacji Szkolny.eu. Zalecamy używanie wyłącznie oficjalnych wersji aplikacji.\n\nOstatnie zmiany w tej wersji zostały wprowadzone przez %3$s w repozytorium %2$s (%1$s).\n\nTo okno nie wyświetli się ponownie.</string>
<string name="build_invalid_title">Informacja dotycząca wersji aplikacji</string>
</resources>