Fix AppInstaller spawning useless instances by preserving the original instance of every instantiated installer type (fixes wrongly started or concurrent installs/uninstalls), implement service based install for new downloads (integrated in the UpdateService.kt and the AppDetailsActivity.kt), prevent multiple calls for install by using ThreadPoolExecutor (applied only to ServiceInstaller.kt for now), fix timer issues in the UpdateService.kt

This commit is contained in:
Konstantin Tuev
2021-06-09 23:09:27 +03:00
parent ca4f8d939e
commit 4e111fa9f4
8 changed files with 459 additions and 173 deletions

View File

@@ -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
} }
} }
} }

View File

@@ -25,9 +25,7 @@ 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
@@ -41,10 +39,14 @@ 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
class ServiceInstaller(context: Context) : InstallerBase(context) { class ServiceInstaller(context: Context) : InstallerBase(context) {
private lateinit var serviceConnection: ServiceConnection 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
@@ -85,134 +87,168 @@ 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) { var readyWithAction = false
AuroraApplication.enqueuedInstalls.add(packageName) Handler(Looper.getMainLooper()).post {
val service = IPrivilegedService.Stub.asInterface(binder) serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
if (service.hasPrivilegedPermissions()) { if (isAlreadyQueued(packageName)) {
Log.i(context.getString(R.string.installer_service_available)) if (::serviceConnection.isInitialized) {
context.unbindService(serviceConnection)
val callback = object : IPrivilegedCallback.Stub() { }
readyWithAction = true
override fun handleResult(packageName: String, returnCode: Int) { 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)
readyWithAction = true
handleCallbackUninstall(packageName, returnCode, extra)
}
}
try {
service.deletePackageX(
packageName,
2,
BuildConfig.APPLICATION_ID,
callback
)
} catch (e: RemoteException) {
Log.e("Failed to connect Aurora Services")
removeFromInstallQueue(packageName)
readyWithAction = true
}
} else {
removeFromInstallQueue(packageName) removeFromInstallQueue(packageName)
handleCallbackUninstall(packageName, returnCode, extra) readyWithAction = true
postError(
packageName,
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_misconfigured)
)
} }
} }
try { override fun onServiceDisconnected(name: ComponentName) {
service.deletePackageX( removeFromInstallQueue(packageName)
packageName, Log.e("Disconnected from Aurora Services")
2, readyWithAction = 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)
)
removeFromInstallQueue(packageName)
} }
}
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) {
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>) {
serviceConnection = object : ServiceConnection { executor.execute {
override fun onServiceConnected(name: ComponentName, binder: IBinder) { var readyWithAction = false
if (isAlreadyQueued(packageName)) { Handler(Looper.getMainLooper()).post {
if (::serviceConnection.isInitialized) { serviceConnection = object : ServiceConnection {
context.unbindService(serviceConnection) override fun onServiceConnected(name: ComponentName, binder: IBinder) {
} if (isAlreadyQueued(packageName)) {
return if (::serviceConnection.isInitialized) {
} context.unbindService(serviceConnection)
AuroraApplication.enqueuedInstalls.add(packageName) }
val service = IPrivilegedService.Stub.asInterface(binder) readyWithAction = true
return
if (service.hasPrivilegedPermissions()) {
Log.i(context.getString(R.string.installer_service_available))
val callback = object : IPrivilegedCallback.Stub() {
override fun handleResult(packageName: String, returnCode: Int) {
} }
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)
readyWithAction = true
handleCallback(packageName, returnCode, extra)
}
}
try {
service.installSplitPackageX(
packageName,
uriList,
ACTION_INSTALL_REPLACE_EXISTING,
BuildConfig.APPLICATION_ID,
callback
)
} catch (e: RemoteException) {
removeFromInstallQueue(packageName)
readyWithAction = true
postError(packageName, e.localizedMessage, e.stackTraceToString())
}
} else {
removeFromInstallQueue(packageName) removeFromInstallQueue(packageName)
handleCallback(packageName, returnCode, extra) readyWithAction = true
postError(
packageName,
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_misconfigured)
)
} }
} }
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 = true
Log.e("Disconnected from Aurora Services")
} }
} else {
postError(
packageName,
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_misconfigured)
)
removeFromInstallQueue(packageName)
} }
}
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) {
Thread.sleep(1000)
}
Log.i("Services Callback : install wait done")
} }
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
context.bindService(
intent,
serviceConnection,
Context.BIND_AUTO_CREATE
)
} }
private fun handleCallbackUninstall(packageName: String, returnCode: Int, extra: String?) { private fun handleCallbackUninstall(packageName: String, returnCode: Int, extra: String?) {

View File

@@ -50,7 +50,7 @@ open class PackageManagerReceiver : BroadcastReceiver() {
} }
//Clear installation queue //Clear installation queue
AppInstaller(context) AppInstaller.getInstance(context)
.getPreferredInstaller() .getPreferredInstaller()
.removeFromInstallQueue(packageName) .removeFromInstallQueue(packageName)

View File

@@ -401,7 +401,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,

View File

@@ -17,32 +17,31 @@ import com.aurora.gplayapi.helpers.PurchaseHelper
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 import com.aurora.store.data.downloader.RequestBuilder
import com.aurora.store.data.downloader.RequestGroupIdBuilder
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.event.InstallerEvent
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.providers.AuthProvider
import com.aurora.store.util.Log import com.aurora.store.util.Log
import com.tonyodev.fetch2.* import com.tonyodev.fetch2.*
import com.tonyodev.fetch2core.DownloadBlock
import com.tonyodev.fetch2core.FetchObserver import com.tonyodev.fetch2core.FetchObserver
import com.tonyodev.fetch2core.Reason import com.tonyodev.fetch2core.Reason
import nl.komponents.kovenant.task import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.apache.commons.io.FileUtils 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 java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList
import kotlin.concurrent.timerTask import kotlin.concurrent.timerTask
class UpdateService: LifecycleService() { class UpdateService: LifecycleService() {
lateinit var fetch: Fetch lateinit var fetch: Fetch
private lateinit var fetchListener: AbstractFetchGroupListener lateinit var downloadManager: DownloadManager
private lateinit var fetchListener: FetchGroupListener
private var fetchActiveDownloadObserver = object : FetchObserver<Boolean> { private var fetchActiveDownloadObserver = object : FetchObserver<Boolean> {
override fun onChanged(data: Boolean, reason: Reason) { override fun onChanged(data: Boolean, reason: Reason) {
if (!data && !installing.get() && listeners.isEmpty()) { if (!data && !installing.get() && listeners.isEmpty()) {
@@ -56,7 +55,7 @@ class UpdateService: LifecycleService() {
} }
private var hasActiveDownloadObserver = false private var hasActiveDownloadObserver = false
private val listeners: ArrayList<AbstractFetchGroupListener> = ArrayList() private val listeners: ArrayList<FetchGroupListener> = ArrayList()
private val pendingEvents: MutableMap</*groupId: */Int, AppDownloadStatus> = mutableMapOf() private val pendingEvents: MutableMap</*groupId: */Int, AppDownloadStatus> = mutableMapOf()
@@ -114,8 +113,9 @@ class UpdateService: LifecycleService() {
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
authData = AuthProvider.with(this).getAuthData() authData = AuthProvider.with(this).getAuthData()
purchaseHelper = PurchaseHelper(authData) purchaseHelper = PurchaseHelper(authData)
fetch = DownloadManager.with(this).fetch downloadManager = DownloadManager.with(this)
fetchListener = object : AbstractFetchGroupListener() { fetch = downloadManager.fetch
fetchListener = object : FetchGroupListener {
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) { override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach { listeners.forEach {
@@ -127,6 +127,12 @@ class UpdateService: LifecycleService() {
} }
} }
override fun onAdded(download: Download) {
listeners.forEach {
it.onAdded(download)
}
}
override fun onProgress( override fun onProgress(
groupId: Int, groupId: Int,
download: Download, download: Download,
@@ -139,6 +145,86 @@ class UpdateService: LifecycleService() {
} }
} }
override fun onProgress(download: Download, etaInMilliSeconds: Long, downloadedBytesPerSecond: Long) {
listeners.forEach {
it.onProgress(download, etaInMilliSeconds, downloadedBytesPerSecond)
}
}
override fun onQueued(groupId: Int, download: Download, waitingNetwork: Boolean, fetchGroup: FetchGroup) {
listeners.forEach {
it.onQueued(groupId, download, waitingNetwork, fetchGroup)
}
}
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
listeners.forEach {
it.onQueued(download, waitingOnNetwork)
}
}
override fun onRemoved(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onRemoved(groupId, download, fetchGroup)
}
}
override fun onRemoved(download: Download) {
listeners.forEach {
it.onRemoved(download)
}
}
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onResumed(groupId, download, fetchGroup)
}
}
override fun onResumed(download: Download) {
listeners.forEach {
it.onResumed(download)
}
}
override fun onStarted(
groupId: Int,
download: Download,
downloadBlocks: List<DownloadBlock>,
totalBlocks: Int,
fetchGroup: FetchGroup
) {
listeners.forEach {
it.onStarted(
groupId,
download,
downloadBlocks,
totalBlocks,
fetchGroup)
}
}
override fun onStarted(download: Download, downloadBlocks: List<DownloadBlock>, totalBlocks: Int) {
listeners.forEach {
it.onStarted(
download,
downloadBlocks,
totalBlocks)
}
}
override fun onWaitingNetwork(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onWaitingNetwork(groupId, download, fetchGroup)
}
}
override fun onWaitingNetwork(download: Download) {
listeners.forEach {
it.onWaitingNetwork(download)
}
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach { listeners.forEach {
it.onCompleted(groupId, download, fetchGroup) it.onCompleted(groupId, download, fetchGroup)
@@ -146,7 +232,7 @@ class UpdateService: LifecycleService() {
if (listeners.isEmpty()) { if (listeners.isEmpty()) {
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true) pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true)
} }
if (fetchGroup.groupDownloadProgress == 100 || fetchGroup.groupDownloadProgress == -1) { if (fetchGroup.groupDownloadProgress == 100) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
try { try {
install(download.tag!!, fetchGroup.downloads) install(download.tag!!, fetchGroup.downloads)
@@ -159,6 +245,12 @@ class UpdateService: LifecycleService() {
}*/ }*/
} }
override fun onCompleted(download: Download) {
listeners.forEach {
it.onCompleted(download)
}
}
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) { override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach { listeners.forEach {
it.onCancelled(groupId, download, fetchGroup) it.onCancelled(groupId, download, fetchGroup)
@@ -168,6 +260,10 @@ class UpdateService: LifecycleService() {
} }
} }
override fun onCancelled(download: Download) {
TODO("Not yet implemented")
}
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach { listeners.forEach {
it.onDeleted(groupId, download, fetchGroup) it.onDeleted(groupId, download, fetchGroup)
@@ -176,6 +272,80 @@ class UpdateService: LifecycleService() {
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true) pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
} }
} }
override fun onDeleted(download: Download) {
listeners.forEach {
it.onDeleted(download)
}
}
override fun onDownloadBlockUpdated(
groupId: Int,
download: Download,
downloadBlock: DownloadBlock,
totalBlocks: Int,
fetchGroup: FetchGroup
) {
listeners.forEach {
it.onDownloadBlockUpdated(
groupId,
download,
downloadBlock,
totalBlocks,
fetchGroup
)
}
}
override fun onDownloadBlockUpdated(download: Download, downloadBlock: DownloadBlock, totalBlocks: Int) {
listeners.forEach {
it.onDownloadBlockUpdated(
download,
downloadBlock,
totalBlocks
)
}
}
override fun onError(
groupId: Int,
download: Download,
error: Error,
throwable: Throwable?,
fetchGroup: FetchGroup
) {
listeners.forEach {
it.onError(
groupId,
download,
error,
throwable,
fetchGroup
)
}
}
override fun onError(download: Download, error: Error, throwable: Throwable?) {
listeners.forEach {
it.onError(
download,
error,
throwable
)
}
}
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
listeners.forEach {
it.onPaused(groupId, download, fetchGroup)
}
}
override fun onPaused(download: Download) {
listeners.forEach {
it.onPaused(download)
}
}
} }
/*liveUpdateData.observe(this) { updateData -> /*liveUpdateData.observe(this) { updateData ->
@@ -210,16 +380,16 @@ class UpdateService: LifecycleService() {
} }
var timer: Timer? = null var timer: Timer? = null
val timerTask: TimerTask = timerTask { val timerTaskRun: Runnable = Runnable {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
if (!installing.get() && listeners.isEmpty()) { if (!installing.get() && listeners.isEmpty()) {
fetch.hasActiveDownloads(true, { hasActiveDownloads -> fetch.hasActiveDownloads(true) { hasActiveDownloads ->
if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) { if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
stopSelf() stopSelf()
} }
} }
}) }
} }
} }
} }
@@ -237,7 +407,7 @@ class UpdateService: LifecycleService() {
if (filesExist) { if (filesExist) {
task { task {
try { try {
val installer = AppInstaller(this) val installer = AppInstaller.getInstance(this)
.getPreferredInstaller() .getPreferredInstaller()
installer.install( installer.install(
packageName, packageName,
@@ -255,30 +425,24 @@ class UpdateService: LifecycleService() {
} }
} }
@Subscribe() var timerLock = Object()
fun onEventMainThreadExec(event: Any) {
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onEventBackgroundThreadExec(event: Any) {
when (event) { when (event) {
is InstallerEvent.Success -> { is InstallerEvent.Success,
if (timer != null) {
timer!!.cancel()
timer = null
}
if (timer == null) {
timer = Timer()
}
installing.set(false)
timer!!.schedule(timerTask, 10 * 1000)
}
is InstallerEvent.Failed -> { is InstallerEvent.Failed -> {
if (timer != null) { synchronized(timerLock) {
timer!!.cancel() if (timer != null) {
timer = null timer!!.cancel()
timer = null
}
if (timer == null) {
timer = Timer()
}
installing.set(false)
timer!!.schedule(timerTask { timerTaskRun.run() }, 10 * 1000)
} }
if (timer == null) {
timer = Timer()
}
installing.set(false)
timer!!.schedule(timerTask, 10 * 1000)
} }
else -> { else -> {
@@ -286,7 +450,7 @@ class UpdateService: LifecycleService() {
} }
} }
fun registerListener(listener: AbstractFetchGroupListener) { fun registerListener(listener: FetchGroupListener) {
listeners.add(listener) listeners.add(listener)
val iterator = pendingEvents.iterator() val iterator = pendingEvents.iterator()
while (iterator.hasNext()) { while (iterator.hasNext()) {

View File

@@ -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
@@ -47,6 +50,7 @@ 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.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
@@ -74,8 +78,22 @@ 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 lateinit var fetch: Fetch private var updateService: UpdateService? = null
private var pendingAddListener = true
private var serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
if (::fetchGroupListener.isInitialized) {
updateService!!.registerListener(fetchGroupListener)
pendingAddListener = false
}
}
override fun onServiceDisconnected(name: ComponentName) {
updateService = null
}
}
private lateinit var fetchGroupListener: FetchGroupListener private lateinit var fetchGroupListener: FetchGroupListener
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
@@ -166,9 +184,8 @@ class AppDetailsActivity : BaseDetailsActivity() {
} }
override fun onResume() { override fun onResume() {
if (!isLAndAbove()) { getUpdateServiceInstance()
checkAndSetupInstall() checkAndSetupInstall()
}
super.onResume() super.onResume()
} }
@@ -207,11 +224,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
} }
@@ -306,7 +326,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,
@@ -325,7 +345,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)
} }
@@ -428,14 +448,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
private fun startDownload() { private fun startDownload() {
when (status) { when (status) {
Status.PAUSED -> { Status.PAUSED -> {
fetch.resumeGroup(app.getGroupId(this@AppDetailsActivity)) updateService?.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.getGroupId(this@AppDetailsActivity)) { updateService?.fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
verifyAndInstall(it.downloads) verifyAndInstall(it.downloads)
} }
} }
@@ -519,10 +539,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
if (requestList.isNotEmpty()) { if (requestList.isNotEmpty()) {
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/ /*Remove old fetch group if downloaded earlier, mostly in case of updates*/
fetch.deleteGroup(app.getGroupId(this@AppDetailsActivity)) updateService?.fetch?.deleteGroup(app.getGroupId(this@AppDetailsActivity))
/*Enqueue new fetch group*/ /*Enqueue new fetch group*/
fetch.enqueue( updateService?.fetch?.enqueue(
requestList requestList
) { ) {
status = Status.ADDED status = Status.ADDED
@@ -604,6 +624,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)
@@ -619,6 +642,9 @@ class AppDetailsActivity : BaseDetailsActivity() {
startDownload() startDownload()
} }
} }
if (uninstallActionEnabled) {
invalidateOptionsMenu()
}
} }
} }
} }
@@ -636,16 +662,13 @@ class AppDetailsActivity : BaseDetailsActivity() {
} }
private fun attachFetch() { private fun attachFetch() {
downloadManager = DownloadManager.with(this) updateService?.fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
fetch = downloadManager.fetch
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 (updateService?.downloadManager?.isDownloading(fetchGroup) == true) {
status = Status.DOWNLOADING status = Status.DOWNLOADING
flip(1) flip(1)
} else if (downloadManager.isCanceled(fetchGroup)) { } else if (updateService?.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
@@ -716,12 +739,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
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()
}
runOnUiThread {
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
} }
} }
} }
@@ -749,13 +774,45 @@ class AppDetailsActivity : BaseDetailsActivity() {
} }
} }
fetch.addListener(fetchGroupListener) getUpdateServiceInstance()
B.layoutDetailsInstall.imgCancel.setOnClickListener { B.layoutDetailsInstall.imgCancel.setOnClickListener {
fetch.cancelGroup( updateService?.fetch?.cancelGroup(
app.getGroupId(this@AppDetailsActivity) app.getGroupId(this@AppDetailsActivity)
) )
} }
if (pendingAddListener && updateService != null) {
pendingAddListener = false
updateService!!.registerListener(fetchGroupListener)
}
}
fun getUpdateServiceInstance() {
if (updateService == null) {
val intent = Intent(this, UpdateService::class.java)
startService(intent)
bindService(
intent,
serviceConnection,
0
)
}
}
override fun onPause() {
if (updateService != null) {
updateService = null
unbindService(serviceConnection)
}
super.onPause()
}
override fun onDestroy() {
super.onDestroy()
if (updateService != null) {
updateService = null
unbindService(serviceConnection)
}
} }
private fun attachBottomSheet() { private fun attachBottomSheet() {

View File

@@ -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)
} }
} }

View File

@@ -285,7 +285,7 @@ class UpdatesFragment : BaseFragment() {
if (filesExist) { if (filesExist) {
task { task {
AppInstaller(requireContext()) AppInstaller.getInstance(requireContext())
.getPreferredInstaller() .getPreferredInstaller()
.install( .install(
packageName, packageName,