Updating apps is done in a separate foreground service so that AuroraStore doesn't need to be open for updating to work, every app downloaded is fetched with a unique random but saved and constant groupID based on appID and versionCode (prevents install issues with updated apps while keeping history), fix double install calls by finishing the implementation of the install queue (ServiceInstaller only for now), fix issues with ServiceInstaller (uninstall calls wrong bus event, service connection was not detached properly) and more + more to come

This commit is contained in:
Konstantin Tuev
2021-06-08 22:22:25 +03:00
parent aa9ccbcd00
commit ca4f8d939e
13 changed files with 586 additions and 74 deletions

View File

@@ -77,7 +77,10 @@ class DownloadManager private constructor(var context: Context) {
if (packageList.contains(download.tag)) {
val packageName = download.tag
if (packageName != null) {
fetch.deleteGroup(packageName.hashCode())
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
groupIDsOfPackageName.forEach {
fetch.deleteGroup(it)
}
packageList.remove(packageName)
}
}

View File

@@ -31,9 +31,9 @@ import com.tonyodev.fetch2.Request
import com.tonyodev.fetch2core.Extras
import java.lang.reflect.Modifier
private inline fun Request.attachMetaData(app: App) {
private inline fun Request.attachMetaData(context: Context, app: App) {
apply {
groupId = app.id
groupId = app.getGroupId(context)
tag = app.packageName
enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY
networkType = NetworkType.ALL
@@ -61,7 +61,7 @@ object RequestBuilder {
File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file)
}
return Request(file.url, fileName).apply {
attachMetaData(app)
attachMetaData(context, app)
attachExtra(app)
}
}

View File

@@ -0,0 +1,55 @@
package com.aurora.store.data.downloader
import android.content.Context
import com.aurora.gplayapi.data.models.App
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_UNIQUE_GROUP_IDS
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.apache.commons.lang3.RandomUtils
class RequestGroupIdBuilder {
data class AppIDnVersion(val id: Int, val versionCode: Int)
companion object {
fun getGroupIDsForApp(context: Context, appID: Int): MutableList<Int> {
val data = Preferences.getPrefs(context).getString(PREFERENCE_UNIQUE_GROUP_IDS, "")!!
val gson = Gson()
var groupIDMap = HashMap<Int, RequestGroupIdBuilder.AppIDnVersion>()
if (data.isNotEmpty()) {
val empMapType = object : TypeToken<Map<Int, RequestGroupIdBuilder.AppIDnVersion>?>() {}.type
groupIDMap = HashMap(gson.fromJson(data, empMapType) ?: HashMap())
}
val out = mutableListOf<Int>()
for (item in groupIDMap.entries) {
if (item.value.id == appID) {
out.add(item.key)
}
}
return out
}
}
}
fun App.getGroupId(context: Context): Int {
val data = Preferences.getPrefs(context).getString(PREFERENCE_UNIQUE_GROUP_IDS, "")!!
val gson = Gson()
var groupIDMap = HashMap<Int, RequestGroupIdBuilder.AppIDnVersion>()
if (data.isNotEmpty()) {
val empMapType = object : TypeToken<Map<Int, RequestGroupIdBuilder.AppIDnVersion>?>() {}.type
groupIDMap = HashMap(gson.fromJson(data, empMapType) ?: HashMap())
}
for (item in groupIDMap.entries) {
if (item.value.id == this.id && item.value.versionCode == this.versionCode) {
return item.key
}
}
var randomGroupID = RandomUtils.nextInt(0, Int.MAX_VALUE)
while (groupIDMap.containsKey(randomGroupID)) {
randomGroupID = RandomUtils.nextInt(0, Int.MAX_VALUE)
}
groupIDMap[randomGroupID] = RequestGroupIdBuilder.AppIDnVersion(this.id, this.versionCode)
Preferences.getPrefs(context).edit().putString(PREFERENCE_UNIQUE_GROUP_IDS, gson.toJson(groupIDMap)).apply()
return randomGroupID
}

View File

@@ -32,8 +32,10 @@ import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.aurora.services.IPrivilegedCallback
import com.aurora.services.IPrivilegedService
import com.aurora.store.AuroraApplication
import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.util.Log
import com.aurora.store.util.PackageUtil
@@ -42,6 +44,8 @@ import java.io.File
class ServiceInstaller(context: Context) : InstallerBase(context) {
private lateinit var serviceConnection: ServiceConnection
companion object {
const val ACTION_INSTALL_REPLACE_EXISTING = 2
const val PRIVILEGED_EXTENSION_PACKAGE_NAME = "com.aurora.services"
@@ -83,6 +87,7 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
override fun uninstall(packageName: String) {
val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
AuroraApplication.enqueuedInstalls.add(packageName)
val service = IPrivilegedService.Stub.asInterface(binder)
if (service.hasPrivilegedPermissions()) {
@@ -100,7 +105,7 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
extra: String?
) {
removeFromInstallQueue(packageName)
handleCallback(packageName, returnCode, extra)
handleCallbackUninstall(packageName, returnCode, extra)
}
}
@@ -120,6 +125,7 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_misconfigured)
)
removeFromInstallQueue(packageName)
}
}
@@ -141,8 +147,15 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun xInstall(packageName: String, uriList: List<Uri>) {
val serviceConnection = object : ServiceConnection {
serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
if (isAlreadyQueued(packageName)) {
if (::serviceConnection.isInitialized) {
context.unbindService(serviceConnection)
}
return
}
AuroraApplication.enqueuedInstalls.add(packageName)
val service = IPrivilegedService.Stub.asInterface(binder)
if (service.hasPrivilegedPermissions()) {
@@ -182,6 +195,7 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_misconfigured)
)
removeFromInstallQueue(packageName)
}
}
@@ -201,6 +215,32 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
)
}
private fun handleCallbackUninstall(packageName: String, returnCode: Int, extra: String?) {
Log.i("Services Callback : $packageName $returnCode $extra")
when (returnCode) {
PackageInstaller.STATUS_SUCCESS -> {
EventBus.getDefault().post(
BusEvent.UninstallEvent(
packageName,
context.getString(R.string.installer_status_success)
)
)
}
else -> {
val error = AppInstaller.getErrorString(
context,
returnCode
)
postError(packageName, error, extra)
}
}
if (::serviceConnection.isInitialized) {
context.unbindService(serviceConnection)
}
}
private fun handleCallback(packageName: String, returnCode: Int, extra: String?) {
Log.i("Services Callback : $packageName $returnCode $extra")
@@ -222,6 +262,16 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
postError(packageName, error, extra)
}
}
if (::serviceConnection.isInitialized) {
context.unbindService(serviceConnection)
}
}
override fun postError(packageName: String, error: String?, extra: String?) {
super.postError(packageName, error, extra)
if (::serviceConnection.isInitialized) {
context.unbindService(serviceConnection)
}
}
override fun getUri(file: File): Uri {

View File

@@ -24,6 +24,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.store.BuildConfig
import com.aurora.store.data.downloader.RequestGroupIdBuilder
import com.aurora.store.data.event.BusEvent.InstallEvent
import com.aurora.store.data.event.BusEvent.UninstallEvent
import com.aurora.store.data.installer.AppInstaller
@@ -72,7 +73,10 @@ open class PackageManagerReceiver : BroadcastReceiver() {
private fun clearNotification(context: Context, packageName: String) {
val notificationManager = context.applicationContext
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(packageName, packageName.hashCode())
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
groupIDsOfPackageName.forEach {
notificationManager.cancel(packageName, it)
}
}
private fun clearDownloads(context: Context, packageName: String) {

View File

@@ -26,6 +26,7 @@ import android.graphics.Color
import android.os.Build
import android.os.IBinder
import android.util.ArrayMap
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.aurora.Constants
@@ -34,6 +35,7 @@ import com.aurora.extensions.isLAndAbove
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.downloader.RequestGroupIdBuilder
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.receiver.DownloadCancelReceiver
@@ -78,10 +80,6 @@ class NotificationService : Service() {
return null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return START_NOT_STICKY
}
override fun onCreate() {
super.onCreate()
@@ -137,6 +135,10 @@ class NotificationService : Service() {
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
}
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
}
}
fetch.addListener(fetchListener)
@@ -184,6 +186,11 @@ class NotificationService : Service() {
if (app == null)
return
if (status == Status.DELETED) {
notificationManager.cancel(app.id)
return
}
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
builder.setContentTitle(app.displayName)
builder.setSmallIcon(R.drawable.ic_notification_outlined)
@@ -201,7 +208,8 @@ class NotificationService : Service() {
builder.setContentText(getString(R.string.download_canceled))
builder.color = Color.RED
}
Status.FAILED -> {
Status.FAILED ->
{
builder.setSmallIcon(R.drawable.ic_download_fail)
builder.setContentText(getString(R.string.download_failed))
builder.color = Color.RED
@@ -362,12 +370,26 @@ class NotificationService : Service() {
fun onEventMainThread(event: Any) {
when (event) {
is InstallerEvent.Success -> {
val app = appMap[event.packageName.hashCode()]
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(this, event.packageName.hashCode())
var app: App? = null
for (item in groupIDsOfPackageName) {
app = appMap[item]
if (app != null) {
break
}
}
if (app != null)
notifyInstallationStatus(app, event.extra)
}
is InstallerEvent.Failed -> {
val app = appMap[event.packageName.hashCode()]
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(this, event.packageName.hashCode())
var app: App? = null
for (item in groupIDsOfPackageName) {
app = appMap[item]
if (app != null) {
break
}
}
if (app != null)
notifyInstallationStatus(app, event.error)
}

View File

@@ -15,6 +15,7 @@ import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.downloader.RequestBuilder.buildRequest
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.installer.NativeInstaller
import com.aurora.store.data.model.SelfUpdate
import com.aurora.store.util.CertUtil.isFDroidApp
@@ -135,14 +136,14 @@ class SelfUpdateService : Service() {
groupId: Int, download: Download, error: Error,
throwable: Throwable?, fetchGroup: FetchGroup
) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@SelfUpdateService)) {
Log.e("Error self-updating ${app.displayName}")
destroyService()
}
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) {
if (groupId == app.getGroupId(this@SelfUpdateService) && fetchGroup.groupDownloadProgress == 100) {
Log.d("Calling installer ${app.displayName}")
try {
@@ -163,7 +164,7 @@ class SelfUpdateService : Service() {
}
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@SelfUpdateService)) {
Log.d("Self-update cancelled ${app.displayName}")
destroyService()
}

View File

@@ -0,0 +1,346 @@
package com.aurora.store.data.service
import android.app.Notification
import android.content.Intent
import android.os.*
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.MutableLiveData
import com.aurora.Constants
import com.aurora.extensions.stackTraceToString
import com.aurora.extensions.toast
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.store.R
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.downloader.RequestBuilder
import com.aurora.store.data.downloader.RequestGroupIdBuilder
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.UpdateFile
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.tonyodev.fetch2.*
import com.tonyodev.fetch2core.FetchObserver
import com.tonyodev.fetch2core.Reason
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.apache.commons.io.FileUtils
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList
import kotlin.concurrent.timerTask
class UpdateService: LifecycleService() {
lateinit var fetch: Fetch
private lateinit var fetchListener: AbstractFetchGroupListener
private var fetchActiveDownloadObserver = object : FetchObserver<Boolean> {
override fun onChanged(data: Boolean, reason: Reason) {
if (!data && !installing.get() && listeners.isEmpty()) {
Handler(Looper.getMainLooper()).postDelayed ({
if (!installing.get() && listeners.isEmpty()) {
stopSelf()
}
}, 5 * 1000)
}
}
}
private var hasActiveDownloadObserver = false
private val listeners: ArrayList<AbstractFetchGroupListener> = ArrayList()
private val pendingEvents: MutableMap</*groupId: */Int, AppDownloadStatus> = mutableMapOf()
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
inner class UpdateServiceBinder : Binder() {
fun getUpdateService() : UpdateService {
return this@UpdateService
}
}
private var installing: AtomicBoolean = AtomicBoolean()
private lateinit var purchaseHelper: PurchaseHelper
private lateinit var authData: AuthData
data class AppDownloadStatus(val download: Download, val fetchGroup: FetchGroup,
val isCancelled: Boolean = false,
val isComplete: Boolean = false)
@get:RequiresApi(Build.VERSION_CODES.O)
private val notification: Notification
get() {
val notificationBuilder =
NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
return getNotification(notificationBuilder)
}
private fun getNotification(builder: NotificationCompat.Builder): Notification {
return builder.setAutoCancel(true)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.setContentTitle("Updating apps")
.setContentText("Updating apps in the background")
.setOngoing(true)
.setSmallIcon(R.drawable.ic_notification_outlined)
.build()
}
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(1, notification)
} else {
val notification = getNotification(
NotificationCompat.Builder(
this,
Constants.NOTIFICATION_CHANNEL_GENERAL
)
)
startForeground(1, notification)
}
EventBus.getDefault().register(this)
authData = AuthProvider.with(this).getAuthData()
purchaseHelper = PurchaseHelper(authData)
fetch = DownloadManager.with(this).fetch
fetchListener = object : AbstractFetchGroupListener() {
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onAdded(groupId, download, fetchGroup)
}
if (!hasActiveDownloadObserver) {
hasActiveDownloadObserver = true
fetch.addActiveDownloadsObserver(true, fetchActiveDownloadObserver)
}
}
override fun onProgress(
groupId: Int,
download: Download,
etaInMilliSeconds: Long,
downloadedBytesPerSecond: Long,
fetchGroup: FetchGroup
) {
listeners.forEach {
it.onProgress(groupId, download, etaInMilliSeconds, downloadedBytesPerSecond, fetchGroup)
}
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onCompleted(groupId, download, fetchGroup)
}
if (listeners.isEmpty()) {
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true)
}
if (fetchGroup.groupDownloadProgress == 100 || fetchGroup.groupDownloadProgress == -1) {
Handler(Looper.getMainLooper()).post {
try {
install(download.tag!!, fetchGroup.downloads)
} catch (e: Exception) {
Log.e(e.stackTraceToString())
}
}
} /* else if (fetchGroup.groupDownloadProgress == -1) {
fetch.deleteGroup(fetchGroup.id)
}*/
}
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onCancelled(groupId, download, fetchGroup)
}
if (listeners.isEmpty()) {
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
}
}
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onDeleted(groupId, download, fetchGroup)
}
if (listeners.isEmpty()) {
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
}
}
}
/*liveUpdateData.observe(this) { updateData ->
}*/
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
fetch.addListener(fetchListener)
}
}
fun updateApp(app: App) {
installing.set(true)
task {
val files = purchaseHelper.purchase(
app.packageName,
app.versionCode,
app.offerType
)
files.map { RequestBuilder.buildRequest(this, app, it) }
} successUi {
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
if (requests.isNotEmpty()) {
fetch.enqueue(requests) {
Log.i("Updating ${app.displayName}")
}
} else {
toast("Failed to update ${app.displayName}")
}
} failUi {
Log.e("Failed to update ${app.displayName}")
}
}
var timer: Timer? = null
val timerTask: TimerTask = timerTask {
Handler(Looper.getMainLooper()).post {
if (!installing.get() && listeners.isEmpty()) {
fetch.hasActiveDownloads(true, { hasActiveDownloads ->
if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) {
Handler(Looper.getMainLooper()).post {
stopSelf()
}
}
})
}
}
}
@Synchronized
private fun install(packageName: String, files: List<Download>?) {
files?.let { downloads ->
installing.set(true)
var filesExist = true
downloads.forEach { download ->
filesExist = filesExist && FileUtils.getFile(download.file).exists()
}
if (filesExist) {
task {
try {
val installer = AppInstaller(this)
.getPreferredInstaller()
installer.install(
packageName,
files
.filter { it.file.endsWith(".apk") }
.map { it.file }.toList()
)
} catch (th: Throwable) {
th.printStackTrace()
}
}.fail {
Log.e(it.stackTraceToString())
}
}
}
}
@Subscribe()
fun onEventMainThreadExec(event: Any) {
when (event) {
is InstallerEvent.Success -> {
if (timer != null) {
timer!!.cancel()
timer = null
}
if (timer == null) {
timer = Timer()
}
installing.set(false)
timer!!.schedule(timerTask, 10 * 1000)
}
is InstallerEvent.Failed -> {
if (timer != null) {
timer!!.cancel()
timer = null
}
if (timer == null) {
timer = Timer()
}
installing.set(false)
timer!!.schedule(timerTask, 10 * 1000)
}
else -> {
}
}
}
fun registerListener(listener: AbstractFetchGroupListener) {
listeners.add(listener)
val iterator = pendingEvents.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (item.value.isCancelled && !item.value.isComplete) {
listener.onCancelled(item.key, item.value.download, item.value.fetchGroup)
} else if (!item.value.isCancelled && item.value.isComplete) {
listener.onCompleted(item.key, item.value.download, item.value.fetchGroup)
}
iterator.remove()
}
}
fun unregisterListener(listener: AbstractFetchGroupListener) {
listeners.remove(listener)
}
private var binder: UpdateServiceBinder = UpdateServiceBinder()
override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
listeners.clear()
if (!installing.get()) {
fetch.hasActiveDownloads(true, { hasActiveDownloads ->
if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) {
Handler(Looper.getMainLooper()).post {
stopSelf()
}
}
})
}
return true
}
override fun onRebind(intent: Intent?) {
super.onRebind(intent)
}
override fun onDestroy() {
super.onDestroy()
if (hasActiveDownloadObserver) {
hasActiveDownloadObserver = false
fetch.removeActiveDownloadsObserver(fetchActiveDownloadObserver)
}
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
fetch.removeListener(fetchListener)
}
}
fun updateAll(updateFileMap: MutableMap<Int, UpdateFile>) {
updateFileMap.values.forEach { updateApp(it.app) }
}
}

View File

@@ -23,6 +23,8 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import com.google.gson.Gson
object Preferences {
@@ -57,9 +59,15 @@ object Preferences {
const val PREFERENCE_UPDATES_EXTENDED = "PREFERENCE_UPDATES_EXTENDED"
const val PREFERENCE_UNIQUE_GROUP_IDS = "PREFERENCE_UNIQUE_GROUP_IDS"
private var prefs: SharedPreferences? = null
fun getPrefs(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
if (prefs == null) {
prefs = PreferenceManager.getDefaultSharedPreferences(context)
}
return prefs!!
}
fun putString(context: Context, key: String, value: String) {

View File

@@ -41,6 +41,7 @@ import com.aurora.store.R
import com.aurora.store.State
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.downloader.RequestBuilder
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.installer.AppInstaller
@@ -427,14 +428,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
private fun startDownload() {
when (status) {
Status.PAUSED -> {
fetch.resumeGroup(app.id)
fetch.resumeGroup(app.getGroupId(this@AppDetailsActivity))
}
Status.DOWNLOADING -> {
flip(1)
toast("Already downloading")
}
Status.COMPLETED -> {
fetch.getFetchGroup(app.id) {
fetch.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
verifyAndInstall(it.downloads)
}
}
@@ -518,7 +519,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
if (requestList.isNotEmpty()) {
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/
fetch.deleteGroup(app.id)
fetch.deleteGroup(app.getGroupId(this@AppDetailsActivity))
/*Enqueue new fetch group*/
fetch.enqueue(
@@ -638,7 +639,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
downloadManager = DownloadManager.with(this)
fetch = downloadManager.fetch
fetch.getFetchGroup(app.id) { fetchGroup: FetchGroup ->
fetch.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
status = Status.COMPLETED
} else if (downloadManager.isDownloading(fetchGroup)) {
@@ -662,7 +663,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
totalBlocks: Int,
fetchGroup: FetchGroup
) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
status = download.status
flip(1)
@@ -680,7 +681,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
}
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
status = download.status
flip(1)
FileUtils.touch(inProgressMarker)
@@ -688,7 +689,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
}
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
status = download.status
flip(0)
}
@@ -701,7 +702,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
downloadedBytesPerSecond: Long,
fetchGroup: FetchGroup
) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond)
Log.i(
"${app.displayName} : ${download.file} -> Progress : %d",
@@ -711,7 +712,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) {
if (groupId == app.getGroupId(this@AppDetailsActivity) && fetchGroup.groupDownloadProgress == 100) {
status = download.status
flip(0)
updateProgress(fetchGroup, -1, -1)
@@ -726,7 +727,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
}
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
status = download.status
flip(0)
inProgressMarker.delete()
@@ -740,7 +741,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
throwable: Throwable?,
fetchGroup: FetchGroup
) {
if (groupId == app.id) {
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
status = download.status
flip(0)
inProgressMarker.delete()
@@ -752,7 +753,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
B.layoutDetailsInstall.imgCancel.setOnClickListener {
fetch.cancelGroup(
app.id
app.getGroupId(this@AppDetailsActivity)
)
}
}

View File

@@ -19,7 +19,13 @@
package com.aurora.store.view.ui.updates
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -34,9 +40,11 @@ import com.aurora.store.R
import com.aurora.store.State
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.downloader.RequestBuilder
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.UpdateFile
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.service.UpdateService
import com.aurora.store.databinding.FragmentUpdatesBinding
import com.aurora.store.util.Log
import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_
@@ -60,11 +68,17 @@ class UpdatesFragment : BaseFragment() {
private lateinit var B: FragmentUpdatesBinding
private lateinit var VM: UpdatesViewModel
private lateinit var authData: AuthData
private lateinit var purchaseHelper: PurchaseHelper
private lateinit var fetch: Fetch
private lateinit var fetchListener: AbstractFetchGroupListener
private var serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
updateService!!.registerListener(fetchListener)
}
override fun onServiceDisconnected(name: ComponentName) {
updateService = null
}
}
private var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
@@ -83,10 +97,7 @@ class UpdatesFragment : BaseFragment() {
VM = ViewModelProvider(requireActivity()).get(UpdatesViewModel::class.java)
authData = AuthProvider.with(requireContext()).getAuthData()
purchaseHelper = PurchaseHelper(authData)
fetch = DownloadManager.with(requireContext()).fetch
fetchListener = object : AbstractFetchGroupListener() {
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
@@ -106,11 +117,6 @@ class UpdatesFragment : BaseFragment() {
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (fetchGroup.groupDownloadProgress == 100) {
VM.updateDownload(groupId, fetchGroup, isComplete = true)
try {
install(download.tag!!, fetchGroup.downloads)
} catch (e: Exception) {
Log.e(e.stackTraceToString())
}
}
}
@@ -123,21 +129,30 @@ class UpdatesFragment : BaseFragment() {
}
}
getUpdateServiceInstance()
return B.root
}
override fun onResume() {
getUpdateServiceInstance()
super.onResume()
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
fetch.addListener(fetchListener)
}
override fun onPause() {
if (updateService != null) {
updateService = null
requireContext().unbindService(serviceConnection)
}
super.onPause()
}
override fun onDestroy() {
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
fetch.removeListener(fetchListener)
}
super.onDestroy()
if (updateService != null) {
updateService = null
requireContext().unbindService(serviceConnection)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -147,6 +162,7 @@ class UpdatesFragment : BaseFragment() {
updateFileMap = it
updateController(updateFileMap)
B.swipeRefreshLayout.isRefreshing = false
updateService?.liveUpdateData?.postValue(updateFileMap)
})
B.swipeRefreshLayout.setOnRefreshListener {
@@ -221,44 +237,41 @@ class UpdatesFragment : BaseFragment() {
}
}
private var updateService: UpdateService? = null
private fun updateSingle(app: App) {
VM.updateState(app.id, State.QUEUED)
if (updateService != null) {
VM.updateState(app.getGroupId(requireContext()), State.QUEUED)
task {
val files = purchaseHelper.purchase(
app.packageName,
app.versionCode,
app.offerType
)
files.map { RequestBuilder.buildRequest(requireContext(), app, it) }
} successUi {
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
if (requests.isNotEmpty()) {
fetch.enqueue(requests) {
Log.i("Updating ${app.displayName}")
}
} else {
requireContext().toast("Failed to update ${app.displayName}")
}
} failUi {
Log.e("Failed to update ${app.displayName}")
updateService?.updateApp(app)
}
}
private fun cancelSingle(app: App) {
fetch.cancelGroup(app.id)
updateService?.fetch?.cancelGroup(app.getGroupId(requireContext()))
}
private fun updateAll() {
updateFileMap.values.forEach { updateSingle(it.app) }
updateService?.updateAll(updateFileMap)
VM.updateAllEnqueued = true
}
private fun cancelAll() {
VM.updateAllEnqueued = false
updateFileMap.values.forEach { fetch.cancelGroup(it.app.id) }
updateFileMap.values.forEach { updateService?.fetch?.cancelGroup(it.app.getGroupId(requireContext())) }
}
fun getUpdateServiceInstance() {
if (updateService == null) {
val intent = Intent(requireContext(), UpdateService::class.java)
requireContext().startService(intent)
requireContext().bindService(
intent,
serviceConnection,
0
)
}
}
@Synchronized

View File

@@ -25,6 +25,8 @@ import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.State
import com.aurora.store.data.RequestState
import com.aurora.store.data.downloader.RequestGroupIdBuilder
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.model.UpdateFile
@@ -72,7 +74,7 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
updateFileMap.clear()
apps.forEach {
updateFileMap[it.id] = UpdateFile(it)
updateFileMap[it.getGroupId(getApplication<Application>().applicationContext)] = UpdateFile(it)
}
liveUpdateData.postValue(updateFileMap)
@@ -110,7 +112,10 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
is InstallerEvent.Failed -> {
val packageName = event.packageName
packageName?.let {
updateDownload(packageName.hashCode(), null, true)
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
groupIDsOfPackageName.forEach {
updateDownload(it, null, true)
}
}
}
}
@@ -146,8 +151,11 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
}
private fun updateListAndPost(packageName: String) {
//Remove from map
updateFileMap.remove(packageName.hashCode())
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
groupIDsOfPackageName.forEach {
//Remove from map
updateFileMap.remove(it)
}
//Post new update list
liveUpdateData.postValue(updateFileMap)