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.service.NotificationService" />
|
||||||
<service android:name=".data.installer.InstallerService" />
|
<service android:name=".data.installer.InstallerService" />
|
||||||
|
<service android:name=".data.service.UpdateService" />
|
||||||
<service
|
<service
|
||||||
android:name="com.novoda.merlin.MerlinService"
|
android:name="com.novoda.merlin.MerlinService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ interface IPrivilegedService {
|
|||||||
|
|
||||||
boolean hasPrivilegedPermissions();
|
boolean hasPrivilegedPermissions();
|
||||||
|
|
||||||
|
boolean isMoreMethodImplemented();
|
||||||
|
|
||||||
oneway void installPackage(
|
oneway void installPackage(
|
||||||
in Uri packageURI,
|
in Uri packageURI,
|
||||||
in int flags,
|
in int flags,
|
||||||
@@ -52,6 +54,15 @@ interface IPrivilegedService {
|
|||||||
in IPrivilegedCallback callback
|
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(
|
oneway void deletePackage(
|
||||||
in String packageName,
|
in String packageName,
|
||||||
in int flags,
|
in int flags,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ object Constants {
|
|||||||
|
|
||||||
const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT"
|
const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT"
|
||||||
const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL"
|
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"
|
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)) {
|
if (packageList.contains(download.tag)) {
|
||||||
val packageName = download.tag
|
val packageName = download.tag
|
||||||
if (packageName != null) {
|
if (packageName != null) {
|
||||||
fetch.deleteGroup(packageName.hashCode())
|
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(context, packageName.hashCode())
|
||||||
|
groupIDsOfPackageName.forEach {
|
||||||
|
fetch.deleteGroup(it)
|
||||||
|
}
|
||||||
packageList.remove(packageName)
|
packageList.remove(packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ import com.tonyodev.fetch2.Request
|
|||||||
import com.tonyodev.fetch2core.Extras
|
import com.tonyodev.fetch2core.Extras
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
private inline fun Request.attachMetaData(app: App) {
|
private inline fun Request.attachMetaData(context: Context, app: App) {
|
||||||
apply {
|
apply {
|
||||||
groupId = app.id
|
groupId = app.getGroupId(context)
|
||||||
tag = app.packageName
|
tag = app.packageName
|
||||||
enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY
|
enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY
|
||||||
networkType = NetworkType.ALL
|
networkType = NetworkType.ALL
|
||||||
@@ -61,7 +61,7 @@ object RequestBuilder {
|
|||||||
File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file)
|
File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file)
|
||||||
}
|
}
|
||||||
return Request(file.url, fileName).apply {
|
return Request(file.url, fileName).apply {
|
||||||
attachMetaData(app)
|
attachMetaData(context, app)
|
||||||
attachExtra(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
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
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 {
|
companion object {
|
||||||
|
private var instance: AppInstaller? = null
|
||||||
fun getErrorString(context: Context, status: Int): String {
|
fun getErrorString(context: Context, status: Int): String {
|
||||||
return when (status) {
|
return when (status) {
|
||||||
PackageInstaller.STATUS_FAILURE_ABORTED -> context.getString(R.string.installer_status_user_action)
|
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)
|
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 {
|
fun getPreferredInstaller(): IInstaller {
|
||||||
val prefValue = Preferences.getInteger(
|
val prefValue = Preferences.getInteger(
|
||||||
context,
|
context,
|
||||||
PREFERENCE_INSTALLER_ID
|
PREFERENCE_INSTALLER_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (choiceAndInstaller.containsKey(prefValue)) {
|
||||||
|
return choiceAndInstaller[prefValue]!!
|
||||||
|
}
|
||||||
|
|
||||||
return when (prefValue) {
|
return when (prefValue) {
|
||||||
1 -> NativeInstaller(context)
|
1 -> {
|
||||||
2 -> RootInstaller(context)
|
val installer = NativeInstaller(context)
|
||||||
3 -> ServiceInstaller(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) {
|
else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
SessionInstaller(context)
|
val installer = SessionInstaller(context)
|
||||||
|
choiceAndInstaller[prefValue] = installer
|
||||||
|
installer
|
||||||
} else {
|
} 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.ServiceConnection
|
||||||
import android.content.pm.PackageInstaller
|
import android.content.pm.PackageInstaller
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.*
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.RemoteException
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.aurora.services.IPrivilegedCallback
|
import com.aurora.services.IPrivilegedCallback
|
||||||
import com.aurora.services.IPrivilegedService
|
import com.aurora.services.IPrivilegedService
|
||||||
|
import com.aurora.store.AuroraApplication
|
||||||
import com.aurora.store.BuildConfig
|
import com.aurora.store.BuildConfig
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.data.event.BusEvent
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.util.Log
|
import com.aurora.store.util.Log
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import java.io.File
|
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) {
|
class ServiceInstaller(context: Context) : InstallerBase(context) {
|
||||||
|
|
||||||
|
private lateinit var serviceConnection: ServiceConnection
|
||||||
|
private val executor = ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, LinkedBlockingQueue())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_INSTALL_REPLACE_EXISTING = 2
|
const val ACTION_INSTALL_REPLACE_EXISTING = 2
|
||||||
const val PRIVILEGED_EXTENSION_PACKAGE_NAME = "com.aurora.services"
|
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 -> {
|
else -> {
|
||||||
postError(
|
postError(
|
||||||
@@ -81,146 +97,256 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
override fun uninstall(packageName: String) {
|
||||||
val serviceConnection = object : ServiceConnection {
|
executor.execute {
|
||||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
val readyWithAction = AtomicBoolean(false)
|
||||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
val serviceConnection = object : ServiceConnection {
|
||||||
if (service.hasPrivilegedPermissions()) {
|
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||||
Log.i(context.getString(R.string.installer_service_available))
|
if (isAlreadyQueued(packageName)) {
|
||||||
|
if (::serviceConnection.isInitialized) {
|
||||||
val callback = object : IPrivilegedCallback.Stub() {
|
context.unbindService(serviceConnection)
|
||||||
|
}
|
||||||
override fun handleResult(packageName: String, returnCode: Int) {
|
readyWithAction.set(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
AuroraApplication.enqueuedInstalls.add(packageName)
|
||||||
|
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||||
|
|
||||||
override fun handleResultX(
|
if (service.hasPrivilegedPermissions()) {
|
||||||
packageName: String,
|
Log.i(context.getString(R.string.installer_service_available))
|
||||||
returnCode: Int,
|
|
||||||
extra: String?
|
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)
|
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 {
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
service.deletePackageX(
|
removeFromInstallQueue(packageName)
|
||||||
packageName,
|
Log.e("Disconnected from Aurora Services")
|
||||||
2,
|
readyWithAction.set(true)
|
||||||
BuildConfig.APPLICATION_ID,
|
|
||||||
callback
|
|
||||||
)
|
|
||||||
} catch (e: RemoteException) {
|
|
||||||
Log.e("Failed to connect Aurora Services")
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
postError(
|
|
||||||
packageName,
|
|
||||||
context.getString(R.string.installer_status_failure),
|
|
||||||
context.getString(R.string.installer_service_misconfigured)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName) {
|
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
|
||||||
removeFromInstallQueue(packageName)
|
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
|
||||||
Log.e("Disconnected from Aurora Services")
|
|
||||||
|
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)
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
private fun xInstall(packageName: String, uriList: List<Uri>, fileList: List<String>) {
|
||||||
val serviceConnection = object : ServiceConnection {
|
executor.execute {
|
||||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
val readyWithAction = AtomicBoolean(false)
|
||||||
val service = IPrivilegedService.Stub.asInterface(binder)
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
serviceConnection = object : ServiceConnection {
|
||||||
if (service.hasPrivilegedPermissions()) {
|
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||||
Log.i(context.getString(R.string.installer_service_available))
|
if (isAlreadyQueued(packageName)) {
|
||||||
|
if (::serviceConnection.isInitialized) {
|
||||||
val callback = object : IPrivilegedCallback.Stub() {
|
context.unbindService(serviceConnection)
|
||||||
|
}
|
||||||
override fun handleResult(packageName: String, returnCode: Int) {
|
readyWithAction.set(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
AuroraApplication.enqueuedInstalls.add(packageName)
|
||||||
|
val service = IPrivilegedService.Stub.asInterface(binder)
|
||||||
|
|
||||||
override fun handleResultX(
|
if (service.hasPrivilegedPermissions()) {
|
||||||
packageName: String,
|
Log.i(context.getString(R.string.installer_service_available))
|
||||||
returnCode: Int,
|
|
||||||
extra: String?
|
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)
|
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 {
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
service.installSplitPackageX(
|
|
||||||
packageName,
|
|
||||||
uriList,
|
|
||||||
ACTION_INSTALL_REPLACE_EXISTING,
|
|
||||||
BuildConfig.APPLICATION_ID,
|
|
||||||
callback
|
|
||||||
)
|
|
||||||
} catch (e: RemoteException) {
|
|
||||||
removeFromInstallQueue(packageName)
|
removeFromInstallQueue(packageName)
|
||||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
readyWithAction.set(true)
|
||||||
|
Log.e("Disconnected from Aurora Services")
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
postError(
|
|
||||||
packageName,
|
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
|
||||||
context.getString(R.string.installer_status_failure),
|
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
|
||||||
context.getString(R.string.installer_service_misconfigured)
|
|
||||||
|
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) {
|
postError(packageName, error, extra)
|
||||||
removeFromInstallQueue(packageName)
|
}
|
||||||
Log.e("Disconnected from Aurora Services")
|
|
||||||
}
|
}
|
||||||
|
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?) {
|
private fun handleCallback(packageName: String, returnCode: Int, extra: String?) {
|
||||||
Log.i("Services Callback : $packageName $returnCode $extra")
|
Log.i("Services Callback : $packageName $returnCode $extra")
|
||||||
|
|
||||||
when (returnCode) {
|
try {
|
||||||
PackageInstaller.STATUS_SUCCESS -> {
|
when (returnCode) {
|
||||||
EventBus.getDefault().post(
|
PackageInstaller.STATUS_SUCCESS -> {
|
||||||
InstallerEvent.Success(
|
EventBus.getDefault().post(
|
||||||
packageName,
|
InstallerEvent.Success(
|
||||||
context.getString(R.string.installer_status_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.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.aurora.store.BuildConfig
|
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.InstallEvent
|
||||||
import com.aurora.store.data.event.BusEvent.UninstallEvent
|
import com.aurora.store.data.event.BusEvent.UninstallEvent
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
@@ -49,7 +50,7 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Clear installation queue
|
//Clear installation queue
|
||||||
AppInstaller(context)
|
AppInstaller.getInstance(context)
|
||||||
.getPreferredInstaller()
|
.getPreferredInstaller()
|
||||||
.removeFromInstallQueue(packageName)
|
.removeFromInstallQueue(packageName)
|
||||||
|
|
||||||
@@ -72,7 +73,10 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
|||||||
private fun clearNotification(context: Context, packageName: String) {
|
private fun clearNotification(context: Context, packageName: String) {
|
||||||
val notificationManager = context.applicationContext
|
val notificationManager = context.applicationContext
|
||||||
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
.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) {
|
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.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.ArrayMap
|
import android.util.ArrayMap
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
@@ -34,6 +35,7 @@ import com.aurora.extensions.isLAndAbove
|
|||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
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.event.InstallerEvent
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.receiver.DownloadCancelReceiver
|
import com.aurora.store.data.receiver.DownloadCancelReceiver
|
||||||
@@ -78,10 +80,6 @@ class NotificationService : Service() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
|
||||||
return START_NOT_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
@@ -137,6 +135,10 @@ class NotificationService : Service() {
|
|||||||
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
showNotification(groupId, download, fetchGroup)
|
showNotification(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
|
showNotification(groupId, download, fetchGroup)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch.addListener(fetchListener)
|
fetch.addListener(fetchListener)
|
||||||
@@ -159,6 +161,13 @@ class NotificationService : Service() {
|
|||||||
NotificationManager.IMPORTANCE_MIN
|
NotificationManager.IMPORTANCE_MIN
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
channels.add(
|
||||||
|
NotificationChannel(
|
||||||
|
Constants.NOTIFICATION_CHANNEL_UPDATER_SERVICE,
|
||||||
|
getString(R.string.notification_channel_updater_service),
|
||||||
|
NotificationManager.IMPORTANCE_MIN
|
||||||
|
)
|
||||||
|
)
|
||||||
notificationManager.createNotificationChannels(channels)
|
notificationManager.createNotificationChannels(channels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,6 +193,11 @@ class NotificationService : Service() {
|
|||||||
if (app == null)
|
if (app == null)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (status == Status.DELETED) {
|
||||||
|
notificationManager.cancel(app.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
|
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
|
||||||
builder.setContentTitle(app.displayName)
|
builder.setContentTitle(app.displayName)
|
||||||
builder.setSmallIcon(R.drawable.ic_notification_outlined)
|
builder.setSmallIcon(R.drawable.ic_notification_outlined)
|
||||||
@@ -201,7 +215,8 @@ class NotificationService : Service() {
|
|||||||
builder.setContentText(getString(R.string.download_canceled))
|
builder.setContentText(getString(R.string.download_canceled))
|
||||||
builder.color = Color.RED
|
builder.color = Color.RED
|
||||||
}
|
}
|
||||||
Status.FAILED -> {
|
Status.FAILED ->
|
||||||
|
{
|
||||||
builder.setSmallIcon(R.drawable.ic_download_fail)
|
builder.setSmallIcon(R.drawable.ic_download_fail)
|
||||||
builder.setContentText(getString(R.string.download_failed))
|
builder.setContentText(getString(R.string.download_failed))
|
||||||
builder.color = Color.RED
|
builder.color = Color.RED
|
||||||
@@ -362,12 +377,26 @@ class NotificationService : Service() {
|
|||||||
fun onEventMainThread(event: Any) {
|
fun onEventMainThread(event: Any) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is InstallerEvent.Success -> {
|
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)
|
if (app != null)
|
||||||
notifyInstallationStatus(app, event.extra)
|
notifyInstallationStatus(app, event.extra)
|
||||||
}
|
}
|
||||||
is InstallerEvent.Failed -> {
|
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)
|
if (app != null)
|
||||||
notifyInstallationStatus(app, event.error)
|
notifyInstallationStatus(app, event.error)
|
||||||
}
|
}
|
||||||
@@ -379,7 +408,7 @@ class NotificationService : Service() {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun install(packageName: String, files: List<Download>) {
|
private fun install(packageName: String, files: List<Download>) {
|
||||||
AppInstaller(this)
|
AppInstaller.getInstance(this)
|
||||||
.getPreferredInstaller()
|
.getPreferredInstaller()
|
||||||
.install(
|
.install(
|
||||||
packageName,
|
packageName,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.aurora.store.BuildConfig
|
|||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.DownloadManager
|
||||||
import com.aurora.store.data.downloader.RequestBuilder.buildRequest
|
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.installer.NativeInstaller
|
||||||
import com.aurora.store.data.model.SelfUpdate
|
import com.aurora.store.data.model.SelfUpdate
|
||||||
import com.aurora.store.util.CertUtil.isFDroidApp
|
import com.aurora.store.util.CertUtil.isFDroidApp
|
||||||
@@ -135,14 +136,14 @@ class SelfUpdateService : Service() {
|
|||||||
groupId: Int, download: Download, error: Error,
|
groupId: Int, download: Download, error: Error,
|
||||||
throwable: Throwable?, fetchGroup: FetchGroup
|
throwable: Throwable?, fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@SelfUpdateService)) {
|
||||||
Log.e("Error self-updating ${app.displayName}")
|
Log.e("Error self-updating ${app.displayName}")
|
||||||
destroyService()
|
destroyService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
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}")
|
Log.d("Calling installer ${app.displayName}")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -163,7 +164,7 @@ class SelfUpdateService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
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}")
|
Log.d("Self-update cancelled ${app.displayName}")
|
||||||
destroyService()
|
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
|
import com.aurora.gplayapi.data.models.File
|
||||||
|
|
||||||
fun Context.getInternalBaseDirectory(): String {
|
fun Context.getInternalBaseDirectory(): String {
|
||||||
return filesDir.path
|
return (getExternalFilesDir(null) ?: filesDir).path
|
||||||
}
|
}
|
||||||
|
|
||||||
object PathUtil {
|
object PathUtil {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import android.content.Context
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.gson.Gson
|
||||||
|
|
||||||
|
|
||||||
object Preferences {
|
object Preferences {
|
||||||
|
|
||||||
@@ -57,9 +59,15 @@ object Preferences {
|
|||||||
|
|
||||||
const val PREFERENCE_UPDATES_EXTENDED = "PREFERENCE_UPDATES_EXTENDED"
|
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 {
|
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) {
|
fun putString(context: Context, key: String, value: String) {
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ package com.aurora.store.view.ui.details
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -32,20 +35,19 @@ import com.aurora.Constants
|
|||||||
import com.aurora.extensions.*
|
import com.aurora.extensions.*
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
import com.aurora.gplayapi.data.models.AuthData
|
||||||
import com.aurora.gplayapi.data.models.File
|
|
||||||
import com.aurora.gplayapi.exceptions.ApiException
|
|
||||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
|
||||||
import com.aurora.store.MainActivity
|
import com.aurora.store.MainActivity
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.State
|
import com.aurora.store.State
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
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.BusEvent
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.network.HttpClient
|
import com.aurora.store.data.network.HttpClient
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
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.databinding.ActivityDetailsBinding
|
||||||
import com.aurora.store.util.*
|
import com.aurora.store.util.*
|
||||||
import com.aurora.store.view.ui.downloads.DownloadActivity
|
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.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class AppDetailsActivity : BaseDetailsActivity() {
|
class AppDetailsActivity : BaseDetailsActivity() {
|
||||||
|
|
||||||
@@ -73,9 +74,39 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
|
|
||||||
private lateinit var authData: AuthData
|
private lateinit var authData: AuthData
|
||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
private lateinit var downloadManager: DownloadManager
|
private var fetch: Fetch? = null
|
||||||
private lateinit var fetch: Fetch
|
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 fetchGroupListener: FetchGroupListener
|
||||||
|
private lateinit var appMetadataListener: AppMetadataStatusListener
|
||||||
private lateinit var completionMarker: java.io.File
|
private lateinit var completionMarker: java.io.File
|
||||||
private lateinit var inProgressMarker: java.io.File
|
private lateinit var inProgressMarker: java.io.File
|
||||||
|
|
||||||
@@ -83,6 +114,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
private var isNone = false
|
private var isNone = false
|
||||||
private var status = Status.NONE
|
private var status = Status.NONE
|
||||||
private var isInstalled: Boolean = false
|
private var isInstalled: Boolean = false
|
||||||
|
private var isUpdatable: Boolean = false
|
||||||
private var autoDownload: Boolean = false
|
private var autoDownload: Boolean = false
|
||||||
private var downloadOnly: Boolean = false
|
private var downloadOnly: Boolean = false
|
||||||
|
|
||||||
@@ -165,9 +197,8 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
if (!isLAndAbove()) {
|
getUpdateServiceInstance()
|
||||||
checkAndSetupInstall()
|
checkAndSetupInstall()
|
||||||
}
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,11 +237,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var uninstallActionEnabled = false
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_details, menu)
|
menuInflater.inflate(R.menu.menu_details, menu)
|
||||||
if (::app.isInitialized) {
|
if (::app.isInitialized) {
|
||||||
val installed = PackageUtil.isInstalled(this, app.packageName)
|
val installed = PackageUtil.isInstalled(this, app.packageName)
|
||||||
menu?.findItem(R.id.action_uninstall)?.isVisible = installed
|
menu?.findItem(R.id.action_uninstall)?.isVisible = installed
|
||||||
|
uninstallActionEnabled = installed
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -305,7 +339,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
showDialog(R.string.title_installer, R.string.dialog_desc_native_split)
|
showDialog(R.string.title_installer, R.string.dialog_desc_native_split)
|
||||||
} else {
|
} else {
|
||||||
task {
|
task {
|
||||||
AppInstaller(this)
|
AppInstaller.getInstance(this)
|
||||||
.getPreferredInstaller()
|
.getPreferredInstaller()
|
||||||
.install(
|
.install(
|
||||||
app.packageName,
|
app.packageName,
|
||||||
@@ -324,7 +358,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
private fun uninstallApp() {
|
private fun uninstallApp() {
|
||||||
task {
|
task {
|
||||||
AppInstaller(this)
|
AppInstaller.getInstance(this)
|
||||||
.getPreferredInstaller()
|
.getPreferredInstaller()
|
||||||
.uninstall(app.packageName)
|
.uninstall(app.packageName)
|
||||||
}
|
}
|
||||||
@@ -427,14 +461,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
private fun startDownload() {
|
private fun startDownload() {
|
||||||
when (status) {
|
when (status) {
|
||||||
Status.PAUSED -> {
|
Status.PAUSED -> {
|
||||||
fetch.resumeGroup(app.id)
|
fetch?.resumeGroup(app.getGroupId(this@AppDetailsActivity))
|
||||||
}
|
}
|
||||||
Status.DOWNLOADING -> {
|
Status.DOWNLOADING -> {
|
||||||
flip(1)
|
flip(1)
|
||||||
toast("Already downloading")
|
toast("Already downloading")
|
||||||
}
|
}
|
||||||
Status.COMPLETED -> {
|
Status.COMPLETED -> {
|
||||||
fetch.getFetchGroup(app.id) {
|
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
|
||||||
verifyAndInstall(it.downloads)
|
verifyAndInstall(it.downloads)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,92 +478,25 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val listOfActionsWhenServiceAttaches = ArrayList<Runnable>()
|
||||||
|
|
||||||
private fun purchase() {
|
private fun purchase() {
|
||||||
|
bottomSheetBehavior.isHideable = false
|
||||||
|
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
updateActionState(State.PROGRESS)
|
updateActionState(State.PROGRESS)
|
||||||
|
|
||||||
task {
|
runWithPermissions(
|
||||||
val authData = AuthProvider
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
.with(this)
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
.getAuthData()
|
) {
|
||||||
|
if (updateService == null) {
|
||||||
PurchaseHelper(authData)
|
listOfActionsWhenServiceAttaches.add({
|
||||||
.using(HttpClient.getPreferredClient())
|
updateService?.updateApp(app, removeExisiting = true)
|
||||||
.purchase(app.packageName, app.versionCode, app.offerType)
|
})
|
||||||
} successUi { files ->
|
getUpdateServiceInstance()
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
Log.e("Failed to download : ${app.displayName}")
|
updateService?.updateApp(app, removeExisiting = true)
|
||||||
updateActionState(State.IDLE)
|
|
||||||
}
|
}
|
||||||
} 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
|
else
|
||||||
0
|
0
|
||||||
|
|
||||||
|
if (progress == 100) {
|
||||||
|
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
B.layoutDetailsInstall.apply {
|
B.layoutDetailsInstall.apply {
|
||||||
txtProgressPercent.text = ("${progress}%")
|
txtProgressPercent.text = ("${progress}%")
|
||||||
|
|
||||||
@@ -585,7 +556,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
|
|
||||||
B.layoutDetailsInstall.btnDownload.let { btn ->
|
B.layoutDetailsInstall.btnDownload.let { btn ->
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
val isUpdatable = PackageUtil.isUpdatable(
|
isUpdatable = PackageUtil.isUpdatable(
|
||||||
this,
|
this,
|
||||||
app.packageName,
|
app.packageName,
|
||||||
app.versionCode.toLong()
|
app.versionCode.toLong()
|
||||||
@@ -603,6 +574,9 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
btn.setText(R.string.action_open)
|
btn.setText(R.string.action_open)
|
||||||
btn.addOnClickListener { openApp() }
|
btn.addOnClickListener { openApp() }
|
||||||
}
|
}
|
||||||
|
if (!uninstallActionEnabled) {
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (app.isFree) {
|
if (app.isFree) {
|
||||||
btn.setText(R.string.action_install)
|
btn.setText(R.string.action_install)
|
||||||
@@ -618,6 +592,9 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
startDownload()
|
startDownload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (uninstallActionEnabled) {
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -635,16 +612,17 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun attachFetch() {
|
private fun attachFetch() {
|
||||||
downloadManager = DownloadManager.with(this)
|
if (fetch == null) {
|
||||||
fetch = downloadManager.fetch
|
downloadManager = DownloadManager.with(this)
|
||||||
|
fetch = downloadManager!!.fetch
|
||||||
fetch.getFetchGroup(app.id) { fetchGroup: FetchGroup ->
|
}
|
||||||
|
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
|
||||||
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
|
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
|
||||||
status = Status.COMPLETED
|
status = Status.COMPLETED
|
||||||
} else if (downloadManager.isDownloading(fetchGroup)) {
|
} else if (downloadManager?.isDownloading(fetchGroup) == true) {
|
||||||
status = Status.DOWNLOADING
|
status = Status.DOWNLOADING
|
||||||
flip(1)
|
flip(1)
|
||||||
} else if (downloadManager.isCanceled(fetchGroup)) {
|
} else if (downloadManager?.isCanceled(fetchGroup) == true) {
|
||||||
status = Status.CANCELLED
|
status = Status.CANCELLED
|
||||||
} else if (fetchGroup.pausedDownloads.isNotEmpty()) {
|
} else if (fetchGroup.pausedDownloads.isNotEmpty()) {
|
||||||
status = Status.PAUSED
|
status = Status.PAUSED
|
||||||
@@ -655,6 +633,12 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
|
|
||||||
fetchGroupListener = object : AbstractFetchGroupListener() {
|
fetchGroupListener = object : AbstractFetchGroupListener() {
|
||||||
|
|
||||||
|
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
|
status = download.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStarted(
|
override fun onStarted(
|
||||||
groupId: Int,
|
groupId: Int,
|
||||||
download: Download,
|
download: Download,
|
||||||
@@ -662,7 +646,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
totalBlocks: Int,
|
totalBlocks: Int,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
status = download.status
|
status = download.status
|
||||||
flip(1)
|
flip(1)
|
||||||
|
|
||||||
@@ -680,7 +664,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
status = download.status
|
status = download.status
|
||||||
flip(1)
|
flip(1)
|
||||||
FileUtils.touch(inProgressMarker)
|
FileUtils.touch(inProgressMarker)
|
||||||
@@ -688,7 +672,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
status = download.status
|
status = download.status
|
||||||
flip(0)
|
flip(0)
|
||||||
}
|
}
|
||||||
@@ -701,7 +685,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
downloadedBytesPerSecond: Long,
|
downloadedBytesPerSecond: Long,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond)
|
updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond)
|
||||||
Log.i(
|
Log.i(
|
||||||
"${app.displayName} : ${download.file} -> Progress : %d",
|
"${app.displayName} : ${download.file} -> Progress : %d",
|
||||||
@@ -711,22 +695,21 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
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
|
status = download.status
|
||||||
flip(0)
|
flip(0)
|
||||||
updateProgress(fetchGroup, -1, -1)
|
updateProgress(fetchGroup, -1, -1)
|
||||||
inProgressMarker.delete()
|
|
||||||
completionMarker.createNewFile()
|
|
||||||
try {
|
try {
|
||||||
verifyAndInstall(fetchGroup.downloads)
|
inProgressMarker.delete()
|
||||||
} catch (e: Exception) {
|
completionMarker.createNewFile()
|
||||||
Log.e(e.stackTraceToString())
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
status = download.status
|
status = download.status
|
||||||
flip(0)
|
flip(0)
|
||||||
inProgressMarker.delete()
|
inProgressMarker.delete()
|
||||||
@@ -740,7 +723,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
throwable: Throwable?,
|
throwable: Throwable?,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
if (groupId == app.id) {
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
status = download.status
|
status = download.status
|
||||||
flip(0)
|
flip(0)
|
||||||
inProgressMarker.delete()
|
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 {
|
B.layoutDetailsInstall.imgCancel.setOnClickListener {
|
||||||
fetch.cancelGroup(
|
fetch?.cancelGroup(
|
||||||
app.id
|
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() {
|
private fun attachBottomSheet() {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.aurora.store.R
|
|||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.DownloadManager
|
||||||
import com.aurora.store.data.model.DownloadFile
|
import com.aurora.store.data.model.DownloadFile
|
||||||
import com.aurora.store.databinding.ActivityDownloadBinding
|
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.DownloadViewModel_
|
||||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||||
import com.aurora.store.view.ui.commons.BaseActivity
|
import com.aurora.store.view.ui.commons.BaseActivity
|
||||||
@@ -156,10 +157,12 @@ class DownloadActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
R.id.action_clear_completed -> {
|
R.id.action_clear_completed -> {
|
||||||
fetch.removeAllWithStatus(Status.COMPLETED)
|
fetch.removeAllWithStatus(Status.COMPLETED)
|
||||||
|
Preferences.getPrefs(this).edit().remove(Preferences.PREFERENCE_UNIQUE_GROUP_IDS).apply()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_force_clear_all -> {
|
R.id.action_force_clear_all -> {
|
||||||
fetch.deleteAll()
|
fetch.deleteAll()
|
||||||
|
Preferences.getPrefs(this).edit().remove(Preferences.PREFERENCE_UNIQUE_GROUP_IDS).apply()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,21 +113,8 @@ class OnboardingActivity : BaseActivity() {
|
|||||||
B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
B.btnBackward.isEnabled = position != 0
|
lastPosition = position
|
||||||
if (position == 4) {
|
refreshButtonState()
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -145,6 +132,32 @@ class OnboardingActivity : BaseActivity() {
|
|||||||
B.viewpager2.setCurrentItem(B.viewpager2.currentItem - 1, true)
|
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() {
|
override fun onBackPressed() {
|
||||||
if (B.viewpager2.currentItem == 0) {
|
if (B.viewpager2.currentItem == 0) {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
|
|||||||
@@ -91,6 +91,21 @@ class PermissionsFragment : BaseFragment() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
B.epoxyRecycler.withModels {
|
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)
|
setFilterDuplicates(true)
|
||||||
installerList.forEach {
|
installerList.forEach {
|
||||||
add(
|
add(
|
||||||
@@ -99,12 +114,9 @@ class PermissionsFragment : BaseFragment() {
|
|||||||
.permission(it)
|
.permission(it)
|
||||||
.isGranted(
|
.isGranted(
|
||||||
when (it.id) {
|
when (it.id) {
|
||||||
0 -> ActivityCompat.checkSelfPermission(
|
0 -> writeExternalStorage
|
||||||
requireContext(),
|
1 -> storageManager
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
2 -> canInstallPackages
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
1 -> if (isRAndAbove()) Environment.isExternalStorageManager() else true
|
|
||||||
2 -> if (isOAndAbove()) requireContext().packageManager.canRequestPackageInstalls() else true
|
|
||||||
else -> false
|
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 -> {
|
R.id.action_uninstall -> {
|
||||||
task {
|
task {
|
||||||
AppInstaller(requireContext())
|
AppInstaller.getInstance(requireContext())
|
||||||
.getPreferredInstaller().uninstall(app.packageName)
|
.getPreferredInstaller().uninstall(app.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,24 +19,24 @@
|
|||||||
|
|
||||||
package com.aurora.store.view.ui.updates
|
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.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
import com.aurora.extensions.stackTraceToString
|
import com.aurora.extensions.stackTraceToString
|
||||||
import com.aurora.extensions.toast
|
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.State
|
import com.aurora.store.State
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.getGroupId
|
||||||
import com.aurora.store.data.downloader.RequestBuilder
|
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.model.UpdateFile
|
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.databinding.FragmentUpdatesBinding
|
||||||
import com.aurora.store.util.Log
|
import com.aurora.store.util.Log
|
||||||
import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_
|
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.aurora.store.viewmodel.all.UpdatesViewModel
|
||||||
import com.tonyodev.fetch2.AbstractFetchGroupListener
|
import com.tonyodev.fetch2.AbstractFetchGroupListener
|
||||||
import com.tonyodev.fetch2.Download
|
import com.tonyodev.fetch2.Download
|
||||||
import com.tonyodev.fetch2.Fetch
|
|
||||||
import com.tonyodev.fetch2.FetchGroup
|
import com.tonyodev.fetch2.FetchGroup
|
||||||
import nl.komponents.kovenant.task
|
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.apache.commons.io.FileUtils
|
||||||
|
|
||||||
class UpdatesFragment : BaseFragment() {
|
class UpdatesFragment : BaseFragment() {
|
||||||
@@ -60,11 +57,28 @@ class UpdatesFragment : BaseFragment() {
|
|||||||
private lateinit var B: FragmentUpdatesBinding
|
private lateinit var B: FragmentUpdatesBinding
|
||||||
private lateinit var VM: UpdatesViewModel
|
private lateinit var VM: UpdatesViewModel
|
||||||
|
|
||||||
private lateinit var authData: AuthData
|
val listOfActionsWhenServiceAttaches = ArrayList<Runnable>()
|
||||||
private lateinit var purchaseHelper: PurchaseHelper
|
|
||||||
|
|
||||||
private lateinit var fetch: Fetch
|
|
||||||
private lateinit var fetchListener: AbstractFetchGroupListener
|
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()
|
private var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
|
||||||
|
|
||||||
@@ -83,10 +97,7 @@ class UpdatesFragment : BaseFragment() {
|
|||||||
|
|
||||||
VM = ViewModelProvider(requireActivity()).get(UpdatesViewModel::class.java)
|
VM = ViewModelProvider(requireActivity()).get(UpdatesViewModel::class.java)
|
||||||
|
|
||||||
authData = AuthProvider.with(requireContext()).getAuthData()
|
|
||||||
purchaseHelper = PurchaseHelper(authData)
|
|
||||||
|
|
||||||
fetch = DownloadManager.with(requireContext()).fetch
|
|
||||||
fetchListener = object : AbstractFetchGroupListener() {
|
fetchListener = object : AbstractFetchGroupListener() {
|
||||||
|
|
||||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
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) {
|
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
if (fetchGroup.groupDownloadProgress == 100) {
|
if (fetchGroup.groupDownloadProgress == 100) {
|
||||||
VM.updateDownload(groupId, fetchGroup, isComplete = true)
|
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
|
return B.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
getUpdateServiceInstance()
|
||||||
super.onResume()
|
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() {
|
override fun onDestroy() {
|
||||||
if (::fetch.isInitialized && ::fetchListener.isInitialized) {
|
|
||||||
fetch.removeListener(fetchListener)
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
if (updateService != null) {
|
||||||
|
updateService = null
|
||||||
|
attachToServiceCalled = false
|
||||||
|
requireContext().unbindService(serviceConnection)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
VM.liveUpdateData.observe(viewLifecycleOwner, {
|
VM.liveUpdateData.observe(viewLifecycleOwner) {
|
||||||
updateFileMap = it
|
updateFileMap = it
|
||||||
updateController(updateFileMap)
|
updateController(updateFileMap)
|
||||||
B.swipeRefreshLayout.isRefreshing = false
|
B.swipeRefreshLayout.isRefreshing = false
|
||||||
})
|
updateService?.liveUpdateData?.postValue(updateFileMap)
|
||||||
|
}
|
||||||
|
|
||||||
B.swipeRefreshLayout.setOnRefreshListener {
|
B.swipeRefreshLayout.setOnRefreshListener {
|
||||||
VM.observe()
|
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) {
|
private fun updateSingle(app: App) {
|
||||||
|
runInService {
|
||||||
|
VM.updateState(app.getGroupId(requireContext()), State.QUEUED)
|
||||||
|
|
||||||
VM.updateState(app.id, State.QUEUED)
|
updateService?.updateApp(app)
|
||||||
|
|
||||||
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}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelSingle(app: App) {
|
private fun cancelSingle(app: App) {
|
||||||
fetch.cancelGroup(app.id)
|
runInService {
|
||||||
|
updateService?.fetch?.cancelGroup(app.getGroupId(requireContext()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAll() {
|
private fun updateAll() {
|
||||||
updateFileMap.values.forEach { updateSingle(it.app) }
|
runInService {
|
||||||
VM.updateAllEnqueued = true
|
updateService?.updateAll(updateFileMap)
|
||||||
|
VM.updateAllEnqueued = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelAll() {
|
private fun cancelAll() {
|
||||||
VM.updateAllEnqueued = false
|
runInService {
|
||||||
updateFileMap.values.forEach { fetch.cancelGroup(it.app.id) }
|
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
|
@Synchronized
|
||||||
@@ -272,7 +302,7 @@ class UpdatesFragment : BaseFragment() {
|
|||||||
|
|
||||||
if (filesExist) {
|
if (filesExist) {
|
||||||
task {
|
task {
|
||||||
AppInstaller(requireContext())
|
AppInstaller.getInstance(requireContext())
|
||||||
.getPreferredInstaller()
|
.getPreferredInstaller()
|
||||||
.install(
|
.install(
|
||||||
packageName,
|
packageName,
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.State
|
import com.aurora.store.State
|
||||||
import com.aurora.store.data.RequestState
|
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.BusEvent
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.model.UpdateFile
|
import com.aurora.store.data.model.UpdateFile
|
||||||
@@ -72,7 +74,7 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
|||||||
updateFileMap.clear()
|
updateFileMap.clear()
|
||||||
|
|
||||||
apps.forEach {
|
apps.forEach {
|
||||||
updateFileMap[it.id] = UpdateFile(it)
|
updateFileMap[it.getGroupId(getApplication<Application>().applicationContext)] = UpdateFile(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
liveUpdateData.postValue(updateFileMap)
|
liveUpdateData.postValue(updateFileMap)
|
||||||
@@ -110,7 +112,10 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
|||||||
is InstallerEvent.Failed -> {
|
is InstallerEvent.Failed -> {
|
||||||
val packageName = event.packageName
|
val packageName = event.packageName
|
||||||
packageName?.let {
|
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) {
|
private fun updateListAndPost(packageName: String) {
|
||||||
//Remove from map
|
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
|
||||||
updateFileMap.remove(packageName.hashCode())
|
groupIDsOfPackageName.forEach {
|
||||||
|
//Remove from map
|
||||||
|
updateFileMap.remove(it)
|
||||||
|
}
|
||||||
|
|
||||||
//Post new update list
|
//Post new update list
|
||||||
liveUpdateData.postValue(updateFileMap)
|
liveUpdateData.postValue(updateFileMap)
|
||||||
|
|||||||
@@ -366,4 +366,7 @@
|
|||||||
<string name="toast_aas_token_failed">Could not generate AAS Token</string>
|
<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_success">Device config exported</string>
|
||||||
<string name="toast_export_failed">Could not export device config</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>
|
</resources>
|
||||||
Reference in New Issue
Block a user