Skip to content

Commit

Permalink
Merge branch 'gh-576'
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobi823 committed Apr 7, 2024
2 parents 8d99a2a + d15d0cb commit 5ee251f
Show file tree
Hide file tree
Showing 18 changed files with 531 additions and 366 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ fastlane/readme.md
/ffupdater/release/output-metadata.json
/ffupdater/release/ffupdater-release.zip
/ffupdater/http_cache/
/ffupdater/release/baselineProfiles/0/ffupdater-release.dm
/ffupdater/release/baselineProfiles/1/ffupdater-release.dm
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
* project. Don't use `apply false` in sub-projects. For more information,
* see Applying external plugins with same version to subprojects.
*/
id 'com.android.application' version '8.1.4' apply false
id 'com.android.application' version '8.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.8.22' apply false
id 'de.mannodermaus.android-junit5' version '1.9.3.0' apply false
id 'dev.rikka.tools.refine' version '4.3.0' apply false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ import androidx.annotation.Keep
open class DisplayableException : RuntimeException {
constructor(message: String) : super(message)
constructor(message: String, throwable: Throwable?) : super(message, throwable)

fun getNullSafeMessage(): String {
return message ?: javaClass.name
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package de.marmaro.krt.ffupdater.activity.download

import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.MainThread
import de.marmaro.krt.ffupdater.DisplayableException
import de.marmaro.krt.ffupdater.R
import de.marmaro.krt.ffupdater.app.App
import de.marmaro.krt.ffupdater.app.entity.InstalledAppStatus
import de.marmaro.krt.ffupdater.crash.CrashReportActivity
import de.marmaro.krt.ffupdater.crash.LogReader
import de.marmaro.krt.ffupdater.crash.ThrowableAndLogs
import de.marmaro.krt.ffupdater.installer.entity.Installer
import de.marmaro.krt.ffupdater.network.exceptions.ApiRateLimitExceededException
import de.marmaro.krt.ffupdater.network.exceptions.NetworkException
import de.marmaro.krt.ffupdater.network.exceptions.NetworkNotSuitableException
import de.marmaro.krt.ffupdater.network.file.DownloadStatus
import de.marmaro.krt.ffupdater.settings.ForegroundSettings
import de.marmaro.krt.ffupdater.settings.InstallerSettings
import kotlinx.coroutines.channels.Channel

class GuiHelper(val app: App, val activity: DownloadActivity) {
private val appImpl = app.findImpl()

fun show(vararg viewIds: Int) {
viewIds.forEach { activity.findViewById<View>(it).visibility = View.VISIBLE }
}

fun hide(vararg viewIds: Int) {
viewIds.forEach { activity.findViewById<View>(it).visibility = View.GONE }
}

fun setText(textId: Int, text: String) {
activity.findViewById<TextView>(textId).text = text
}

@MainThread
fun displayAppInstallationFailure(errorMessage: String, exception: Exception) {
show(R.id.install_activity__exception)

setText(
R.id.install_activity__exception__text,
activity.getString(R.string.application_installation_was_not_successful)
)
if (InstallerSettings.getInstallerMethod() == Installer.SESSION_INSTALLER) {
show(R.id.install_activity__different_installer_info)
}

val throwableAndLogs = ThrowableAndLogs(exception, LogReader.readLogs())
show(R.id.install_activity__exception__description)
activity.findViewById<TextView>(R.id.install_activity__exception__description).text = errorMessage
activity.findViewById<TextView>(R.id.install_activity__exception__show_button).setOnClickListener {
val description = activity.getString(R.string.crash_report__explain_text__download_activity_install_file)
val intent = CrashReportActivity.createIntent(activity, throwableAndLogs, description)
activity.startActivity(intent)
}

val cacheFolder = appImpl.getApkCacheFolder(activity).absolutePath
setText(R.id.install_activity__cache_folder_path, cacheFolder)
if (!ForegroundSettings.isDeleteUpdateIfInstallFailed) {
show(R.id.install_activity__delete_cache)
show(R.id.install_activity__open_cache_folder)
}
}

@MainThread
fun displayDownloadFailure(status: InstalledAppStatus, exception: DisplayableException) {
val description = if (exception is NetworkException) {
activity.getString(R.string.install_activity__download_file_failed__crash_text)
} else {
exception.getNullSafeMessage()
}
show(R.id.install_activity__download_file_failed)
setText(R.id.install_activity__download_file_failed__url, status.latestVersion.downloadUrl)
setText(R.id.install_activity__download_file_failed__text, description)
val throwableAndLogs = ThrowableAndLogs(exception, LogReader.readLogs())
val text = activity.findViewById<TextView>(R.id.install_activity__download_file_failed__show_exception)
text.setOnClickListener {
val intent = CrashReportActivity.createIntent(activity, throwableAndLogs, description)
activity.startActivity(intent)
}
}

suspend fun showDownloadProgress(progressChannel: Channel<DownloadStatus>) {
for (progress in progressChannel) {
if (progress.progressInPercent != null) {
activity.findViewById<ProgressBar>(R.id.downloadingFileProgressBar).progress =
progress.progressInPercent
}

val text = when {
progress.progressInPercent != null -> "(${progress.progressInPercent}%)"
else -> "(${progress.totalMB}MB)"
}
val statusText = activity.getString(R.string.download_activity__download_app_with_status)
setText(R.id.downloadingFileText, "$statusText $text")
}
}

@MainThread
fun displayFetchFailure(exception: DisplayableException) {
val message = when (exception) {
is ApiRateLimitExceededException -> activity.getString(R.string.download_activity__github_rate_limit_exceeded)
is NetworkNotSuitableException -> exception.getNullSafeMessage()
else -> activity.getString(R.string.download_activity__temporary_network_issue)
}

show(R.id.install_activity__exception)
setText(R.id.install_activity__exception__text, message)
val throwableAndLogs = ThrowableAndLogs(exception, LogReader.readLogs())
activity.findViewById<TextView>(R.id.install_activity__exception__show_button).setOnClickListener {
val description = activity.getString(R.string.crash_report__explain_text__download_activity_fetching_url)
val intent = CrashReportActivity.createIntent(activity, throwableAndLogs, description)
activity.startActivity(intent)
}
}

fun resetGui() {
hide(
R.id.downloadedFile,
R.id.downloadingFile,
R.id.externalStorageNotAccessible,
R.id.fetchUrl,
R.id.fetchedUrlSuccess,
R.id.fingerprintInstalledGood,
R.id.install_activity__delete_cache,
R.id.install_activity__different_installer_info,
R.id.install_activity__download_file_failed,
R.id.install_activity__exception,
R.id.install_activity__exception__description,
R.id.install_activity__open_cache_folder,
R.id.install_activity__retry_installation,
R.id.installerSuccess,
R.id.installingApplication,
R.id.tooLowMemory,
R.id.useCachedDownloadedApk,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ object Brave : AppBase() {
@Throws(NetworkException::class)
override suspend fun fetchLatestUpdate(context: Context, cacheBehaviour: CacheBehaviour): LatestVersion {
val fileName = findNameOfApkFile()
Log.e("test", "|${fileName}|")
val result = GithubConsumer.findLatestRelease(
repository = REPOSITORY,
isValidRelease = { !it.isPreRelease && it.name.startsWith("Release v") },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import de.marmaro.krt.ffupdater.network.exceptions.NetworkException
import de.marmaro.krt.ffupdater.network.file.CacheBehaviour
import de.marmaro.krt.ffupdater.network.mozillaci.MozillaCiJsonConsumer
import de.marmaro.krt.ffupdater.settings.DeviceSettingsHelper
import java.time.Duration
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter

Expand All @@ -38,42 +37,29 @@ object FirefoxNightly : AppBase() {
override val icon = R.drawable.ic_logo_firefox_nightly
override val minApiLevel = Build.VERSION_CODES.LOLLIPOP

@Suppress("SpellCheckingInspection")
private const val HOSTNAME = "https://firefox-ci-tc.services.mozilla.com"
private const val TASK_PARENT_NAMESPACE = "gecko.v2.mozilla-central.latest.mobile"
private const val TASK_NAMESPACE = "$TASK_PARENT_NAMESPACE.fenix-nightly"

override val signatureHash = "5004779088e7f988d5bc5cc5f8798febf4f8cd084a1b2a46efd4c8ee4aeaf211"
override val supportedAbis = ARM32_ARM64_X86_X64
override val projectPage =
"https://firefox-ci-tc.services.mozilla.com/tasks/index/mobile.v3.firefox-android.apks.fenix-nightly.latest"
override val projectPage = "$HOSTNAME/tasks/index/$TASK_PARENT_NAMESPACE/fenix-nightly"
override val displayCategory = listOf(FROM_MOZILLA)

private val versionDateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")

@MainThread
@Throws(NetworkException::class)
override suspend fun fetchLatestUpdate(context: Context, cacheBehaviour: CacheBehaviour): LatestVersion {
val abiString = findAbiString()
val preferences = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
var taskId = preferences.getString("cache__firefox_nightly__task_id", null)
var cacheAge = preferences.getLong("cache__firefox_nightly__age_ms", 0)
if (taskId == null || (System.currentTimeMillis() - cacheAge) >= Duration.ofHours(24).toMillis()) {
val indexPath = "mobile.v3.firefox-android.apks.fenix-nightly.latest.arm64-v8a"
taskId = MozillaCiJsonConsumer.findTaskId(indexPath, cacheBehaviour)
cacheAge = System.currentTimeMillis()
preferences.edit()
.putString("cache__firefox_nightly__task_id", taskId)
.putLong("cache__firefox_nightly__age_ms", cacheAge)
.apply()
}
val result = MozillaCiJsonConsumer.findChainOfTrustJson(taskId, abiString, cacheBehaviour)
val downloadUrl = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/" +
"mobile.v3.firefox-android.apks.fenix-nightly.latest.${abiString}/artifacts/" +
"public%2Fbuild%2Ftarget.${abiString}.apk"
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val releaseDate = ZonedDateTime.parse(result.releaseDate, DateTimeFormatter.ISO_ZONED_DATE_TIME)
val version = formatter.format(releaseDate)
val taskId = MozillaCiJsonConsumer.findTaskId(TASK_NAMESPACE, cacheBehaviour)
val metaInformation = MozillaCiJsonConsumer.findChainOfTrustJson(taskId, abiString, cacheBehaviour)
return LatestVersion(
downloadUrl = downloadUrl,
version = version,
publishDate = result.releaseDate,
downloadUrl = getDownloadUrl(abiString),
version = formatReleaseDate(metaInformation.releaseDate),
publishDate = metaInformation.releaseDate,
exactFileSizeBytesOfDownload = null,
fileHash = result.fileHash,
fileHash = metaInformation.fileHash,
)
}

Expand All @@ -88,6 +74,15 @@ object FirefoxNightly : AppBase() {
return abiString
}

private fun getDownloadUrl(abiString: String): String {
return "$HOSTNAME/api/index/v1/task/$TASK_NAMESPACE/artifacts/public%2Fbuild%2Ftarget.$abiString.apk"
}

private fun formatReleaseDate(releaseDate: String): String {
val date = ZonedDateTime.parse(releaseDate, DateTimeFormatter.ISO_ZONED_DATE_TIME)
return versionDateFormat.format(date)
}

override suspend fun isInstalledAppOutdated(
context: Context,
available: LatestVersion,
Expand Down Expand Up @@ -126,6 +121,6 @@ object FirefoxNightly : AppBase() {
return packageInfo.versionCode.toLong()
}

const val INSTALLED_VERSION_CODE = "firefox_nightly_installed_version_code"
const val INSTALLED_SHA256_HASH = "firefox_nightly_installed_sha256_hash"
private const val INSTALLED_VERSION_CODE = "firefox_nightly_installed_version_code"
private const val INSTALLED_SHA256_HASH = "firefox_nightly_installed_sha256_hash"
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ interface ApkDownloader : AppAttributes {
getApkCacheFolder(context.applicationContext).listFiles()!!
.filter { it.name.startsWith("${getSanitizedPackageName()}_") }
.filter { it.name.endsWith(".apk") }
.also { if (it.isNotEmpty()) Log.i(LOG_TAG, "ApkDownloader: Delete files from ${app.name}") }
.forEach { it.delete() }
.forEach {
Log.d(LOG_TAG, "ApkDownloader: Delete cached APK of ${app.name} (${it.absolutePath}).")
it.delete()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ import de.marmaro.krt.ffupdater.installer.entity.Installer
import de.marmaro.krt.ffupdater.installer.error.intent.GeneralInstallResultDecoder
import de.marmaro.krt.ffupdater.installer.error.intent.HuaweiInstallResultDecoder
import de.marmaro.krt.ffupdater.installer.exceptions.InstallationFailedException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.runBlocking
import java.io.File
import java.lang.Exception
import java.lang.RuntimeException


@Keep
Expand All @@ -29,15 +33,21 @@ class IntentInstaller(
) : AbstractAppInstaller(app) {
override val type = Installer.NATIVE_INSTALLER
private lateinit var appInstallationCallback: ActivityResultLauncher<Intent>
private val installResult = CompletableDeferred<Boolean>()
private var installFailure = Channel<Exception?>()

private val appResultCallback = lambda@{ activityResult: ActivityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
installResult.complete(true)
return@lambda
val result = if (activityResult.resultCode == Activity.RESULT_OK) {
null
} else {
createInstallationFailedException(activityResult, context)
}
try {
runBlocking {
installFailure.send(result)
}
} catch (e: Exception) {
throw RuntimeException("Can't use channel to send installation results", e)
}
val exception = createInstallationFailedException(activityResult, context)
this.installResult.completeExceptionally(exception)
}

private fun createInstallationFailedException(
Expand All @@ -64,10 +74,22 @@ class IntentInstaller(

@Throws(IllegalArgumentException::class)
override suspend fun installApkFile(context: Context, file: File) {
removePreviousResultsFromChannel()
require(this::appInstallationCallback.isInitialized) { "Call lifecycle.addObserver(...) first!" }
require(file.exists()) { "File does not exists." }
installApkFileHelper(context.applicationContext, file)
installResult.await()

val result = installFailure.receive()
if (result != null) {
throw result
}
}

@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun removePreviousResultsFromChannel() {
while (!installFailure.isEmpty) {
installFailure.receive()
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package de.marmaro.krt.ffupdater.network.exceptions

class NetworkNotSuitableException(message: String): NetworkException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.marmaro.krt.ffupdater.storage

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.FileUriExposedException
import android.os.StrictMode
import android.widget.Toast
import androidx.annotation.RequiresApi
import de.marmaro.krt.ffupdater.R
import de.marmaro.krt.ffupdater.device.DeviceSdkTester
import java.io.File

object SystemFileManager {
fun openFolder(folder: File, activity: Activity) {
require(folder.isDirectory)
val uri = Uri.parse("file://${folder.absolutePath}/")
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "resource/folder")
val chooser = Intent.createChooser(intent, activity.getString(R.string.download_activity__open_folder))
if (DeviceSdkTester.supportsAndroid7Nougat24()) {
startFileManagerForAndroid24(chooser, activity)
} else {
activity.startActivity(chooser)
}
}

@RequiresApi(Build.VERSION_CODES.N)
private fun startFileManagerForAndroid24(chooser: Intent, activity: Activity) {
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX)
try {
activity.startActivity(chooser)
} catch (e: FileUriExposedException) {
Toast.makeText(activity, R.string.download_activity__file_uri_exposed_toast, Toast.LENGTH_LONG).show()
}
}
}
Loading

0 comments on commit 5ee251f

Please sign in to comment.