Drop dependency upon Fetch2 library

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2023-12-25 20:04:34 +05:30
parent a796c4c381
commit e9080614af
18 changed files with 0 additions and 1810 deletions

View File

@@ -156,9 +156,6 @@ dependencies {
//HTTP Clients
implementation("com.squareup.okhttp3:okhttp:4.12.0")
//Fetch - Downloader
implementation("androidx.tonyodev.fetch2:xfetch2:3.1.6")
//EventBus
implementation("org.greenrobot:eventbus:3.3.1")

View File

@@ -97,11 +97,6 @@
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<service android:name=".data.service.NotificationService" />
<service android:name=".data.installer.InstallerService" />
<service android:name=".data.service.UpdateService" />
<service android:name=".data.service.SelfUpdateService" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
@@ -139,9 +134,6 @@
tools:node="remove" />
</provider>
<receiver android:name=".data.receiver.DownloadResumeReceiver" />
<receiver android:name=".data.receiver.DownloadPauseReceiver" />
<receiver android:name=".data.receiver.DownloadCancelReceiver" />
<receiver
android:name=".data.receiver.InstallReceiver"
android:exported="false" />

View File

@@ -25,14 +25,11 @@ import androidx.core.content.ContextCompat
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import com.aurora.extensions.isPAndAbove
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.receiver.PackageManagerReceiver
import com.aurora.store.data.service.NotificationService
import com.aurora.store.util.CommonUtil
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PackageUtil
import com.tonyodev.fetch2.Fetch
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import org.lsposed.hiddenapibypass.HiddenApiBypass
@@ -52,8 +49,6 @@ class AuroraApplication : Application(), Configuration.Provider {
.setWorkerFactory(workerFactory)
.build()
private lateinit var fetch: Fetch
companion object {
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
}
@@ -69,9 +64,6 @@ class AuroraApplication : Application(), Configuration.Provider {
//Create Notification Channels : General & Alert
NotificationUtil.createNotificationChannel(this)
NotificationService.startService(this)
fetch = DownloadManager.with(this).fetch
// Initialize DownloadWorker to observe and trigger downloads
downloadWorkerUtil.init()

View File

@@ -1,92 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.downloader
import android.content.Context
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences
import com.tonyodev.fetch2.BuildConfig
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Fetch
import com.tonyodev.fetch2.FetchConfiguration
import com.tonyodev.fetch2.FetchGroup
import com.tonyodev.fetch2.FetchListener
import com.tonyodev.fetch2core.DefaultStorageResolver
import com.tonyodev.fetch2core.getFileTempDir
class DownloadManager private constructor(var context: Context) {
companion object : SingletonHolder<DownloadManager, Context>(::DownloadManager)
var fetch: Fetch
init {
fetch = Fetch.getInstance(getFetchConfiguration(context))
}
fun getFetchInstance(): Fetch {
return fetch
}
private fun getFetchConfiguration(context: Context): FetchConfiguration {
return FetchConfiguration.Builder(context)
.enableLogging(BuildConfig.DEBUG)
.enableHashCheck(true)
.enableFileExistChecks(true)
.enableRetryOnNetworkGain(true)
.enableAutoStart(true)
.setAutoRetryMaxAttempts(3)
.setProgressReportingInterval(3000)
.setNamespace(BuildConfig.APPLICATION_ID)
.setStorageResolver(DefaultStorageResolver(context, getFileTempDir(context)))
.build()
}
fun isDownloading(fetchGroup: FetchGroup): Boolean {
return fetchGroup.downloadingDownloads.isNotEmpty()
|| fetchGroup.queuedDownloads.isNotEmpty()
|| fetchGroup.addedDownloads.isNotEmpty()
}
fun isCanceled(fetchGroup: FetchGroup): Boolean {
return fetchGroup.cancelledDownloads.isNotEmpty()
|| fetchGroup.removedDownloads.isNotEmpty()
|| fetchGroup.deletedDownloads.isNotEmpty()
}
fun updateOngoingDownloads(
fetch: Fetch, packageList: MutableList<String?>, download: Download,
fetchListener: FetchListener?
) {
if (packageList.contains(download.tag)) {
val packageName = download.tag
if (packageName != null) {
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
groupIDsOfPackageName.forEach {
fetch.deleteGroup(it)
}
packageList.remove(packageName)
}
}
if (packageList.size == 0) {
fetch.removeListener(fetchListener!!)
}
}
}

View File

@@ -1,71 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.downloader
import android.content.Context
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
import com.aurora.store.util.PathUtil
import com.aurora.store.util.Preferences
import com.google.gson.GsonBuilder
import com.tonyodev.fetch2.EnqueueAction
import com.tonyodev.fetch2.NetworkType
import com.tonyodev.fetch2.Request
import com.tonyodev.fetch2core.Extras
import java.lang.reflect.Modifier
private fun Request.attachMetaData(context: Context, app: App) {
val isWifiOnly = Preferences.getBoolean(context, Preferences.PREFERENCE_DOWNLOAD_WIFI_ONLY)
apply {
groupId = app.getGroupId(context)
tag = app.packageName
enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY
networkType = if (isWifiOnly) NetworkType.WIFI_ONLY else NetworkType.ALL
}
}
private fun Request.attachExtra(app: App) {
val stringMap: MutableMap<String, String> = mutableMapOf()
val gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create()
stringMap[Constants.STRING_EXTRA] = gson.toJson(app)
apply {
extras = Extras(stringMap)
}
}
object RequestBuilder {
fun buildRequest(context: Context, app: App, file: File): Request {
val fileName = when (file.type) {
File.FileType.BASE,
File.FileType.SPLIT -> PathUtil.getApkDownloadFile(context, app.packageName, app.versionCode, file)
File.FileType.OBB,
File.FileType.PATCH -> PathUtil.getObbDownloadFile(app.packageName, file)
}
return Request(file.url, fileName).apply {
attachMetaData(context, app)
attachExtra(app)
}
}
}

View File

@@ -1,54 +0,0 @@
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
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 = (0 until Int.MAX_VALUE).random()
while (groupIDMap.containsKey(randomGroupID)) {
randomGroupID = (0 until Int.MAX_VALUE).random()
}
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

@@ -1,39 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
import android.os.Parcelable
import com.tonyodev.fetch2.Download
import kotlinx.parcelize.Parcelize
@Parcelize
data class DownloadFile(val download: Download) : Parcelable {
override fun hashCode(): Int {
return download.id
}
override fun equals(other: Any?): Boolean {
return when (other) {
is DownloadFile -> other.download.status == download.status && other.download.progress == download.progress
else -> false
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
import com.aurora.gplayapi.data.models.App
import com.aurora.store.State
import com.tonyodev.fetch2.FetchGroup
data class UpdateFile(val app: App) {
var group: FetchGroup? = null
var state: State = State.IDLE
override fun hashCode(): Int {
return app.id
}
override fun equals(other: Any?): Boolean {
return when (other) {
is UpdateFile -> other.app.id == app.id
&& other.state == state
&& isGroupSame(group, other.group)
else -> false
}
}
private fun isGroupSame(oldGroup: FetchGroup?, newGroup: FetchGroup?): Boolean {
if (oldGroup != null && newGroup == null)
return true
return oldGroup?.groupDownloadProgress != newGroup?.groupDownloadProgress
}
}

View File

@@ -1,40 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.Constants.FETCH_GROUP_ID
import com.aurora.store.data.downloader.DownloadManager
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DownloadCancelReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
val groupId = extras.getInt(FETCH_GROUP_ID, -1)
DownloadManager
.with(context)
.getFetchInstance()
.cancelGroup(groupId)
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.Constants.FETCH_GROUP_ID
import com.aurora.store.data.downloader.DownloadManager
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DownloadPauseReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1)
DownloadManager
.with(context)
.getFetchInstance()
.pauseGroup(groupId)
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.Constants.FETCH_GROUP_ID
import com.aurora.store.data.downloader.DownloadManager
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DownloadResumeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1)
DownloadManager
.with(context)
.getFetchInstance()
.resumeGroup(groupId)
}
}
}

View File

@@ -19,12 +19,10 @@
package com.aurora.store.data.receiver
import android.app.NotificationManager
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
@@ -56,8 +54,6 @@ open class PackageManagerReceiver : BroadcastReceiver() {
.getPreferredInstaller()
.removeFromInstallQueue(packageName)
//clearNotification(context, packageName)
val isAutoDeleteAPKEnabled = Preferences.getBoolean(
context,
Preferences.PREFERENCE_AUTO_DELETE
@@ -72,15 +68,6 @@ open class PackageManagerReceiver : BroadcastReceiver() {
}
}
private fun clearNotification(context: Context, packageName: String) {
val notificationManager = context.applicationContext
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
groupIDsOfPackageName.forEach {
notificationManager.cancel(packageName, it)
}
}
private fun clearDownloads(context: Context, packageName: String) {
try {
val rootDirPath = PathUtil.getPackageDirectory(context, packageName)

View File

@@ -1,7 +0,0 @@
package com.aurora.store.data.service
import com.aurora.gplayapi.data.models.App
interface AppMetadataStatusListener {
fun onAppMetadataStatusError(reason: String, app: App)
}

View File

@@ -1,234 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
* Copyright (C) 2022, The Calyx Institute
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.service
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.ArrayMap
import androidx.core.app.NotificationManagerCompat
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
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.util.Log
import com.aurora.store.util.NotificationUtil
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.tonyodev.fetch2.AbstractFetchGroupListener
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Fetch
import com.tonyodev.fetch2.FetchGroup
import com.tonyodev.fetch2.Status
import dagger.hilt.android.AndroidEntryPoint
import java.lang.reflect.Modifier
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@AndroidEntryPoint
class NotificationService : Service() {
companion object {
fun startService(context: Context) {
try {
context.startService(Intent(context, NotificationService::class.java))
} catch (e: Exception) {
Log.e("Failed to start notification service : %s", e.message)
}
}
}
private lateinit var fetch: Fetch
private lateinit var fetchListener: AbstractFetchGroupListener
private lateinit var notificationManager: NotificationManager
private val appMap = ArrayMap<Int, App>()
private val gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
.create()
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
Log.i("Notification Service Started")
EventBus.getDefault().register(this)
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
fetch = DownloadManager.with(this).fetch
fetchListener = object : AbstractFetchGroupListener() {
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
}
override fun onError(
groupId: Int,
download: Download,
error: Error,
throwable: Throwable?,
fetchGroup: FetchGroup
) {
showNotification(groupId, download, fetchGroup)
}
override fun onProgress(
groupId: Int,
download: Download,
etaInMilliSeconds: Long,
downloadedBytesPerSecond: Long,
fetchGroup: FetchGroup
) {
showNotification(groupId, download, fetchGroup)
}
override fun onQueued(
groupId: Int,
download: Download,
waitingNetwork: Boolean,
fetchGroup: FetchGroup
) {
showNotification(groupId, download, fetchGroup)
}
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)
}
private fun showNotification(groupId: Int, download: Download, fetchGroup: FetchGroup) {
//Ignore notifications for completion of sub-parts of a bundled apk
if (download.status == Status.COMPLETED && fetchGroup.groupDownloadProgress < 100)
return
//synchronized(appMap) {
var app: App? = appMap[groupId]
if (app == null) {
app = gson.fromJson(
download.extras.getString(Constants.STRING_EXTRA, "{}"),
App::class.java
)
appMap[groupId] = app
}
if (app == null)
return
if (download.status == Status.DELETED) {
notificationManager.cancel(app.id)
return
}
if (NotificationManagerCompat.from(this).areNotificationsEnabled()) {
notificationManager.notify(
app.packageName,
app.id,
NotificationUtil.getDownloadNotification(this, app, groupId, download, fetchGroup)
)
}
}
@Subscribe()
fun onEventMainThread(event: Any) {
when (event) {
is InstallerEvent.Success -> {
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 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)
}
else -> {
}
}
}
@Synchronized
private fun install(packageName: String, files: List<Download>) {
AppInstaller.getInstance(this)
.getPreferredInstaller()
.install(
packageName,
files
.filter { it.file.endsWith(".apk") }
.map {
it.file
}.toList()
)
}
private fun notifyInstallationStatus(app: App, status: String?) {
if (NotificationManagerCompat.from(this).areNotificationsEnabled()) {
notificationManager.notify(
app.packageName,
app.id,
NotificationUtil.getInstallNotification(this, app, status)
)
}
}
override fun onDestroy() {
Log.i("Notification Service Stopped")
fetch.removeListener(fetchListener)
EventBus.getDefault().unregister(this);
super.onDestroy()
}
}

View File

@@ -1,189 +0,0 @@
package com.aurora.store.data.service
import android.app.Notification
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
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
import com.aurora.store.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.tonyodev.fetch2.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.lang.reflect.Modifier
import java.util.*
@AndroidEntryPoint
class SelfUpdateService : LifecycleService() {
private lateinit var app: App
private lateinit var fetch: Fetch
private lateinit var fetchListener: FetchListener
private var gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
.create()
private val hashCode = BuildConfig.APPLICATION_ID.hashCode()
// Coroutine
private val job = SupervisorJob()
private val serviceScope = CoroutineScope(Dispatchers.IO + job)
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val rawSelfUpdate = intent?.getStringExtra(Constants.STRING_EXTRA)
if (rawSelfUpdate?.isNotBlank() == true) {
val selfUpdate = gson.fromJson(rawSelfUpdate, SelfUpdate::class.java)
selfUpdate?.let {
downloadAndUpdate(it)
}
}
return START_NOT_STICKY
}
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)
}
}
override fun onDestroy() {
job.cancel()
super.onDestroy()
}
private fun destroyService() {
Log.d("Self-update service destroyed")
fetch.removeListener(fetchListener)
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
}
private fun downloadAndUpdate(update: SelfUpdate) {
app = App(BuildConfig.APPLICATION_ID)
app.id = hashCode
app.packageName = BuildConfig.APPLICATION_ID
app.displayName = getString(R.string.app_name)
app.versionName = update.versionName
app.versionCode = update.versionCode
val file = File().apply {
name = "AuroraStore.apk"
url = if (isFDroidVariant)
update.fdroidBuild
else
update.auroraBuild
type = File.FileType.BASE
}
val request = buildRequest(this, app, file)
request.enqueueAction = EnqueueAction.REPLACE_EXISTING
val requestList: MutableList<Request> = ArrayList()
requestList.add(request)
fetch = DownloadManager.with(this).fetch
fetch.enqueue(requestList) {
Log.i("Downloading latest update")
}
fetchListener = getFetchListener()
fetch.addListener(fetchListener)
}
private val isFDroidVariant: Boolean
get() = isFDroidApp(this, BuildConfig.APPLICATION_ID)
@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("Self update")
.setContentText("Updating Aurora Store in background")
.setOngoing(true)
.setSmallIcon(R.drawable.ic_notification_outlined)
.build()
}
private fun getFetchListener(): FetchListener {
return object : AbstractFetchGroupListener() {
override fun onError(
groupId: Int, download: Download, error: Error,
throwable: Throwable?, fetchGroup: FetchGroup
) {
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.getGroupId(this@SelfUpdateService) && fetchGroup.groupDownloadProgress == 100) {
Log.d("Calling installer ${app.displayName}")
try {
NativeInstaller(this@SelfUpdateService).install(
app.packageName,
fetchGroup.downloads.map { it.file }
)
} catch (e: Exception) {
Log.e("Self update : ${e.stackTraceToString()}")
}
serviceScope.launch {
delay(10000)
destroyService()
}
}
}
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (groupId == app.getGroupId(this@SelfUpdateService)) {
Log.d("Self-update cancelled ${app.displayName}")
destroyService()
}
}
}
}
}

View File

@@ -1,760 +0,0 @@
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.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.MutableLiveData
import com.aurora.Constants
import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.stackTraceToString
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.exceptions.ApiException
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.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.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.util.PackageUtil.isSharedLibraryInstalled
import com.aurora.store.util.PathUtil
import com.aurora.store.util.Preferences
import com.tonyodev.fetch2.*
import com.tonyodev.fetch2core.DownloadBlock
import com.tonyodev.fetch2core.FetchObserver
import com.tonyodev.fetch2core.Reason
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.File
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.timerTask
@AndroidEntryPoint
class UpdateService: LifecycleService() {
lateinit var fetch: Fetch
lateinit var downloadManager: DownloadManager
private lateinit var fetchListener: FetchGroupListener
private var fetchActiveDownloadObserver = object : FetchObserver<Boolean> {
override fun onChanged(data: Boolean, reason: Reason) {
if (!data && isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
Handler(Looper.getMainLooper()).postDelayed ({
if (isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
ServiceCompat.stopForeground(
this@UpdateService,
ServiceCompat.STOP_FOREGROUND_REMOVE
)
stopSelf()
}
}, 5 * 1000)
}
}
}
private var hasActiveDownloadObserver = false
private val fetchListeners: ArrayList<FetchGroupListener> = ArrayList()
private val fetchPendingEvents: MutableMap</*groupId: */Int, AppDownloadStatus> = mutableMapOf()
private val appMetadataListeners: ArrayList<AppMetadataStatusListener> = ArrayList()
private val appMetadataPendingEvents: ArrayList<AppMetadataStatus> = ArrayList()
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
inner class UpdateServiceBinder : Binder() {
fun getUpdateService() : UpdateService {
return this@UpdateService
}
}
// HashMap<packageName, HashSet<downloadFilePath>>
private var downloadsInCompletedGroup = HashMap<String, HashSet<String>>()
private var installing = HashSet<String>()
private var lock = ReentrantLock()
// Coroutine
private val job = SupervisorJob()
private val serviceScope = CoroutineScope(Dispatchers.IO + job)
fun putInInstalling(packageName: String?) {
if (packageName == null) {
return
}
if (lock.tryLock()) {
installing.add(packageName)
try {
lock.unlock()
} catch (th: Throwable) {
th.printStackTrace()
}
} else {
Thread {
while (!lock.tryLock()) {
Thread.sleep(50)
}
installing.add(packageName)
try {
lock.unlock()
} catch (th: Throwable) {
th.printStackTrace()
}
}.start()
}
}
fun removeFromInstalling(packageName: String?, runFromCurrentThread: Boolean = false) {
if (packageName == null) {
return
}
if (lock.tryLock()) {
installing.remove(packageName)
try {
lock.unlock()
} catch (th: Throwable) {
th.printStackTrace()
}
} else {
val toRun = Runnable {
while (!lock.tryLock()) {
Thread.sleep(50)
}
installing.remove(packageName)
try {
lock.unlock()
} catch (th: Throwable) {
th.printStackTrace()
}
}
if (runFromCurrentThread) {
toRun.run()
} else {
Thread(toRun).start()
}
}
}
fun isEmptyInstalling(): Boolean {
while (!lock.tryLock()) {
Thread.sleep(50)
}
val out: Boolean = installing.isEmpty()
try {
lock.unlock()
} catch (th: Throwable) {
th.printStackTrace()
}
return out
}
fun containsInInstalling(packageName: String?): Boolean {
if (packageName == null) {
return false
}
while (!lock.tryLock()) {
Thread.sleep(50)
}
val out = installing.contains(packageName)
try {
lock.unlock()
} catch (th: Throwable) {
th.printStackTrace()
}
return out
}
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)
data class AppMetadataStatus(val reason: String, val app: App)
@get:RequiresApi(Build.VERSION_CODES.O)
private val notification: Notification
get() {
val notificationBuilder =
NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_UPDATER_SERVICE)
return getNotification(notificationBuilder)
}
private fun getNotification(builder: NotificationCompat.Builder): Notification {
return builder.setAutoCancel(true)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.setContentTitle(getString(R.string.app_updater_service_notif_title))
.setContentText(getString(R.string.app_updater_service_notif_text))
.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_UPDATER_SERVICE
)
)
startForeground(1, notification)
}
EventBus.getDefault().register(this)
authData = AuthProvider.with(this).getAuthData()
purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient(this))
downloadManager = DownloadManager.with(this)
fetch = downloadManager.fetch
fetchListener = object : FetchGroupListener {
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onAdded(groupId, download, fetchGroup)
}
if (download.tag != null) {
removeFromInstalling(download.tag, runFromCurrentThread = true)
}
if (!hasActiveDownloadObserver) {
hasActiveDownloadObserver = true
fetch.addActiveDownloadsObserver(true, fetchActiveDownloadObserver)
}
}
override fun onAdded(download: Download) {
fetchListeners.forEach {
it.onAdded(download)
}
}
override fun onProgress(
groupId: Int,
download: Download,
etaInMilliSeconds: Long,
downloadedBytesPerSecond: Long,
fetchGroup: FetchGroup
) {
fetchListeners.forEach {
it.onProgress(groupId, download, etaInMilliSeconds, downloadedBytesPerSecond, fetchGroup)
}
}
override fun onProgress(download: Download, etaInMilliSeconds: Long, downloadedBytesPerSecond: Long) {
fetchListeners.forEach {
it.onProgress(download, etaInMilliSeconds, downloadedBytesPerSecond)
}
}
override fun onQueued(groupId: Int, download: Download, waitingNetwork: Boolean, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onQueued(groupId, download, waitingNetwork, fetchGroup)
}
}
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
fetchListeners.forEach {
it.onQueued(download, waitingOnNetwork)
}
}
override fun onRemoved(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onRemoved(groupId, download, fetchGroup)
}
}
override fun onRemoved(download: Download) {
fetchListeners.forEach {
it.onRemoved(download)
}
}
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onResumed(groupId, download, fetchGroup)
}
}
override fun onResumed(download: Download) {
fetchListeners.forEach {
it.onResumed(download)
}
}
override fun onStarted(
groupId: Int,
download: Download,
downloadBlocks: List<DownloadBlock>,
totalBlocks: Int,
fetchGroup: FetchGroup
) {
fetchListeners.forEach {
it.onStarted(
groupId,
download,
downloadBlocks,
totalBlocks,
fetchGroup)
}
}
override fun onStarted(download: Download, downloadBlocks: List<DownloadBlock>, totalBlocks: Int) {
fetchListeners.forEach {
it.onStarted(
download,
downloadBlocks,
totalBlocks)
}
}
override fun onWaitingNetwork(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onWaitingNetwork(groupId, download, fetchGroup)
}
}
override fun onWaitingNetwork(download: Download) {
fetchListeners.forEach {
it.onWaitingNetwork(download)
}
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onCompleted(groupId, download, fetchGroup)
}
if (fetchListeners.isEmpty()) {
fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true)
}
var packageDownloadFilesWhichCompleted = downloadsInCompletedGroup[download.tag!!]
if (packageDownloadFilesWhichCompleted == null) {
packageDownloadFilesWhichCompleted = HashSet()
downloadsInCompletedGroup[download.tag!!] = packageDownloadFilesWhichCompleted
}
packageDownloadFilesWhichCompleted.add(download.file)
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.downloads.all { packageDownloadFilesWhichCompleted.contains(it.file) }) {
downloadsInCompletedGroup.remove(download.tag!!)
if (download.tag != null) {
removeFromInstalling(download.tag, runFromCurrentThread = true)
}
Log.d("Group (${download.tag!!}) downloaded and verified all downloaded!")
Handler(Looper.getMainLooper()).post {
try {
install(download.tag!!, fetchGroup.downloads)
} catch (e: Exception) {
Log.e(e.stackTraceToString())
}
}
}
if (fetchGroup.groupDownloadProgress == 100) {
Log.d("Group (${download.tag!!}) downloaded but NOT verified all downloaded!")
} /* else if (fetchGroup.groupDownloadProgress == -1) {
fetch.deleteGroup(fetchGroup.id)
}*/
}
override fun onCompleted(download: Download) {
fetchListeners.forEach {
it.onCompleted(download)
}
}
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onCancelled(groupId, download, fetchGroup)
}
if (fetchListeners.isEmpty()) {
fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
}
}
override fun onCancelled(download: Download) {
fetchListeners.forEach {
it.onCancelled(download)
}
}
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onDeleted(groupId, download, fetchGroup)
}
if (fetchListeners.isEmpty()) {
fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
}
}
override fun onDeleted(download: Download) {
fetchListeners.forEach {
it.onDeleted(download)
}
}
override fun onDownloadBlockUpdated(
groupId: Int,
download: Download,
downloadBlock: DownloadBlock,
totalBlocks: Int,
fetchGroup: FetchGroup
) {
fetchListeners.forEach {
it.onDownloadBlockUpdated(
groupId,
download,
downloadBlock,
totalBlocks,
fetchGroup
)
}
}
override fun onDownloadBlockUpdated(download: Download, downloadBlock: DownloadBlock, totalBlocks: Int) {
fetchListeners.forEach {
it.onDownloadBlockUpdated(
download,
downloadBlock,
totalBlocks
)
}
}
override fun onError(
groupId: Int,
download: Download,
error: Error,
throwable: Throwable?,
fetchGroup: FetchGroup
) {
fetchListeners.forEach {
it.onError(
groupId,
download,
error,
throwable,
fetchGroup
)
}
}
override fun onError(download: Download, error: Error, throwable: Throwable?) {
fetchListeners.forEach {
it.onError(
download,
error,
throwable
)
}
}
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
fetchListeners.forEach {
it.onPaused(groupId, download, fetchGroup)
}
}
override fun onPaused(download: Download) {
fetchListeners.forEach {
it.onPaused(download)
}
}
}
/*liveUpdateData.observe(this) { updateData ->
}*/
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
fetch.addListener(fetchListener)
}
}
fun updateApp(app: App, removeExisiting: Boolean = false) {
putInInstalling(app.packageName)
serviceScope.launch {
try {
val files = purchaseHelper.purchase(
app.packageName,
app.versionCode,
app.offerType
)
if (app.dependencies.dependentLibraries.isNotEmpty() && isOAndAbove()) {
app.dependencies.dependentLibraries.forEach {
if (!isSharedLibraryInstalled(
this@UpdateService,
it.packageName,
it.versionCode
)
) {
it.displayName = getString(R.string.downloading_dep, app.displayName)
it.iconArtwork = app.iconArtwork
updateApp(it, removeExisiting)
while (containsInInstalling(it.packageName) ||
!isSharedLibraryInstalled(
this@UpdateService,
it.packageName,
it.versionCode
)
) {
delay(1000)
}
// Clear library downloads
clearDownloadsIfRequired(it.packageName)
}
}
}
val requests = files.filter { it.url.isNotEmpty() }
.map { RequestBuilder.buildRequest(this@UpdateService, app, it) }
.toList()
if (requests.isNotEmpty()) {
if (removeExisiting) {
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/
fetch.deleteGroup(app.getGroupId(this@UpdateService))
}
fetch.enqueue(requests) {
Log.i("Updating ${app.displayName}")
}
} else {
removeFromInstalling(app.packageName)
Log.e("Failed to download : ${app.displayName}")
appMetadataListeners.forEach {
it.onAppMetadataStatusError(
getString(R.string.purchase_session_expired),
app
)
}
if (appMetadataListeners.isEmpty()) {
appMetadataPendingEvents.add(
AppMetadataStatus(
getString(R.string.purchase_session_expired),
app
)
)
}
}
} catch (exception: Exception) {
removeFromInstalling(app.packageName)
var reason = "Unknown"
when (exception) {
is ApiException.AppNotPurchased -> {
reason = getString(R.string.purchase_invalid)
}
is ApiException.AppNotFound -> {
reason = getString(R.string.purchase_not_found)
}
is ApiException.AppNotSupported -> {
reason = getString(R.string.purchase_unsupported)
}
is ApiException.EmptyDownloads -> {
reason = getString(R.string.purchase_no_file)
}
}
appMetadataListeners.forEach {
it.onAppMetadataStatusError(reason, app)
}
if (appMetadataListeners.isEmpty()) {
appMetadataPendingEvents.add(AppMetadataStatus(reason, app))
}
Log.e("Failed to purchase ${app.displayName} : $reason")
}
}
}
var timer: Timer? = null
val timerTaskRun: Runnable = Runnable {
Handler(Looper.getMainLooper()).post {
if (isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
fetch.hasActiveDownloads(true) { hasActiveDownloads ->
if (!hasActiveDownloads && isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
Handler(Looper.getMainLooper()).post {
ServiceCompat.stopForeground(
this@UpdateService,
ServiceCompat.STOP_FOREGROUND_REMOVE
)
stopSelf()
}
}
}
}
}
}
@Synchronized
private fun install(packageName: String, files: List<Download>?) {
if (containsInInstalling(packageName)) {
println("Already installing $packageName!")
return
}
putInInstalling(packageName)
files?.let { downloads ->
if (downloads.all { File(it.file).exists() }) {
serviceScope.launch {
try {
AppInstaller.getInstance(this@UpdateService).getPreferredInstaller()
.install(
packageName,
files.filter { it.file.endsWith(".apk") }.map { it.file }.toList()
)
} catch (exception: Exception) {
removeFromInstalling(packageName)
Log.e(exception.stackTraceToString())
}
}
} else {
removeFromInstalling(packageName)
}
}
}
var timerLock = Object()
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onEventBackgroundThreadExec(event: Any) {
when (event) {
is InstallerEvent.Success,
is InstallerEvent.Cancelled,
is InstallerEvent.Failed -> {
when (event) {
is InstallerEvent.Success -> event.packageName
is InstallerEvent.Cancelled -> event.packageName
is InstallerEvent.Failed -> event.packageName
else -> null
}?.run {
removeFromInstalling(this)
}
synchronized(timerLock) {
if (timer != null) {
timer!!.cancel()
timer = null
}
if (timer == null) {
timer = Timer()
}
timer!!.schedule(timerTask { timerTaskRun.run() }, 5 * 1000)
}
}
else -> {
}
}
}
fun registerFetchListener(listener: FetchGroupListener) {
fetchListeners.add(listener)
val iterator = fetchPendingEvents.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 registerAppMetadataListener(listener: AppMetadataStatusListener) {
appMetadataListeners.add(listener)
val iterator = appMetadataPendingEvents.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
listener.onAppMetadataStatusError(item.reason, item.app)
iterator.remove()
}
}
fun unregisterAppMetadataListener(listener: AppMetadataStatusListener) {
appMetadataListeners.remove(listener)
}
fun unregisterFetchListener(listener: AbstractFetchGroupListener) {
fetchListeners.remove(listener)
}
private var binder: UpdateServiceBinder = UpdateServiceBinder()
override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
fetchListeners.clear()
appMetadataListeners.clear()
synchronized(timerLock) {
if (timer != null) {
timer!!.cancel()
timer = null
}
if (timer == null) {
timer = Timer()
}
timer!!.schedule(timerTask { timerTaskRun.run() }, 5 * 1000)
}
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) }
}
private fun clearDownloadsIfRequired(packageName: String) {
if (Preferences.getBoolean(this, Preferences.PREFERENCE_AUTO_DELETE)) {
try {
val rootDirPath = PathUtil.getPackageDirectory(this, packageName)
val rootDir = File(rootDirPath)
if (rootDir.exists()) rootDir.deleteRecursively()
} catch (e: Exception) {
Log.d("Failed to clear downloads!", e)
}
}
}
}

View File

@@ -25,12 +25,6 @@ import com.aurora.store.data.room.download.Download as AuroraDownload
import com.aurora.store.R
import com.aurora.store.data.activity.InstallActivity
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.receiver.DownloadCancelReceiver
import com.aurora.store.data.receiver.DownloadPauseReceiver
import com.aurora.store.data.receiver.DownloadResumeReceiver
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.FetchGroup
import com.tonyodev.fetch2.Status
import java.net.URL
import java.util.UUID
@@ -89,131 +83,6 @@ object NotificationUtil {
.build()
}
fun getDownloadNotification(
context: Context,
app: App,
groupId: Int,
download: Download,
fetchGroup: FetchGroup
): Notification {
val status = download.status
val builder = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_GENERAL)
builder.setContentTitle(app.displayName)
builder.setSmallIcon(R.drawable.ic_notification_outlined)
builder.color = ContextCompat.getColor(context, R.color.colorAccent)
builder.setWhen(download.created)
builder.setContentIntent(getContentIntentForDownloads(context))
when (status) {
Status.PAUSED -> {
builder.setSmallIcon(R.drawable.ic_download_pause)
builder.setContentText(context.getString(R.string.download_paused))
}
Status.CANCELLED -> {
builder.setSmallIcon(R.drawable.ic_download_cancel)
builder.setContentText(context.getString(R.string.download_canceled))
builder.color = Color.RED
}
Status.FAILED -> {
builder.setSmallIcon(R.drawable.ic_download_fail)
builder.setContentText(context.getString(R.string.download_failed))
builder.color = Color.RED
}
Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) {
builder.setSmallIcon(android.R.drawable.stat_sys_download_done)
builder.setContentText(context.getString(R.string.download_completed))
}
else -> {
builder.setSmallIcon(android.R.drawable.stat_sys_download)
builder.setContentText(context.getString(R.string.download_metadata))
}
}
val progress = fetchGroup.groupDownloadProgress
val progressBigText = NotificationCompat.BigTextStyle()
when (status) {
Status.QUEUED -> {
builder.setProgress(100, 0, true)
progressBigText.bigText(context.getString(R.string.download_queued))
builder.setStyle(progressBigText)
}
Status.DOWNLOADING -> {
val speedString: String =
CommonUtil.humanReadableByteSpeed(download.downloadedBytesPerSecond, true)
progressBigText.bigText(
context.getString(
R.string.download_progress,
fetchGroup.completedDownloads.size + 1,
fetchGroup.downloads.size,
speedString
)
)
builder.setStyle(progressBigText)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_download_pause,
context.getString(R.string.action_pause),
getPauseIntent(context, groupId)
).build()
)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_download_cancel,
context.getString(R.string.action_cancel),
getCancelIntent(context, groupId)
).build()
)
if (progress < 0) builder.setProgress(
100,
0,
true
) else builder.setProgress(100, progress, false)
}
Status.PAUSED -> {
progressBigText.bigText(
context.getString(
R.string.download_paused,
fetchGroup.completedDownloads.size,
fetchGroup.downloads.size
)
)
builder.setStyle(progressBigText)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_download_pause,
context.getString(R.string.action_resume),
getResumeIntent(context, groupId)
).build()
)
}
Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) {
builder.setAutoCancel(true)
builder.setContentIntent(getContentIntentForDetails(context, app.packageName))
builder.setStyle(progressBigText)
}
else -> {
}
}
when (status) {
Status.DOWNLOADING -> builder.setCategory(Notification.CATEGORY_PROGRESS)
Status.FAILED, Status.CANCELLED -> builder.setCategory(Notification.CATEGORY_ERROR)
else -> builder.setCategory(Notification.CATEGORY_STATUS)
}
return builder.build()
}
fun getDownloadNotification(
context: Context,
download: AuroraDownload,
@@ -307,33 +176,6 @@ object NotificationUtil {
return builder.build()
}
private fun getPauseIntent(context: Context, groupId: Int): PendingIntent {
val intent = Intent(context, DownloadPauseReceiver::class.java)
intent.putExtra(Constants.FETCH_GROUP_ID, groupId)
val flags = if (isMAndAbove())
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
else PendingIntent.FLAG_CANCEL_CURRENT
return PendingIntent.getBroadcast(context, groupId, intent, flags)
}
private fun getResumeIntent(context: Context, groupId: Int): PendingIntent {
val intent = Intent(context, DownloadResumeReceiver::class.java)
intent.putExtra(Constants.FETCH_GROUP_ID, groupId)
val flags = if (isMAndAbove())
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
else PendingIntent.FLAG_CANCEL_CURRENT
return PendingIntent.getBroadcast(context, groupId, intent, flags)
}
private fun getCancelIntent(context: Context, groupId: Int): PendingIntent {
val intent = Intent(context, DownloadCancelReceiver::class.java)
intent.putExtra(Constants.FETCH_GROUP_ID, groupId)
val flags = if (isMAndAbove())
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
else PendingIntent.FLAG_CANCEL_CURRENT
return PendingIntent.getBroadcast(context, groupId, intent, flags)
}
private fun getContentIntentForDetails(context: Context, packageName: String): PendingIntent {
return NavDeepLinkBuilder(context)
.setGraph(R.navigation.mobile_navigation)

View File

@@ -29,17 +29,12 @@ import androidx.navigation.fragment.navArgs
import com.aurora.extensions.copyToClipBoard
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.databinding.SheetDownloadMenuBinding
import com.aurora.store.util.DownloadWorkerUtil
import com.tonyodev.fetch2.Fetch
import com.tonyodev.fetch2.Status
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlin.properties.Delegates
@AndroidEntryPoint
class DownloadMenuSheet : BaseBottomSheet() {