DownloadWorker: Verify downloaded files using SHA256 or SHA1
This lets us avoid downloading same files again Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -3,5 +3,7 @@ package com.aurora.store.data.model
|
|||||||
data class Request(
|
data class Request(
|
||||||
val url: String,
|
val url: String,
|
||||||
val filePath: String,
|
val filePath: String,
|
||||||
val size: Long
|
val size: Long,
|
||||||
|
val sha1: String,
|
||||||
|
val sha256: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.security.DigestInputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
import com.aurora.gplayapi.data.models.File as GPlayFile
|
import com.aurora.gplayapi.data.models.File as GPlayFile
|
||||||
@@ -191,27 +193,40 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
PathUtil.getObbDownloadFile(download.packageName, it)
|
PathUtil.getObbDownloadFile(download.packageName, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
downloadList.add(Request(it.url, filePath, it.size))
|
downloadList.add(Request(it.url, filePath, it.size, it.sha1, it.sha256))
|
||||||
}
|
}
|
||||||
return downloadList
|
return downloadList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
private suspend fun downloadFile(request: Request): Result {
|
private suspend fun downloadFile(request: Request): Result {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val requestFile = File(request.filePath)
|
val requestFile = File(request.filePath)
|
||||||
|
|
||||||
|
// If file exists, no need to download again
|
||||||
|
if (!shouldDownload(request)) {
|
||||||
|
Log.i(TAG, "$requestFile already exists")
|
||||||
|
return@withContext Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
val algorithm = if (request.sha256.isBlank()) "SHA-1" else "SHA-256"
|
||||||
|
val messageDigest = MessageDigest.getInstance(algorithm)
|
||||||
|
|
||||||
requestFile.createNewFile()
|
requestFile.createNewFile()
|
||||||
URL(request.url).openStream().use { input ->
|
DigestInputStream(URL(request.url).openStream(), messageDigest).use { input ->
|
||||||
requestFile.outputStream().use {
|
requestFile.outputStream().use {
|
||||||
input.copyTo(it, request.size).collectLatest { p -> onProgress(p) }
|
input.copyTo(it, request.size).collectLatest { p -> onProgress(p) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ensure downloaded file exists
|
|
||||||
if (!File(request.filePath).exists()) {
|
val sha = messageDigest.digest().toHexString()
|
||||||
Log.e(TAG, "Failed to find downloaded file at ${request.filePath}")
|
if (!File(request.filePath).exists() || !(sha == request.sha1 || sha == request.sha256)) {
|
||||||
|
Log.e(TAG, "$requestFile is either missing or corrupt")
|
||||||
notifyStatus(DownloadStatus.FAILED)
|
notifyStatus(DownloadStatus.FAILED)
|
||||||
return@withContext Result.failure()
|
return@withContext Result.failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
return@withContext Result.success()
|
return@withContext Result.success()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e(TAG, "Failed to download ${request.filePath}!", exception)
|
Log.e(TAG, "Failed to download ${request.filePath}!", exception)
|
||||||
@@ -277,4 +292,26 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
val notificationID = if (dID != -1) dID else download.packageName.hashCode()
|
val notificationID = if (dID != -1) dID else download.packageName.hashCode()
|
||||||
notificationManager.notify(notificationID, notification)
|
notificationManager.notify(notificationID, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
private suspend fun shouldDownload(request: Request): Boolean {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val file = File(request.filePath)
|
||||||
|
if (file.exists()) {
|
||||||
|
val algorithm = if (request.sha256.isBlank()) "SHA-1" else "SHA-256"
|
||||||
|
val messageDigest = MessageDigest.getInstance(algorithm)
|
||||||
|
DigestInputStream(file.inputStream(), messageDigest).use { input ->
|
||||||
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
|
var read = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)
|
||||||
|
while (read > -1) {
|
||||||
|
read = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val sha = messageDigest.digest().toHexString()
|
||||||
|
return@withContext !(sha == request.sha1 || sha == request.sha256)
|
||||||
|
} else {
|
||||||
|
return@withContext true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,8 +129,4 @@ class ActionButton : RelativeLayout {
|
|||||||
fun addOnClickListener(onClickListener: OnClickListener?) {
|
fun addOnClickListener(onClickListener: OnClickListener?) {
|
||||||
B.btn.setOnClickListener(onClickListener)
|
B.btn.setOnClickListener(onClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOnLongClickListener(onLongClickListener: OnLongClickListener?) {
|
|
||||||
B.btn.setOnLongClickListener(onLongClickListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.widget.RelativeLayout
|
|||||||
import com.aurora.extensions.getString
|
import com.aurora.extensions.getString
|
||||||
import com.aurora.extensions.runOnUiThread
|
import com.aurora.extensions.runOnUiThread
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.State
|
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
import com.aurora.store.databinding.ViewUpdateButtonBinding
|
import com.aurora.store.databinding.ViewUpdateButtonBinding
|
||||||
|
|
||||||
@@ -77,7 +76,6 @@ class UpdateButton : RelativeLayout {
|
|||||||
val displayChild = when (downloadStatus) {
|
val displayChild = when (downloadStatus) {
|
||||||
DownloadStatus.QUEUED,
|
DownloadStatus.QUEUED,
|
||||||
DownloadStatus.DOWNLOADING -> 2
|
DownloadStatus.DOWNLOADING -> 2
|
||||||
DownloadStatus.COMPLETED -> 3
|
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,8 +93,4 @@ class UpdateButton : RelativeLayout {
|
|||||||
fun addNegativeOnClickListener(onClickListener: OnClickListener?) {
|
fun addNegativeOnClickListener(onClickListener: OnClickListener?) {
|
||||||
B.btnNegative.setOnClickListener(onClickListener)
|
B.btnNegative.setOnClickListener(onClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addInstallOnClickListener(onClickListener: OnClickListener?) {
|
|
||||||
B.btnInstall.setOnClickListener(onClickListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ import com.aurora.store.data.model.DownloadStatus
|
|||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
import com.aurora.store.databinding.ViewAppUpdateBinding
|
import com.aurora.store.databinding.ViewAppUpdateBinding
|
||||||
import com.aurora.store.util.CommonUtil
|
import com.aurora.store.util.CommonUtil
|
||||||
import com.aurora.store.util.PathUtil
|
|
||||||
import com.aurora.store.view.epoxy.views.BaseView
|
import com.aurora.store.view.epoxy.views.BaseView
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@ModelView(
|
@ModelView(
|
||||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||||
@@ -135,20 +133,6 @@ class AppUpdateView : RelativeLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadStatus.COMPLETED -> {
|
|
||||||
B.progressDownload.invisible()
|
|
||||||
|
|
||||||
// It is possible that while the app was downloaded in past, files have been removed
|
|
||||||
// Check once again to reflect appropriate status
|
|
||||||
val files = File(
|
|
||||||
PathUtil.getAppDownloadDir(
|
|
||||||
context,
|
|
||||||
download.packageName,
|
|
||||||
download.versionCode
|
|
||||||
).path
|
|
||||||
).listFiles()
|
|
||||||
if (files.isNullOrEmpty()) B.btnAction.updateState(DownloadStatus.UNAVAILABLE)
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
B.progressDownload.progress = 0
|
B.progressDownload.progress = 0
|
||||||
B.progressDownload.invisible()
|
B.progressDownload.invisible()
|
||||||
@@ -172,11 +156,6 @@ class AppUpdateView : RelativeLayout {
|
|||||||
B.btnAction.addNegativeOnClickListener(onClickListener)
|
B.btnAction.addNegativeOnClickListener(onClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallbackProp
|
|
||||||
fun installAction(onClickListener: OnClickListener?) {
|
|
||||||
B.btnAction.addInstallOnClickListener(onClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallbackProp
|
@CallbackProp
|
||||||
fun longClick(onClickListener: OnLongClickListener?) {
|
fun longClick(onClickListener: OnLongClickListener?) {
|
||||||
B.layoutContent.setOnLongClickListener(onClickListener)
|
B.layoutContent.setOnLongClickListener(onClickListener)
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ import com.aurora.extensions.isRAndAbove
|
|||||||
import com.aurora.extensions.runOnUiThread
|
import com.aurora.extensions.runOnUiThread
|
||||||
import com.aurora.extensions.share
|
import com.aurora.extensions.share
|
||||||
import com.aurora.extensions.show
|
import com.aurora.extensions.show
|
||||||
import com.aurora.extensions.showDialog
|
|
||||||
import com.aurora.extensions.toast
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
import com.aurora.gplayapi.data.models.AuthData
|
||||||
@@ -96,7 +95,6 @@ import kotlinx.coroutines.launch
|
|||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.io.File
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
@@ -437,31 +435,6 @@ class AppDetailsFragment : BaseFragment(R.layout.fragment_details) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun install() {
|
|
||||||
val files = File(
|
|
||||||
PathUtil.getAppDownloadDir(
|
|
||||||
requireContext(),
|
|
||||||
app.packageName,
|
|
||||||
app.versionCode
|
|
||||||
).path
|
|
||||||
).listFiles() ?: return
|
|
||||||
|
|
||||||
val apkFiles = files.filter { it.path.endsWith(".apk") }
|
|
||||||
val preferredInstaller =
|
|
||||||
Preferences.getInteger(requireContext(), Preferences.PREFERENCE_INSTALLER_ID)
|
|
||||||
|
|
||||||
if (apkFiles.size > 1 && preferredInstaller == 1) {
|
|
||||||
showDialog(R.string.title_installer, R.string.dialog_desc_native_split)
|
|
||||||
} else {
|
|
||||||
viewModel.install(requireContext(), app.packageName, apkFiles)
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
binding.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun uninstallApp() {
|
private fun uninstallApp() {
|
||||||
val installer = AppInstaller.getInstance(requireContext()).getPreferredInstaller()
|
val installer = AppInstaller.getInstance(requireContext()).getPreferredInstaller()
|
||||||
@@ -554,31 +527,13 @@ class AppDetailsFragment : BaseFragment(R.layout.fragment_details) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun startDownload(forceDownload: Boolean = false) {
|
private fun startDownload() {
|
||||||
when (downloadStatus) {
|
when (downloadStatus) {
|
||||||
DownloadStatus.DOWNLOADING -> {
|
DownloadStatus.DOWNLOADING -> {
|
||||||
flip(1)
|
flip(1)
|
||||||
toast("Already downloading")
|
toast("Already downloading")
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadStatus.COMPLETED -> {
|
|
||||||
if (forceDownload) {
|
|
||||||
purchase()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if files are still present, else proceed to re-download the app
|
|
||||||
val files = File(
|
|
||||||
PathUtil.getAppDownloadDir(
|
|
||||||
requireContext(),
|
|
||||||
app.packageName,
|
|
||||||
app.versionCode
|
|
||||||
).path
|
|
||||||
).listFiles()
|
|
||||||
|
|
||||||
if (files?.isNotEmpty() == true) install() else purchase()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
flip(1)
|
flip(1)
|
||||||
purchase()
|
purchase()
|
||||||
@@ -660,10 +615,6 @@ class AppDetailsFragment : BaseFragment(R.layout.fragment_details) {
|
|||||||
("$installedVersion ➔ ${app.versionName} (${app.versionCode})")
|
("$installedVersion ➔ ${app.versionName} (${app.versionCode})")
|
||||||
btn.setText(R.string.action_update)
|
btn.setText(R.string.action_update)
|
||||||
btn.addOnClickListener { startDownload() }
|
btn.addOnClickListener { startDownload() }
|
||||||
btn.addOnLongClickListener {
|
|
||||||
startDownload(true)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
binding.layoutDetailsApp.txtLine3.text = installedVersion
|
binding.layoutDetailsApp.txtLine3.text = installedVersion
|
||||||
btn.setText(R.string.action_open)
|
btn.setText(R.string.action_open)
|
||||||
@@ -689,15 +640,6 @@ class AppDetailsFragment : BaseFragment(R.layout.fragment_details) {
|
|||||||
startDownload()
|
startDownload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
btn.addOnLongClickListener {
|
|
||||||
if (authData.isAnonymous && !app.isFree) {
|
|
||||||
toast(R.string.toast_purchase_blocked)
|
|
||||||
} else {
|
|
||||||
btn.setText(R.string.download_metadata)
|
|
||||||
startDownload(true)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
if (uninstallActionEnabled) {
|
if (uninstallActionEnabled) {
|
||||||
binding.layoutDetailsToolbar.toolbar.invalidateMenu()
|
binding.layoutDetailsToolbar.toolbar.invalidateMenu()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,11 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.aurora.extensions.isRAndAbove
|
import com.aurora.extensions.isRAndAbove
|
||||||
import com.aurora.extensions.toast
|
import com.aurora.extensions.toast
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.event.BusEvent
|
import com.aurora.store.data.event.BusEvent
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||||
import com.aurora.store.util.PathUtil
|
import com.aurora.store.util.PathUtil
|
||||||
@@ -48,7 +46,6 @@ import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
|||||||
import com.aurora.store.view.ui.commons.BaseFragment
|
import com.aurora.store.view.ui.commons.BaseFragment
|
||||||
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.io.File
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -186,24 +183,6 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
|||||||
}
|
}
|
||||||
.positiveAction { _ -> updateSingle(app) }
|
.positiveAction { _ -> updateSingle(app) }
|
||||||
.negativeAction { _ -> cancelSingle(app) }
|
.negativeAction { _ -> cancelSingle(app) }
|
||||||
.installAction { _ ->
|
|
||||||
val files = File(
|
|
||||||
PathUtil.getAppDownloadDir(
|
|
||||||
requireContext(),
|
|
||||||
app.packageName,
|
|
||||||
app.versionCode
|
|
||||||
).path
|
|
||||||
).listFiles()
|
|
||||||
|
|
||||||
// Downloaded files are missing, trigger re-download
|
|
||||||
if (files.isNullOrEmpty()) {
|
|
||||||
updateSingle(app)
|
|
||||||
return@installAction
|
|
||||||
}
|
|
||||||
|
|
||||||
val apkFiles = files.filter { it.path.endsWith(".apk") }
|
|
||||||
viewModel.install(requireContext(), app.packageName, apkFiles)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,15 +20,12 @@
|
|||||||
package com.aurora.store.viewmodel.all
|
package com.aurora.store.viewmodel.all
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
|
||||||
import com.aurora.store.util.DownloadWorkerUtil
|
import com.aurora.store.util.DownloadWorkerUtil
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import java.io.File
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -70,18 +67,6 @@ class UpdatesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun install(context: Context, packageName: String, files: List<File>) {
|
|
||||||
try {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
AppInstaller.getInstance(context).getPreferredInstaller()
|
|
||||||
.install(packageName, files)
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.e(TAG, "Failed to install app", exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun download(app: App) {
|
fun download(app: App) {
|
||||||
viewModelScope.launch { downloadWorkerUtil.enqueueApp(app) }
|
viewModelScope.launch { downloadWorkerUtil.enqueueApp(app) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.aurora.gplayapi.data.models.Review
|
|||||||
import com.aurora.gplayapi.data.models.details.TestingProgramStatus
|
import com.aurora.gplayapi.data.models.details.TestingProgramStatus
|
||||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
|
||||||
import com.aurora.store.data.model.ExodusReport
|
import com.aurora.store.data.model.ExodusReport
|
||||||
import com.aurora.store.data.model.Report
|
import com.aurora.store.data.model.Report
|
||||||
import com.aurora.store.data.network.HttpClient
|
import com.aurora.store.data.network.HttpClient
|
||||||
@@ -17,7 +16,6 @@ import com.aurora.store.data.providers.AuthProvider
|
|||||||
import com.aurora.store.util.DownloadWorkerUtil
|
import com.aurora.store.util.DownloadWorkerUtil
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import java.io.File
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
@@ -146,18 +144,6 @@ class AppDetailsViewModel @Inject constructor(
|
|||||||
viewModelScope.launch { downloadWorkerUtil.cancelDownload(app.packageName) }
|
viewModelScope.launch { downloadWorkerUtil.cancelDownload(app.packageName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun install(context: Context, packageName: String, files: List<File>) {
|
|
||||||
try {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
AppInstaller.getInstance(context).getPreferredInstaller()
|
|
||||||
.install(packageName, files)
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.e(TAG, "Failed to install app", exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseResponse(response: String, packageName: String): List<Report> {
|
private fun parseResponse(response: String, packageName: String): List<Report> {
|
||||||
try {
|
try {
|
||||||
val gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
|
val gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
|
||||||
|
|||||||
@@ -70,18 +70,6 @@
|
|||||||
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/btn_install"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/action_install"
|
|
||||||
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
|
||||||
</RelativeLayout>
|
|
||||||
</ViewFlipper>
|
</ViewFlipper>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user