Drop dependency upon Fetch2 library
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user