Merge branch 'master' into 'master'
Enabled downloading/updating any/all apps in the background with a service, fixed many issues with download, install, uninstall See merge request AuroraOSS/AuroraStore!130
This commit is contained in:
@@ -157,6 +157,7 @@
|
||||
|
||||
<service android:name=".data.service.NotificationService" />
|
||||
<service android:name=".data.installer.InstallerService" />
|
||||
<service android:name=".data.service.UpdateService" />
|
||||
<service
|
||||
android:name="com.novoda.merlin.MerlinService"
|
||||
android:exported="false" />
|
||||
|
||||
@@ -22,6 +22,8 @@ interface IPrivilegedService {
|
||||
|
||||
boolean hasPrivilegedPermissions();
|
||||
|
||||
boolean isMoreMethodImplemented();
|
||||
|
||||
oneway void installPackage(
|
||||
in Uri packageURI,
|
||||
in int flags,
|
||||
@@ -52,6 +54,15 @@ interface IPrivilegedService {
|
||||
in IPrivilegedCallback callback
|
||||
);
|
||||
|
||||
oneway void installSplitPackageMore(
|
||||
in String packageName,
|
||||
in List<Uri> uriList,
|
||||
in int flags,
|
||||
in String installerPackageName,
|
||||
in IPrivilegedCallback callback,
|
||||
in List<String> fileList
|
||||
);
|
||||
|
||||
oneway void deletePackage(
|
||||
in String packageName,
|
||||
in int flags,
|
||||
|
||||
@@ -37,6 +37,7 @@ object Constants {
|
||||
|
||||
const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT"
|
||||
const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL"
|
||||
const val NOTIFICATION_CHANNEL_UPDATER_SERVICE = "NOTIFICATION_CHANNEL_UPDATER_SERVICE"
|
||||
|
||||
const val URL_DISPENSER = "http://goolag.store:1337/api/auth"
|
||||
|
||||
|
||||
@@ -77,7 +77,10 @@ class DownloadManager private constructor(var context: Context) {
|
||||
if (packageList.contains(download.tag)) {
|
||||
val packageName = download.tag
|
||||
if (packageName != null) {
|
||||
fetch.deleteGroup(packageName.hashCode())
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
|
||||
groupIDsOfPackageName.forEach {
|
||||
fetch.deleteGroup(it)
|
||||
}
|
||||
packageList.remove(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ import com.tonyodev.fetch2.Request
|
||||
import com.tonyodev.fetch2core.Extras
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
private inline fun Request.attachMetaData(app: App) {
|
||||
private inline fun Request.attachMetaData(context: Context, app: App) {
|
||||
apply {
|
||||
groupId = app.id
|
||||
groupId = app.getGroupId(context)
|
||||
tag = app.packageName
|
||||
enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY
|
||||
networkType = NetworkType.ALL
|
||||
@@ -61,7 +61,7 @@ object RequestBuilder {
|
||||
File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file)
|
||||
}
|
||||
return Request(file.url, fileName).apply {
|
||||
attachMetaData(app)
|
||||
attachMetaData(context, app)
|
||||
attachExtra(app)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.aurora.store.data.downloader
|
||||
|
||||
import android.content.Context
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UNIQUE_GROUP_IDS
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.apache.commons.lang3.RandomUtils
|
||||
|
||||
class RequestGroupIdBuilder {
|
||||
|
||||
data class AppIDnVersion(val id: Int, val versionCode: Int)
|
||||
|
||||
companion object {
|
||||
fun getGroupIDsForApp(context: Context, appID: Int): MutableList<Int> {
|
||||
val data = Preferences.getPrefs(context).getString(PREFERENCE_UNIQUE_GROUP_IDS, "")!!
|
||||
val gson = Gson()
|
||||
var groupIDMap = HashMap<Int, RequestGroupIdBuilder.AppIDnVersion>()
|
||||
if (data.isNotEmpty()) {
|
||||
val empMapType = object : TypeToken<Map<Int, RequestGroupIdBuilder.AppIDnVersion>?>() {}.type
|
||||
groupIDMap = HashMap(gson.fromJson(data, empMapType) ?: HashMap())
|
||||
}
|
||||
val out = mutableListOf<Int>()
|
||||
for (item in groupIDMap.entries) {
|
||||
if (item.value.id == appID) {
|
||||
out.add(item.key)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun App.getGroupId(context: Context): Int {
|
||||
val data = Preferences.getPrefs(context).getString(PREFERENCE_UNIQUE_GROUP_IDS, "")!!
|
||||
val gson = Gson()
|
||||
var groupIDMap = HashMap<Int, RequestGroupIdBuilder.AppIDnVersion>()
|
||||
if (data.isNotEmpty()) {
|
||||
val empMapType = object : TypeToken<Map<Int, RequestGroupIdBuilder.AppIDnVersion>?>() {}.type
|
||||
groupIDMap = HashMap(gson.fromJson(data, empMapType) ?: HashMap())
|
||||
}
|
||||
for (item in groupIDMap.entries) {
|
||||
if (item.value.id == this.id && item.value.versionCode == this.versionCode) {
|
||||
return item.key
|
||||
}
|
||||
}
|
||||
var randomGroupID = RandomUtils.nextInt(0, Int.MAX_VALUE)
|
||||
while (groupIDMap.containsKey(randomGroupID)) {
|
||||
randomGroupID = RandomUtils.nextInt(0, Int.MAX_VALUE)
|
||||
}
|
||||
groupIDMap[randomGroupID] = RequestGroupIdBuilder.AppIDnVersion(this.id, this.versionCode)
|
||||
Preferences.getPrefs(context).edit().putString(PREFERENCE_UNIQUE_GROUP_IDS, gson.toJson(groupIDMap)).apply()
|
||||
return randomGroupID
|
||||
}
|
||||
@@ -26,9 +26,10 @@ import com.aurora.store.R
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||
|
||||
open class AppInstaller constructor(var context: Context) {
|
||||
open class AppInstaller private constructor(var context: Context) {
|
||||
|
||||
companion object {
|
||||
private var instance: AppInstaller? = null
|
||||
fun getErrorString(context: Context, status: Int): String {
|
||||
return when (status) {
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> context.getString(R.string.installer_status_user_action)
|
||||
@@ -40,22 +41,50 @@ open class AppInstaller constructor(var context: Context) {
|
||||
else -> context.getString(R.string.installer_status_failure)
|
||||
}
|
||||
}
|
||||
fun getInstance(context: Context): AppInstaller {
|
||||
if (instance == null) {
|
||||
instance = AppInstaller(context.applicationContext)
|
||||
}
|
||||
return instance!!
|
||||
}
|
||||
}
|
||||
|
||||
val choiceAndInstaller = HashMap<Int, IInstaller>()
|
||||
|
||||
fun getPreferredInstaller(): IInstaller {
|
||||
val prefValue = Preferences.getInteger(
|
||||
context,
|
||||
PREFERENCE_INSTALLER_ID
|
||||
)
|
||||
|
||||
if (choiceAndInstaller.containsKey(prefValue)) {
|
||||
return choiceAndInstaller[prefValue]!!
|
||||
}
|
||||
|
||||
return when (prefValue) {
|
||||
1 -> NativeInstaller(context)
|
||||
2 -> RootInstaller(context)
|
||||
3 -> ServiceInstaller(context)
|
||||
1 -> {
|
||||
val installer = NativeInstaller(context)
|
||||
choiceAndInstaller[prefValue] = installer
|
||||
installer
|
||||
}
|
||||
2 -> {
|
||||
val installer = RootInstaller(context)
|
||||
choiceAndInstaller[prefValue] = installer
|
||||
installer
|
||||
}
|
||||
3 -> {
|
||||
val installer = ServiceInstaller(context)
|
||||
choiceAndInstaller[prefValue] = installer
|
||||
installer
|
||||
}
|
||||
else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
SessionInstaller(context)
|
||||
val installer = SessionInstaller(context)
|
||||
choiceAndInstaller[prefValue] = installer
|
||||
installer
|
||||
} else {
|
||||
NativeInstaller(context)
|
||||
val installer = NativeInstaller(context)
|
||||
choiceAndInstaller[prefValue] = installer
|
||||
installer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,23 +25,30 @@ import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.os.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
import com.aurora.services.IPrivilegedCallback
|
||||
import com.aurora.services.IPrivilegedService
|
||||
import com.aurora.store.AuroraApplication
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
|
||||
private lateinit var serviceConnection: ServiceConnection
|
||||
private val executor = ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||
|
||||
companion object {
|
||||
const val ACTION_INSTALL_REPLACE_EXISTING = 2
|
||||
const val PRIVILEGED_EXTENSION_PACKAGE_NAME = "com.aurora.services"
|
||||
@@ -67,8 +74,17 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
val fileList = files.map {
|
||||
when (it) {
|
||||
is File -> it.absolutePath
|
||||
is String -> File(it).absolutePath
|
||||
else -> {
|
||||
throw Exception("Invalid data, expecting listOf() File or String")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xInstall(packageName, uriList)
|
||||
xInstall(packageName, uriList, fileList)
|
||||
}
|
||||
else -> {
|
||||
postError(
|
||||
@@ -81,146 +97,256 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||
}
|
||||
|
||||
override fun uninstall(packageName: String) {
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||
|
||||
if (service.hasPrivilegedPermissions()) {
|
||||
Log.i(context.getString(R.string.installer_service_available))
|
||||
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
|
||||
override fun handleResult(packageName: String, returnCode: Int) {
|
||||
|
||||
executor.execute {
|
||||
val readyWithAction = AtomicBoolean(false)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
if (isAlreadyQueued(packageName)) {
|
||||
if (::serviceConnection.isInitialized) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
readyWithAction.set(true)
|
||||
return
|
||||
}
|
||||
AuroraApplication.enqueuedInstalls.add(packageName)
|
||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||
|
||||
override fun handleResultX(
|
||||
packageName: String,
|
||||
returnCode: Int,
|
||||
extra: String?
|
||||
) {
|
||||
if (service.hasPrivilegedPermissions()) {
|
||||
Log.i(context.getString(R.string.installer_service_available))
|
||||
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
|
||||
override fun handleResult(packageName: String, returnCode: Int) {}
|
||||
|
||||
override fun handleResultX(
|
||||
packageName: String,
|
||||
returnCode: Int,
|
||||
extra: String?
|
||||
) {
|
||||
removeFromInstallQueue(packageName)
|
||||
handleCallbackUninstall(packageName, returnCode, extra)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.deletePackageX(
|
||||
packageName,
|
||||
2,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e("Failed to connect Aurora Services")
|
||||
removeFromInstallQueue(packageName)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
handleCallback(packageName, returnCode, extra)
|
||||
postError(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_misconfigured)
|
||||
)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.deletePackageX(
|
||||
packageName,
|
||||
2,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e("Failed to connect Aurora Services")
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
removeFromInstallQueue(packageName)
|
||||
Log.e("Disconnected from Aurora Services")
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
} else {
|
||||
postError(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_misconfigured)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
removeFromInstallQueue(packageName)
|
||||
Log.e("Disconnected from Aurora Services")
|
||||
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
|
||||
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
|
||||
|
||||
context.bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
while (!readyWithAction.get()) {
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
|
||||
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
|
||||
|
||||
context.bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||
|
||||
if (service.hasPrivilegedPermissions()) {
|
||||
Log.i(context.getString(R.string.installer_service_available))
|
||||
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
|
||||
override fun handleResult(packageName: String, returnCode: Int) {
|
||||
|
||||
private fun xInstall(packageName: String, uriList: List<Uri>, fileList: List<String>) {
|
||||
executor.execute {
|
||||
val readyWithAction = AtomicBoolean(false)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
if (isAlreadyQueued(packageName)) {
|
||||
if (::serviceConnection.isInitialized) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
readyWithAction.set(true)
|
||||
return
|
||||
}
|
||||
AuroraApplication.enqueuedInstalls.add(packageName)
|
||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||
|
||||
override fun handleResultX(
|
||||
packageName: String,
|
||||
returnCode: Int,
|
||||
extra: String?
|
||||
) {
|
||||
if (service.hasPrivilegedPermissions()) {
|
||||
Log.i(context.getString(R.string.installer_service_available))
|
||||
|
||||
val callback = object : IPrivilegedCallback.Stub() {
|
||||
|
||||
override fun handleResult(packageName: String, returnCode: Int) {}
|
||||
|
||||
override fun handleResultX(
|
||||
packageName: String,
|
||||
returnCode: Int,
|
||||
extra: String?
|
||||
) {
|
||||
removeFromInstallQueue(packageName)
|
||||
handleCallback(packageName, returnCode, extra)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (service.isMoreMethodImplemented) {
|
||||
try {
|
||||
service.installSplitPackageMore(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback,
|
||||
fileList
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
} else {
|
||||
throw Exception("New method not implemented")
|
||||
}
|
||||
} catch (th: Throwable) {
|
||||
th.printStackTrace()
|
||||
try {
|
||||
service.installSplitPackageX(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
handleCallback(packageName, returnCode, extra)
|
||||
postError(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_misconfigured)
|
||||
)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
service.installSplitPackageX(
|
||||
packageName,
|
||||
uriList,
|
||||
ACTION_INSTALL_REPLACE_EXISTING,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
callback
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||
readyWithAction.set(true)
|
||||
Log.e("Disconnected from Aurora Services")
|
||||
}
|
||||
} else {
|
||||
postError(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_service_misconfigured)
|
||||
}
|
||||
|
||||
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
|
||||
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
|
||||
|
||||
context.bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
while (!readyWithAction.get()) {
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
Log.i("Services Callback : install wait done")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCallbackUninstall(packageName: String, returnCode: Int, extra: String?) {
|
||||
Log.i("Services Callback : $packageName $returnCode $extra")
|
||||
|
||||
try {
|
||||
when (returnCode) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
EventBus.getDefault().post(
|
||||
BusEvent.UninstallEvent(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_success)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val error = AppInstaller.getErrorString(
|
||||
context,
|
||||
returnCode
|
||||
)
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
removeFromInstallQueue(packageName)
|
||||
Log.e("Disconnected from Aurora Services")
|
||||
postError(packageName, error, extra)
|
||||
}
|
||||
}
|
||||
if (::serviceConnection.isInitialized) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
} catch (th: Throwable) {
|
||||
th.printStackTrace()
|
||||
}
|
||||
|
||||
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
|
||||
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
|
||||
|
||||
context.bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleCallback(packageName: String, returnCode: Int, extra: String?) {
|
||||
Log.i("Services Callback : $packageName $returnCode $extra")
|
||||
|
||||
when (returnCode) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
EventBus.getDefault().post(
|
||||
InstallerEvent.Success(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_success)
|
||||
try {
|
||||
when (returnCode) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
EventBus.getDefault().post(
|
||||
InstallerEvent.Success(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_success)
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val error = AppInstaller.getErrorString(
|
||||
context,
|
||||
returnCode
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val error = AppInstaller.getErrorString(
|
||||
context,
|
||||
returnCode
|
||||
)
|
||||
|
||||
postError(packageName, error, extra)
|
||||
postError(packageName, error, extra)
|
||||
}
|
||||
}
|
||||
if (::serviceConnection.isInitialized) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
} catch (th: Throwable) {
|
||||
th.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun postError(packageName: String, error: String?, extra: String?) {
|
||||
try {
|
||||
super.postError(packageName, error, extra)
|
||||
if (::serviceConnection.isInitialized) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
} catch (th: Throwable) {
|
||||
th.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.downloader.RequestGroupIdBuilder
|
||||
import com.aurora.store.data.event.BusEvent.InstallEvent
|
||||
import com.aurora.store.data.event.BusEvent.UninstallEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
@@ -49,7 +50,7 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
//Clear installation queue
|
||||
AppInstaller(context)
|
||||
AppInstaller.getInstance(context)
|
||||
.getPreferredInstaller()
|
||||
.removeFromInstallQueue(packageName)
|
||||
|
||||
@@ -72,7 +73,10 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
||||
private fun clearNotification(context: Context, packageName: String) {
|
||||
val notificationManager = context.applicationContext
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.cancel(packageName, packageName.hashCode())
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
|
||||
groupIDsOfPackageName.forEach {
|
||||
notificationManager.cancel(packageName, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearDownloads(context: Context, packageName: String) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aurora.store.data.service
|
||||
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
|
||||
interface AppMetadataStatusListener {
|
||||
fun onAppMetadataStatusError(reason: String, app: App)
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.ArrayMap
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.Constants
|
||||
@@ -34,6 +35,7 @@ import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestGroupIdBuilder
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.receiver.DownloadCancelReceiver
|
||||
@@ -78,10 +80,6 @@ class NotificationService : Service() {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -137,6 +135,10 @@ class NotificationService : Service() {
|
||||
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
showNotification(groupId, download, fetchGroup)
|
||||
}
|
||||
|
||||
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
showNotification(groupId, download, fetchGroup)
|
||||
}
|
||||
}
|
||||
|
||||
fetch.addListener(fetchListener)
|
||||
@@ -159,6 +161,13 @@ class NotificationService : Service() {
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
)
|
||||
)
|
||||
channels.add(
|
||||
NotificationChannel(
|
||||
Constants.NOTIFICATION_CHANNEL_UPDATER_SERVICE,
|
||||
getString(R.string.notification_channel_updater_service),
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
)
|
||||
)
|
||||
notificationManager.createNotificationChannels(channels)
|
||||
}
|
||||
}
|
||||
@@ -184,6 +193,11 @@ class NotificationService : Service() {
|
||||
if (app == null)
|
||||
return
|
||||
|
||||
if (status == Status.DELETED) {
|
||||
notificationManager.cancel(app.id)
|
||||
return
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
|
||||
builder.setContentTitle(app.displayName)
|
||||
builder.setSmallIcon(R.drawable.ic_notification_outlined)
|
||||
@@ -201,7 +215,8 @@ class NotificationService : Service() {
|
||||
builder.setContentText(getString(R.string.download_canceled))
|
||||
builder.color = Color.RED
|
||||
}
|
||||
Status.FAILED -> {
|
||||
Status.FAILED ->
|
||||
{
|
||||
builder.setSmallIcon(R.drawable.ic_download_fail)
|
||||
builder.setContentText(getString(R.string.download_failed))
|
||||
builder.color = Color.RED
|
||||
@@ -362,12 +377,26 @@ class NotificationService : Service() {
|
||||
fun onEventMainThread(event: Any) {
|
||||
when (event) {
|
||||
is InstallerEvent.Success -> {
|
||||
val app = appMap[event.packageName.hashCode()]
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(this, event.packageName.hashCode())
|
||||
var app: App? = null
|
||||
for (item in groupIDsOfPackageName) {
|
||||
app = appMap[item]
|
||||
if (app != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (app != null)
|
||||
notifyInstallationStatus(app, event.extra)
|
||||
}
|
||||
is InstallerEvent.Failed -> {
|
||||
val app = appMap[event.packageName.hashCode()]
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(this, event.packageName.hashCode())
|
||||
var app: App? = null
|
||||
for (item in groupIDsOfPackageName) {
|
||||
app = appMap[item]
|
||||
if (app != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (app != null)
|
||||
notifyInstallationStatus(app, event.error)
|
||||
}
|
||||
@@ -379,7 +408,7 @@ class NotificationService : Service() {
|
||||
|
||||
@Synchronized
|
||||
private fun install(packageName: String, files: List<Download>) {
|
||||
AppInstaller(this)
|
||||
AppInstaller.getInstance(this)
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
packageName,
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestBuilder.buildRequest
|
||||
import com.aurora.store.data.downloader.getGroupId
|
||||
import com.aurora.store.data.installer.NativeInstaller
|
||||
import com.aurora.store.data.model.SelfUpdate
|
||||
import com.aurora.store.util.CertUtil.isFDroidApp
|
||||
@@ -135,14 +136,14 @@ class SelfUpdateService : Service() {
|
||||
groupId: Int, download: Download, error: Error,
|
||||
throwable: Throwable?, fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@SelfUpdateService)) {
|
||||
Log.e("Error self-updating ${app.displayName}")
|
||||
destroyService()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) {
|
||||
if (groupId == app.getGroupId(this@SelfUpdateService) && fetchGroup.groupDownloadProgress == 100) {
|
||||
Log.d("Calling installer ${app.displayName}")
|
||||
|
||||
try {
|
||||
@@ -163,7 +164,7 @@ class SelfUpdateService : Service() {
|
||||
}
|
||||
|
||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@SelfUpdateService)) {
|
||||
Log.d("Self-update cancelled ${app.displayName}")
|
||||
destroyService()
|
||||
}
|
||||
|
||||
702
app/src/main/java/com/aurora/store/data/service/UpdateService.kt
Normal file
702
app/src/main/java/com/aurora/store/data/service/UpdateService.kt
Normal file
@@ -0,0 +1,702 @@
|
||||
package com.aurora.store.data.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Intent
|
||||
import android.os.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.stackTraceToString
|
||||
import com.aurora.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.tonyodev.fetch2.*
|
||||
import com.tonyodev.fetch2core.DownloadBlock
|
||||
import com.tonyodev.fetch2core.FetchObserver
|
||||
import com.tonyodev.fetch2core.Reason
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.timerTask
|
||||
|
||||
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()) {
|
||||
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()
|
||||
|
||||
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())
|
||||
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)
|
||||
task {
|
||||
val files = purchaseHelper.purchase(
|
||||
app.packageName,
|
||||
app.versionCode,
|
||||
app.offerType
|
||||
)
|
||||
|
||||
files.filter { it.url.isNotEmpty() }
|
||||
.map { RequestBuilder.buildRequest(this, app, it) }
|
||||
.toList()
|
||||
} successUi { requests ->
|
||||
if (requests.isNotEmpty()) {
|
||||
if (removeExisiting) {
|
||||
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/
|
||||
fetch.deleteGroup(app.getGroupId(this))
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
} failUi { failException ->
|
||||
removeFromInstalling(app.packageName)
|
||||
var reason = "Unknown"
|
||||
|
||||
when (failException) {
|
||||
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 {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun install(packageName: String, files: List<Download>?) {
|
||||
if (containsInInstalling(packageName)) {
|
||||
println("Already installing $packageName!")
|
||||
return
|
||||
}
|
||||
putInInstalling(packageName)
|
||||
files?.let { downloads ->
|
||||
var filesExist = true
|
||||
|
||||
downloads.forEach { download ->
|
||||
filesExist = filesExist && FileUtils.getFile(download.file).exists()
|
||||
}
|
||||
|
||||
if (filesExist) {
|
||||
task {
|
||||
try {
|
||||
val installer = AppInstaller.getInstance(this)
|
||||
.getPreferredInstaller()
|
||||
installer.install(
|
||||
packageName,
|
||||
files
|
||||
.filter { it.file.endsWith(".apk") }
|
||||
.map { it.file }.toList()
|
||||
)
|
||||
} catch (th: Throwable) {
|
||||
removeFromInstalling(packageName)
|
||||
th.printStackTrace()
|
||||
}
|
||||
}.fail {
|
||||
removeFromInstalling(packageName)
|
||||
Log.e(it.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) }
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.File
|
||||
|
||||
fun Context.getInternalBaseDirectory(): String {
|
||||
return filesDir.path
|
||||
return (getExternalFilesDir(null) ?: filesDir).path
|
||||
}
|
||||
|
||||
object PathUtil {
|
||||
|
||||
@@ -23,6 +23,8 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.gson.Gson
|
||||
|
||||
|
||||
object Preferences {
|
||||
|
||||
@@ -57,9 +59,15 @@ object Preferences {
|
||||
|
||||
const val PREFERENCE_UPDATES_EXTENDED = "PREFERENCE_UPDATES_EXTENDED"
|
||||
|
||||
const val PREFERENCE_UNIQUE_GROUP_IDS = "PREFERENCE_UNIQUE_GROUP_IDS"
|
||||
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
fun getPrefs(context: Context): SharedPreferences {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
if (prefs == null) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
return prefs!!
|
||||
}
|
||||
|
||||
fun putString(context: Context, key: String, value: String) {
|
||||
|
||||
@@ -21,9 +21,12 @@ package com.aurora.store.view.ui.details
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -32,20 +35,19 @@ import com.aurora.Constants
|
||||
import com.aurora.extensions.*
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.data.models.File
|
||||
import com.aurora.gplayapi.exceptions.ApiException
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.MainActivity
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestBuilder
|
||||
import com.aurora.store.data.downloader.getGroupId
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.data.service.AppMetadataStatusListener
|
||||
import com.aurora.store.data.service.UpdateService
|
||||
import com.aurora.store.databinding.ActivityDetailsBinding
|
||||
import com.aurora.store.util.*
|
||||
import com.aurora.store.view.ui.downloads.DownloadActivity
|
||||
@@ -64,7 +66,6 @@ import org.apache.commons.io.FileUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
|
||||
class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
@@ -73,9 +74,39 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
private lateinit var authData: AuthData
|
||||
private lateinit var app: App
|
||||
private lateinit var downloadManager: DownloadManager
|
||||
private lateinit var fetch: Fetch
|
||||
private var fetch: Fetch? = null
|
||||
private var downloadManager: DownloadManager? = null
|
||||
|
||||
private var attachToServiceCalled = false
|
||||
private var updateService: UpdateService? = null
|
||||
private var pendingAddListeners = true
|
||||
private var serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
||||
if (::fetchGroupListener.isInitialized && ::appMetadataListener.isInitialized && pendingAddListeners) {
|
||||
updateService!!.registerFetchListener(fetchGroupListener)
|
||||
// appMetadataListener needs to be initialized after the fetchGroupListener
|
||||
updateService!!.registerAppMetadataListener(appMetadataListener)
|
||||
pendingAddListeners = false
|
||||
}
|
||||
if (listOfActionsWhenServiceAttaches.isNotEmpty()) {
|
||||
val iterator = listOfActionsWhenServiceAttaches.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val next = iterator.next()
|
||||
next.run()
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
pendingAddListeners = true
|
||||
}
|
||||
}
|
||||
private lateinit var fetchGroupListener: FetchGroupListener
|
||||
private lateinit var appMetadataListener: AppMetadataStatusListener
|
||||
private lateinit var completionMarker: java.io.File
|
||||
private lateinit var inProgressMarker: java.io.File
|
||||
|
||||
@@ -83,6 +114,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
private var isNone = false
|
||||
private var status = Status.NONE
|
||||
private var isInstalled: Boolean = false
|
||||
private var isUpdatable: Boolean = false
|
||||
private var autoDownload: Boolean = false
|
||||
private var downloadOnly: Boolean = false
|
||||
|
||||
@@ -165,9 +197,8 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (!isLAndAbove()) {
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
getUpdateServiceInstance()
|
||||
checkAndSetupInstall()
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
@@ -206,11 +237,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private var uninstallActionEnabled = false
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_details, menu)
|
||||
if (::app.isInitialized) {
|
||||
val installed = PackageUtil.isInstalled(this, app.packageName)
|
||||
menu?.findItem(R.id.action_uninstall)?.isVisible = installed
|
||||
uninstallActionEnabled = installed
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -305,7 +339,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
showDialog(R.string.title_installer, R.string.dialog_desc_native_split)
|
||||
} else {
|
||||
task {
|
||||
AppInstaller(this)
|
||||
AppInstaller.getInstance(this)
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
app.packageName,
|
||||
@@ -324,7 +358,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
@Synchronized
|
||||
private fun uninstallApp() {
|
||||
task {
|
||||
AppInstaller(this)
|
||||
AppInstaller.getInstance(this)
|
||||
.getPreferredInstaller()
|
||||
.uninstall(app.packageName)
|
||||
}
|
||||
@@ -427,14 +461,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
private fun startDownload() {
|
||||
when (status) {
|
||||
Status.PAUSED -> {
|
||||
fetch.resumeGroup(app.id)
|
||||
fetch?.resumeGroup(app.getGroupId(this@AppDetailsActivity))
|
||||
}
|
||||
Status.DOWNLOADING -> {
|
||||
flip(1)
|
||||
toast("Already downloading")
|
||||
}
|
||||
Status.COMPLETED -> {
|
||||
fetch.getFetchGroup(app.id) {
|
||||
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
|
||||
verifyAndInstall(it.downloads)
|
||||
}
|
||||
}
|
||||
@@ -444,92 +478,25 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val listOfActionsWhenServiceAttaches = ArrayList<Runnable>()
|
||||
|
||||
private fun purchase() {
|
||||
bottomSheetBehavior.isHideable = false
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
updateActionState(State.PROGRESS)
|
||||
|
||||
task {
|
||||
val authData = AuthProvider
|
||||
.with(this)
|
||||
.getAuthData()
|
||||
|
||||
PurchaseHelper(authData)
|
||||
.using(HttpClient.getPreferredClient())
|
||||
.purchase(app.packageName, app.versionCode, app.offerType)
|
||||
} successUi { files ->
|
||||
if (files.isNotEmpty()) {
|
||||
var hasOBB = false
|
||||
|
||||
files.forEach { file ->
|
||||
if (file.type == File.FileType.OBB || file.type == File.FileType.PATCH) {
|
||||
hasOBB = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOBB)
|
||||
enqueueWithStoragePermission(files)
|
||||
else
|
||||
enqueue(files)
|
||||
runWithPermissions(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) {
|
||||
if (updateService == null) {
|
||||
listOfActionsWhenServiceAttaches.add({
|
||||
updateService?.updateApp(app, removeExisiting = true)
|
||||
})
|
||||
getUpdateServiceInstance()
|
||||
} else {
|
||||
Log.e("Failed to download : ${app.displayName}")
|
||||
updateActionState(State.IDLE)
|
||||
updateService?.updateApp(app, removeExisiting = true)
|
||||
}
|
||||
} failUi {
|
||||
updateActionState(State.IDLE)
|
||||
var reason = "Unknown"
|
||||
|
||||
when (it) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
expandBottomSheet(reason)
|
||||
|
||||
Log.e("Failed to purchase ${app.displayName} : $reason")
|
||||
}
|
||||
}
|
||||
|
||||
private fun enqueueWithStoragePermission(files: List<File>) = runWithPermissions(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) {
|
||||
enqueue(files)
|
||||
}
|
||||
|
||||
private fun enqueue(files: List<File>) {
|
||||
val requestList = files
|
||||
.filter { it.url.isNotEmpty() }
|
||||
.map {
|
||||
RequestBuilder.buildRequest(this, app, it)
|
||||
}
|
||||
.toList()
|
||||
|
||||
if (requestList.isNotEmpty()) {
|
||||
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/
|
||||
fetch.deleteGroup(app.id)
|
||||
|
||||
/*Enqueue new fetch group*/
|
||||
fetch.enqueue(
|
||||
requestList
|
||||
) {
|
||||
status = Status.ADDED
|
||||
Log.i("Downloading Apks : %s", app.displayName)
|
||||
}
|
||||
} else {
|
||||
updateActionState(State.IDLE)
|
||||
expandBottomSheet(getString(R.string.purchase_session_expired))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,6 +511,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
else
|
||||
0
|
||||
|
||||
if (progress == 100) {
|
||||
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
||||
return@runOnUiThread
|
||||
}
|
||||
B.layoutDetailsInstall.apply {
|
||||
txtProgressPercent.text = ("${progress}%")
|
||||
|
||||
@@ -585,7 +556,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
B.layoutDetailsInstall.btnDownload.let { btn ->
|
||||
if (isInstalled) {
|
||||
val isUpdatable = PackageUtil.isUpdatable(
|
||||
isUpdatable = PackageUtil.isUpdatable(
|
||||
this,
|
||||
app.packageName,
|
||||
app.versionCode.toLong()
|
||||
@@ -603,6 +574,9 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
btn.setText(R.string.action_open)
|
||||
btn.addOnClickListener { openApp() }
|
||||
}
|
||||
if (!uninstallActionEnabled) {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
} else {
|
||||
if (app.isFree) {
|
||||
btn.setText(R.string.action_install)
|
||||
@@ -618,6 +592,9 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
startDownload()
|
||||
}
|
||||
}
|
||||
if (uninstallActionEnabled) {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -635,16 +612,17 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
private fun attachFetch() {
|
||||
downloadManager = DownloadManager.with(this)
|
||||
fetch = downloadManager.fetch
|
||||
|
||||
fetch.getFetchGroup(app.id) { fetchGroup: FetchGroup ->
|
||||
if (fetch == null) {
|
||||
downloadManager = DownloadManager.with(this)
|
||||
fetch = downloadManager!!.fetch
|
||||
}
|
||||
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
|
||||
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
|
||||
status = Status.COMPLETED
|
||||
} else if (downloadManager.isDownloading(fetchGroup)) {
|
||||
} else if (downloadManager?.isDownloading(fetchGroup) == true) {
|
||||
status = Status.DOWNLOADING
|
||||
flip(1)
|
||||
} else if (downloadManager.isCanceled(fetchGroup)) {
|
||||
} else if (downloadManager?.isCanceled(fetchGroup) == true) {
|
||||
status = Status.CANCELLED
|
||||
} else if (fetchGroup.pausedDownloads.isNotEmpty()) {
|
||||
status = Status.PAUSED
|
||||
@@ -655,6 +633,12 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
fetchGroupListener = object : AbstractFetchGroupListener() {
|
||||
|
||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStarted(
|
||||
groupId: Int,
|
||||
download: Download,
|
||||
@@ -662,7 +646,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
totalBlocks: Int,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(1)
|
||||
|
||||
@@ -680,7 +664,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(1)
|
||||
FileUtils.touch(inProgressMarker)
|
||||
@@ -688,7 +672,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
}
|
||||
@@ -701,7 +685,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
downloadedBytesPerSecond: Long,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond)
|
||||
Log.i(
|
||||
"${app.displayName} : ${download.file} -> Progress : %d",
|
||||
@@ -711,22 +695,21 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity) && fetchGroup.groupDownloadProgress == 100) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
updateProgress(fetchGroup, -1, -1)
|
||||
inProgressMarker.delete()
|
||||
completionMarker.createNewFile()
|
||||
try {
|
||||
verifyAndInstall(fetchGroup.downloads)
|
||||
} catch (e: Exception) {
|
||||
Log.e(e.stackTraceToString())
|
||||
inProgressMarker.delete()
|
||||
completionMarker.createNewFile()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
inProgressMarker.delete()
|
||||
@@ -740,7 +723,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
throwable: Throwable?,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
if (groupId == app.id) {
|
||||
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||
status = download.status
|
||||
flip(0)
|
||||
inProgressMarker.delete()
|
||||
@@ -748,13 +731,63 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fetch.addListener(fetchGroupListener)
|
||||
appMetadataListener = object : AppMetadataStatusListener {
|
||||
override fun onAppMetadataStatusError(reason: String, app: App) {
|
||||
if (app.packageName == this@AppDetailsActivity.app.packageName) {
|
||||
updateActionState(State.IDLE)
|
||||
expandBottomSheet(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getUpdateServiceInstance()
|
||||
|
||||
B.layoutDetailsInstall.imgCancel.setOnClickListener {
|
||||
fetch.cancelGroup(
|
||||
app.id
|
||||
fetch?.cancelGroup(
|
||||
app.getGroupId(this@AppDetailsActivity)
|
||||
)
|
||||
}
|
||||
if (updateService != null) {
|
||||
pendingAddListeners = false
|
||||
updateService!!.registerFetchListener(fetchGroupListener)
|
||||
// appMetadataListener needs to be initialized after the fetchGroupListener
|
||||
updateService!!.registerAppMetadataListener(appMetadataListener)
|
||||
} else {
|
||||
pendingAddListeners = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getUpdateServiceInstance() {
|
||||
if (updateService == null && !attachToServiceCalled) {
|
||||
attachToServiceCalled = true
|
||||
val intent = Intent(this, UpdateService::class.java)
|
||||
startService(intent)
|
||||
bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (updateService != null) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
pendingAddListeners = true
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (updateService != null) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
pendingAddListeners = true
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachBottomSheet() {
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.aurora.store.R
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.model.DownloadFile
|
||||
import com.aurora.store.databinding.ActivityDownloadBinding
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.view.epoxy.views.DownloadViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseActivity
|
||||
@@ -156,10 +157,12 @@ class DownloadActivity : BaseActivity() {
|
||||
}
|
||||
R.id.action_clear_completed -> {
|
||||
fetch.removeAllWithStatus(Status.COMPLETED)
|
||||
Preferences.getPrefs(this).edit().remove(Preferences.PREFERENCE_UNIQUE_GROUP_IDS).apply()
|
||||
return true
|
||||
}
|
||||
R.id.action_force_clear_all -> {
|
||||
fetch.deleteAll()
|
||||
Preferences.getPrefs(this).edit().remove(Preferences.PREFERENCE_UNIQUE_GROUP_IDS).apply()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,21 +113,8 @@ class OnboardingActivity : BaseActivity() {
|
||||
B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
runOnUiThread {
|
||||
B.btnBackward.isEnabled = position != 0
|
||||
if (position == 4) {
|
||||
B.btnForward.text = getString(R.string.action_finish)
|
||||
B.btnForward.setOnClickListener {
|
||||
save(PREFERENCE_INTRO, true)
|
||||
open(SplashActivity::class.java, true)
|
||||
}
|
||||
} else {
|
||||
B.btnForward.text = getString(R.string.action_next)
|
||||
B.btnForward.setOnClickListener {
|
||||
B.viewpager2.setCurrentItem(
|
||||
B.viewpager2.currentItem + 1, true
|
||||
)
|
||||
}
|
||||
}
|
||||
lastPosition = position
|
||||
refreshButtonState()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -145,6 +132,32 @@ class OnboardingActivity : BaseActivity() {
|
||||
B.viewpager2.setCurrentItem(B.viewpager2.currentItem - 1, true)
|
||||
}
|
||||
|
||||
var lastPosition = 0
|
||||
|
||||
fun refreshButtonState() {
|
||||
B.btnBackward.isEnabled = lastPosition != 0
|
||||
if (lastPosition == 4) {
|
||||
B.btnForward.text = getString(R.string.action_finish)
|
||||
B.btnForward.setOnClickListener {
|
||||
save(PREFERENCE_INTRO, true)
|
||||
open(SplashActivity::class.java, true)
|
||||
}
|
||||
for (fragment in supportFragmentManager.fragments) {
|
||||
if (fragment is PermissionsFragment) {
|
||||
B.btnForward.isEnabled = fragment.canGoForward()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
B.btnForward.text = getString(R.string.action_next)
|
||||
B.btnForward.setOnClickListener {
|
||||
B.viewpager2.setCurrentItem(
|
||||
B.viewpager2.currentItem + 1, true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (B.viewpager2.currentItem == 0) {
|
||||
super.onBackPressed()
|
||||
|
||||
@@ -91,6 +91,21 @@ class PermissionsFragment : BaseFragment() {
|
||||
)
|
||||
|
||||
B.epoxyRecycler.withModels {
|
||||
val writeExternalStorage = ActivityCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
val storageManager = if (isRAndAbove()) Environment.isExternalStorageManager() else true
|
||||
val canInstallPackages = if (isOAndAbove()) requireContext().packageManager.canRequestPackageInstalls() else true
|
||||
canGoForward = writeExternalStorage && storageManager && canInstallPackages
|
||||
if (canGoForwardInitial == null) {
|
||||
canGoForwardInitial = canGoForward
|
||||
}
|
||||
if (canGoForward && canGoForwardInitial == false) {
|
||||
if (activity is OnboardingActivity) {
|
||||
(activity!! as OnboardingActivity).refreshButtonState()
|
||||
}
|
||||
}
|
||||
setFilterDuplicates(true)
|
||||
installerList.forEach {
|
||||
add(
|
||||
@@ -99,12 +114,9 @@ class PermissionsFragment : BaseFragment() {
|
||||
.permission(it)
|
||||
.isGranted(
|
||||
when (it.id) {
|
||||
0 -> ActivityCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
1 -> if (isRAndAbove()) Environment.isExternalStorageManager() else true
|
||||
2 -> if (isOAndAbove()) requireContext().packageManager.canRequestPackageInstalls() else true
|
||||
0 -> writeExternalStorage
|
||||
1 -> storageManager
|
||||
2 -> canInstallPackages
|
||||
else -> false
|
||||
}
|
||||
)
|
||||
@@ -164,4 +176,11 @@ class PermissionsFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var canGoForward = false
|
||||
private var canGoForwardInitial: Boolean? = null
|
||||
|
||||
fun canGoForward(): Boolean {
|
||||
return canGoForward
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ class AppMenuSheet : BaseBottomSheet() {
|
||||
|
||||
R.id.action_uninstall -> {
|
||||
task {
|
||||
AppInstaller(requireContext())
|
||||
AppInstaller.getInstance(requireContext())
|
||||
.getPreferredInstaller().uninstall(app.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,24 +19,24 @@
|
||||
|
||||
package com.aurora.store.view.ui.updates
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.stackTraceToString
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestBuilder
|
||||
import com.aurora.store.data.downloader.getGroupId
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.UpdateFile
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.data.service.UpdateService
|
||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_
|
||||
@@ -48,11 +48,8 @@ import com.aurora.store.view.ui.sheets.AppMenuSheet
|
||||
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
||||
import com.tonyodev.fetch2.AbstractFetchGroupListener
|
||||
import com.tonyodev.fetch2.Download
|
||||
import com.tonyodev.fetch2.Fetch
|
||||
import com.tonyodev.fetch2.FetchGroup
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class UpdatesFragment : BaseFragment() {
|
||||
@@ -60,11 +57,28 @@ class UpdatesFragment : BaseFragment() {
|
||||
private lateinit var B: FragmentUpdatesBinding
|
||||
private lateinit var VM: UpdatesViewModel
|
||||
|
||||
private lateinit var authData: AuthData
|
||||
private lateinit var purchaseHelper: PurchaseHelper
|
||||
|
||||
private lateinit var fetch: Fetch
|
||||
val listOfActionsWhenServiceAttaches = ArrayList<Runnable>()
|
||||
private lateinit var fetchListener: AbstractFetchGroupListener
|
||||
private var attachToServiceCalled = false
|
||||
private var serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
||||
updateService!!.registerFetchListener(fetchListener)
|
||||
if (listOfActionsWhenServiceAttaches.isNotEmpty()) {
|
||||
val iterator = listOfActionsWhenServiceAttaches.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val next = iterator.next()
|
||||
next.run()
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
}
|
||||
}
|
||||
|
||||
private var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
|
||||
|
||||
@@ -83,10 +97,7 @@ class UpdatesFragment : BaseFragment() {
|
||||
|
||||
VM = ViewModelProvider(requireActivity()).get(UpdatesViewModel::class.java)
|
||||
|
||||
authData = AuthProvider.with(requireContext()).getAuthData()
|
||||
purchaseHelper = PurchaseHelper(authData)
|
||||
|
||||
fetch = DownloadManager.with(requireContext()).fetch
|
||||
fetchListener = object : AbstractFetchGroupListener() {
|
||||
|
||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
@@ -106,11 +117,6 @@ class UpdatesFragment : BaseFragment() {
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
if (fetchGroup.groupDownloadProgress == 100) {
|
||||
VM.updateDownload(groupId, fetchGroup, isComplete = true)
|
||||
try {
|
||||
install(download.tag!!, fetchGroup.downloads)
|
||||
} catch (e: Exception) {
|
||||
Log.e(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,31 +129,43 @@ class UpdatesFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
getUpdateServiceInstance()
|
||||
|
||||
return B.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
getUpdateServiceInstance()
|
||||
super.onResume()
|
||||
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
|
||||
fetch.addListener(fetchListener)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (updateService != null) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
requireContext().unbindService(serviceConnection)
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
|
||||
fetch.removeListener(fetchListener)
|
||||
}
|
||||
super.onDestroy()
|
||||
if (updateService != null) {
|
||||
updateService = null
|
||||
attachToServiceCalled = false
|
||||
requireContext().unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
VM.liveUpdateData.observe(viewLifecycleOwner, {
|
||||
VM.liveUpdateData.observe(viewLifecycleOwner) {
|
||||
updateFileMap = it
|
||||
updateController(updateFileMap)
|
||||
B.swipeRefreshLayout.isRefreshing = false
|
||||
})
|
||||
updateService?.liveUpdateData?.postValue(updateFileMap)
|
||||
}
|
||||
|
||||
B.swipeRefreshLayout.setOnRefreshListener {
|
||||
VM.observe()
|
||||
@@ -221,44 +239,56 @@ class UpdatesFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private var updateService: UpdateService? = null
|
||||
|
||||
fun runInService(runnable: Runnable) {
|
||||
if (updateService == null) {
|
||||
listOfActionsWhenServiceAttaches.add(runnable)
|
||||
getUpdateServiceInstance()
|
||||
} else {
|
||||
runnable.run()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSingle(app: App) {
|
||||
runInService {
|
||||
VM.updateState(app.getGroupId(requireContext()), State.QUEUED)
|
||||
|
||||
VM.updateState(app.id, State.QUEUED)
|
||||
|
||||
task {
|
||||
val files = purchaseHelper.purchase(
|
||||
app.packageName,
|
||||
app.versionCode,
|
||||
app.offerType
|
||||
)
|
||||
|
||||
files.map { RequestBuilder.buildRequest(requireContext(), app, it) }
|
||||
} successUi {
|
||||
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
|
||||
if (requests.isNotEmpty()) {
|
||||
fetch.enqueue(requests) {
|
||||
Log.i("Updating ${app.displayName}")
|
||||
}
|
||||
} else {
|
||||
requireContext().toast("Failed to update ${app.displayName}")
|
||||
}
|
||||
} failUi {
|
||||
Log.e("Failed to update ${app.displayName}")
|
||||
updateService?.updateApp(app)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelSingle(app: App) {
|
||||
fetch.cancelGroup(app.id)
|
||||
runInService {
|
||||
updateService?.fetch?.cancelGroup(app.getGroupId(requireContext()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAll() {
|
||||
updateFileMap.values.forEach { updateSingle(it.app) }
|
||||
VM.updateAllEnqueued = true
|
||||
runInService {
|
||||
updateService?.updateAll(updateFileMap)
|
||||
VM.updateAllEnqueued = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelAll() {
|
||||
VM.updateAllEnqueued = false
|
||||
updateFileMap.values.forEach { fetch.cancelGroup(it.app.id) }
|
||||
runInService {
|
||||
VM.updateAllEnqueued = false
|
||||
updateFileMap.values.forEach { updateService?.fetch?.cancelGroup(it.app.getGroupId(requireContext())) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getUpdateServiceInstance() {
|
||||
if (updateService == null && !attachToServiceCalled) {
|
||||
attachToServiceCalled = true
|
||||
val intent = Intent(requireContext(), UpdateService::class.java)
|
||||
requireContext().startService(intent)
|
||||
requireContext().bindService(
|
||||
intent,
|
||||
serviceConnection,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -272,7 +302,7 @@ class UpdatesFragment : BaseFragment() {
|
||||
|
||||
if (filesExist) {
|
||||
task {
|
||||
AppInstaller(requireContext())
|
||||
AppInstaller.getInstance(requireContext())
|
||||
.getPreferredInstaller()
|
||||
.install(
|
||||
packageName,
|
||||
|
||||
@@ -25,6 +25,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.RequestState
|
||||
import com.aurora.store.data.downloader.RequestGroupIdBuilder
|
||||
import com.aurora.store.data.downloader.getGroupId
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.UpdateFile
|
||||
@@ -72,7 +74,7 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
||||
updateFileMap.clear()
|
||||
|
||||
apps.forEach {
|
||||
updateFileMap[it.id] = UpdateFile(it)
|
||||
updateFileMap[it.getGroupId(getApplication<Application>().applicationContext)] = UpdateFile(it)
|
||||
}
|
||||
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
@@ -110,7 +112,10 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
||||
is InstallerEvent.Failed -> {
|
||||
val packageName = event.packageName
|
||||
packageName?.let {
|
||||
updateDownload(packageName.hashCode(), null, true)
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
|
||||
groupIDsOfPackageName.forEach {
|
||||
updateDownload(it, null, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,8 +151,11 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
||||
}
|
||||
|
||||
private fun updateListAndPost(packageName: String) {
|
||||
//Remove from map
|
||||
updateFileMap.remove(packageName.hashCode())
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
|
||||
groupIDsOfPackageName.forEach {
|
||||
//Remove from map
|
||||
updateFileMap.remove(it)
|
||||
}
|
||||
|
||||
//Post new update list
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
|
||||
@@ -366,4 +366,7 @@
|
||||
<string name="toast_aas_token_failed">Could not generate AAS Token</string>
|
||||
<string name="toast_export_success">Device config exported</string>
|
||||
<string name="toast_export_failed">Could not export device config</string>
|
||||
<string name="notification_channel_updater_service">App background download service</string>
|
||||
<string name="app_updater_service_notif_title">Background app download</string>
|
||||
<string name="app_updater_service_notif_text">Enables background app download</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user