From 4e111fa9f488f287a9bd37581f010dc4bf914227 Mon Sep 17 00:00:00 2001 From: Konstantin Tuev Date: Wed, 9 Jun 2021 23:09:27 +0300 Subject: [PATCH] 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 --- .../store/data/installer/AppInstaller.kt | 41 ++- .../store/data/installer/ServiceInstaller.kt | 244 ++++++++++-------- .../data/receiver/PackageManagerReceiver.kt | 2 +- .../store/data/service/NotificationService.kt | 2 +- .../store/data/service/UpdateService.kt | 234 ++++++++++++++--- .../view/ui/details/AppDetailsActivity.kt | 105 ++++++-- .../store/view/ui/sheets/AppMenuSheet.kt | 2 +- .../store/view/ui/updates/UpdatesFragment.kt | 2 +- 8 files changed, 459 insertions(+), 173 deletions(-) diff --git a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt index d185fd14b..99e5ed78a 100644 --- a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt +++ b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt @@ -26,9 +26,10 @@ import com.aurora.store.R import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID -open class AppInstaller constructor(var context: Context) { +open class AppInstaller private constructor(var context: Context) { companion object { + private var instance: AppInstaller? = null fun getErrorString(context: Context, status: Int): String { return when (status) { PackageInstaller.STATUS_FAILURE_ABORTED -> context.getString(R.string.installer_status_user_action) @@ -40,22 +41,50 @@ open class AppInstaller constructor(var context: Context) { else -> context.getString(R.string.installer_status_failure) } } + fun getInstance(context: Context): AppInstaller { + if (instance == null) { + instance = AppInstaller(context.applicationContext) + } + return instance!! + } } + val choiceAndInstaller = HashMap() + fun getPreferredInstaller(): IInstaller { val prefValue = Preferences.getInteger( context, PREFERENCE_INSTALLER_ID ) + if (choiceAndInstaller.containsKey(prefValue)) { + return choiceAndInstaller[prefValue]!! + } + return when (prefValue) { - 1 -> NativeInstaller(context) - 2 -> RootInstaller(context) - 3 -> ServiceInstaller(context) + 1 -> { + val installer = NativeInstaller(context) + choiceAndInstaller[prefValue] = installer + installer + } + 2 -> { + val installer = RootInstaller(context) + choiceAndInstaller[prefValue] = installer + installer + } + 3 -> { + val installer = ServiceInstaller(context) + choiceAndInstaller[prefValue] = installer + installer + } else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - SessionInstaller(context) + val installer = SessionInstaller(context) + choiceAndInstaller[prefValue] = installer + installer } else { - NativeInstaller(context) + val installer = NativeInstaller(context) + choiceAndInstaller[prefValue] = installer + installer } } } diff --git a/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt index f996fe90a..239da7237 100644 --- a/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt +++ b/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt @@ -25,9 +25,7 @@ import android.content.Intent import android.content.ServiceConnection import android.content.pm.PackageInstaller import android.net.Uri -import android.os.Build -import android.os.IBinder -import android.os.RemoteException +import android.os.* import androidx.annotation.RequiresApi import androidx.core.content.FileProvider import com.aurora.services.IPrivilegedCallback @@ -41,10 +39,14 @@ import com.aurora.store.util.Log import com.aurora.store.util.PackageUtil import org.greenrobot.eventbus.EventBus import java.io.File +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit class ServiceInstaller(context: Context) : InstallerBase(context) { private lateinit var serviceConnection: ServiceConnection + private val executor = ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, LinkedBlockingQueue()) companion object { const val ACTION_INSTALL_REPLACE_EXISTING = 2 @@ -85,134 +87,168 @@ class ServiceInstaller(context: Context) : InstallerBase(context) { } override fun uninstall(packageName: String) { - val serviceConnection = object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, binder: IBinder) { - AuroraApplication.enqueuedInstalls.add(packageName) - val service = IPrivilegedService.Stub.asInterface(binder) - - if (service.hasPrivilegedPermissions()) { - Log.i(context.getString(R.string.installer_service_available)) - - val callback = object : IPrivilegedCallback.Stub() { - - override fun handleResult(packageName: String, returnCode: Int) { - + executor.execute { + var readyWithAction = false + Handler(Looper.getMainLooper()).post { + serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + if (isAlreadyQueued(packageName)) { + if (::serviceConnection.isInitialized) { + context.unbindService(serviceConnection) + } + readyWithAction = true + return } + AuroraApplication.enqueuedInstalls.add(packageName) + val service = IPrivilegedService.Stub.asInterface(binder) - override fun handleResultX( - packageName: String, - returnCode: Int, - extra: String? - ) { + if (service.hasPrivilegedPermissions()) { + Log.i(context.getString(R.string.installer_service_available)) + + val callback = object : IPrivilegedCallback.Stub() { + + override fun handleResult(packageName: String, returnCode: Int) { + + } + + override fun handleResultX( + packageName: String, + returnCode: Int, + extra: String? + ) { + removeFromInstallQueue(packageName) + 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) - handleCallbackUninstall(packageName, returnCode, extra) + readyWithAction = true + postError( + packageName, + context.getString(R.string.installer_status_failure), + context.getString(R.string.installer_service_misconfigured) + ) } } - try { - service.deletePackageX( - packageName, - 2, - BuildConfig.APPLICATION_ID, - callback - ) - } catch (e: RemoteException) { - Log.e("Failed to connect Aurora Services") + override fun onServiceDisconnected(name: ComponentName) { + removeFromInstallQueue(packageName) + Log.e("Disconnected from Aurora Services") + readyWithAction = true } - } else { - postError( - packageName, - context.getString(R.string.installer_status_failure), - context.getString(R.string.installer_service_misconfigured) - ) - removeFromInstallQueue(packageName) } - } - override fun onServiceDisconnected(name: ComponentName) { - removeFromInstallQueue(packageName) - Log.e("Disconnected from Aurora Services") + val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) + intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) + + context.bindService( + intent, + serviceConnection, + Context.BIND_AUTO_CREATE + ) + } + while (!readyWithAction) { + Thread.sleep(1000) } } - - val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) - intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) - - context.bindService( - intent, - serviceConnection, - Context.BIND_AUTO_CREATE - ) } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private fun xInstall(packageName: String, uriList: List) { - serviceConnection = object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, binder: IBinder) { - if (isAlreadyQueued(packageName)) { - if (::serviceConnection.isInitialized) { - context.unbindService(serviceConnection) - } - return - } - AuroraApplication.enqueuedInstalls.add(packageName) - val service = IPrivilegedService.Stub.asInterface(binder) - - if (service.hasPrivilegedPermissions()) { - Log.i(context.getString(R.string.installer_service_available)) - - val callback = object : IPrivilegedCallback.Stub() { - - override fun handleResult(packageName: String, returnCode: Int) { - + executor.execute { + var readyWithAction = false + Handler(Looper.getMainLooper()).post { + serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + if (isAlreadyQueued(packageName)) { + if (::serviceConnection.isInitialized) { + context.unbindService(serviceConnection) + } + readyWithAction = true + return } + AuroraApplication.enqueuedInstalls.add(packageName) + val service = IPrivilegedService.Stub.asInterface(binder) - override fun handleResultX( - packageName: String, - returnCode: Int, - extra: String? - ) { + if (service.hasPrivilegedPermissions()) { + Log.i(context.getString(R.string.installer_service_available)) + + val callback = object : IPrivilegedCallback.Stub() { + + override fun handleResult(packageName: String, returnCode: Int) { + + } + + override fun handleResultX( + packageName: String, + returnCode: Int, + extra: String? + ) { + removeFromInstallQueue(packageName) + 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) - handleCallback(packageName, returnCode, extra) + readyWithAction = true + postError( + packageName, + context.getString(R.string.installer_status_failure), + context.getString(R.string.installer_service_misconfigured) + ) } } - try { - service.installSplitPackageX( - packageName, - uriList, - ACTION_INSTALL_REPLACE_EXISTING, - BuildConfig.APPLICATION_ID, - callback - ) - } catch (e: RemoteException) { + override fun onServiceDisconnected(name: ComponentName) { removeFromInstallQueue(packageName) - postError(packageName, e.localizedMessage, e.stackTraceToString()) + readyWithAction = 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) { - removeFromInstallQueue(packageName) - Log.e("Disconnected from Aurora Services") + val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) + intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) + + context.bindService( + intent, + serviceConnection, + Context.BIND_AUTO_CREATE + ) } + while (!readyWithAction) { + 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?) { diff --git a/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt index 51f3ad1d0..5fe1cfdc0 100644 --- a/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt +++ b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt @@ -50,7 +50,7 @@ open class PackageManagerReceiver : BroadcastReceiver() { } //Clear installation queue - AppInstaller(context) + AppInstaller.getInstance(context) .getPreferredInstaller() .removeFromInstallQueue(packageName) diff --git a/app/src/main/java/com/aurora/store/data/service/NotificationService.kt b/app/src/main/java/com/aurora/store/data/service/NotificationService.kt index 205f6ed03..e1f8ffd02 100644 --- a/app/src/main/java/com/aurora/store/data/service/NotificationService.kt +++ b/app/src/main/java/com/aurora/store/data/service/NotificationService.kt @@ -401,7 +401,7 @@ class NotificationService : Service() { @Synchronized private fun install(packageName: String, files: List) { - AppInstaller(this) + AppInstaller.getInstance(this) .getPreferredInstaller() .install( packageName, diff --git a/app/src/main/java/com/aurora/store/data/service/UpdateService.kt b/app/src/main/java/com/aurora/store/data/service/UpdateService.kt index 216f2dad9..9b9ab47f5 100644 --- a/app/src/main/java/com/aurora/store/data/service/UpdateService.kt +++ b/app/src/main/java/com/aurora/store/data/service/UpdateService.kt @@ -17,32 +17,31 @@ 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.RequestGroupIdBuilder -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.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.then 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.atomic.AtomicBoolean -import kotlin.collections.ArrayList import kotlin.concurrent.timerTask class UpdateService: LifecycleService() { lateinit var fetch: Fetch - private lateinit var fetchListener: AbstractFetchGroupListener + lateinit var downloadManager: DownloadManager + private lateinit var fetchListener: FetchGroupListener private var fetchActiveDownloadObserver = object : FetchObserver { override fun onChanged(data: Boolean, reason: Reason) { if (!data && !installing.get() && listeners.isEmpty()) { @@ -56,7 +55,7 @@ class UpdateService: LifecycleService() { } private var hasActiveDownloadObserver = false - private val listeners: ArrayList = ArrayList() + private val listeners: ArrayList = ArrayList() private val pendingEvents: MutableMap = mutableMapOf() @@ -114,8 +113,9 @@ class UpdateService: LifecycleService() { EventBus.getDefault().register(this) authData = AuthProvider.with(this).getAuthData() purchaseHelper = PurchaseHelper(authData) - fetch = DownloadManager.with(this).fetch - fetchListener = object : AbstractFetchGroupListener() { + downloadManager = DownloadManager.with(this) + fetch = downloadManager.fetch + fetchListener = object : FetchGroupListener { override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) { listeners.forEach { @@ -127,6 +127,12 @@ class UpdateService: LifecycleService() { } } + override fun onAdded(download: Download) { + listeners.forEach { + it.onAdded(download) + } + } + override fun onProgress( groupId: Int, 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, + totalBlocks: Int, + fetchGroup: FetchGroup + ) { + listeners.forEach { + it.onStarted( + groupId, + download, + downloadBlocks, + totalBlocks, + fetchGroup) + } + } + + override fun onStarted(download: Download, downloadBlocks: List, 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) { listeners.forEach { it.onCompleted(groupId, download, fetchGroup) @@ -146,7 +232,7 @@ class UpdateService: LifecycleService() { if (listeners.isEmpty()) { pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true) } - if (fetchGroup.groupDownloadProgress == 100 || fetchGroup.groupDownloadProgress == -1) { + if (fetchGroup.groupDownloadProgress == 100) { Handler(Looper.getMainLooper()).post { try { 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) { listeners.forEach { 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) { listeners.forEach { it.onDeleted(groupId, download, fetchGroup) @@ -176,6 +272,80 @@ class UpdateService: LifecycleService() { 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 -> @@ -210,16 +380,16 @@ class UpdateService: LifecycleService() { } var timer: Timer? = null - val timerTask: TimerTask = timerTask { + val timerTaskRun: Runnable = Runnable { Handler(Looper.getMainLooper()).post { if (!installing.get() && listeners.isEmpty()) { - fetch.hasActiveDownloads(true, { hasActiveDownloads -> + fetch.hasActiveDownloads(true) { hasActiveDownloads -> if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) { Handler(Looper.getMainLooper()).post { stopSelf() } } - }) + } } } } @@ -237,7 +407,7 @@ class UpdateService: LifecycleService() { if (filesExist) { task { try { - val installer = AppInstaller(this) + val installer = AppInstaller.getInstance(this) .getPreferredInstaller() installer.install( packageName, @@ -255,30 +425,24 @@ class UpdateService: LifecycleService() { } } - @Subscribe() - fun onEventMainThreadExec(event: Any) { + var timerLock = Object() + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + fun onEventBackgroundThreadExec(event: Any) { when (event) { - 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.Success, is InstallerEvent.Failed -> { - if (timer != null) { - timer!!.cancel() - timer = null + synchronized(timerLock) { + if (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 -> { @@ -286,7 +450,7 @@ class UpdateService: LifecycleService() { } } - fun registerListener(listener: AbstractFetchGroupListener) { + fun registerListener(listener: FetchGroupListener) { listeners.add(listener) val iterator = pendingEvents.iterator() while (iterator.hasNext()) { diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt index a5e1c977f..3c69194c1 100644 --- a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt @@ -21,9 +21,12 @@ package com.aurora.store.view.ui.details import android.Manifest import android.content.ActivityNotFoundException +import android.content.ComponentName import android.content.Intent +import android.content.ServiceConnection import android.os.Build import android.os.Bundle +import android.os.IBinder import android.view.Menu import android.view.MenuItem import android.view.View @@ -47,6 +50,7 @@ import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.installer.AppInstaller import com.aurora.store.data.network.HttpClient import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.data.service.UpdateService import com.aurora.store.databinding.ActivityDetailsBinding import com.aurora.store.util.* import com.aurora.store.view.ui.downloads.DownloadActivity @@ -74,8 +78,22 @@ class AppDetailsActivity : BaseDetailsActivity() { private lateinit var authData: AuthData private lateinit var app: App - private lateinit var downloadManager: DownloadManager - private lateinit var fetch: Fetch + + private var 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 completionMarker: java.io.File private lateinit var inProgressMarker: java.io.File @@ -166,9 +184,8 @@ class AppDetailsActivity : BaseDetailsActivity() { } override fun onResume() { - if (!isLAndAbove()) { - checkAndSetupInstall() - } + getUpdateServiceInstance() + checkAndSetupInstall() super.onResume() } @@ -207,11 +224,14 @@ class AppDetailsActivity : BaseDetailsActivity() { } } + private var uninstallActionEnabled = false + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_details, menu) if (::app.isInitialized) { val installed = PackageUtil.isInstalled(this, app.packageName) menu?.findItem(R.id.action_uninstall)?.isVisible = installed + uninstallActionEnabled = installed } return true } @@ -306,7 +326,7 @@ class AppDetailsActivity : BaseDetailsActivity() { showDialog(R.string.title_installer, R.string.dialog_desc_native_split) } else { task { - AppInstaller(this) + AppInstaller.getInstance(this) .getPreferredInstaller() .install( app.packageName, @@ -325,7 +345,7 @@ class AppDetailsActivity : BaseDetailsActivity() { @Synchronized private fun uninstallApp() { task { - AppInstaller(this) + AppInstaller.getInstance(this) .getPreferredInstaller() .uninstall(app.packageName) } @@ -428,14 +448,14 @@ class AppDetailsActivity : BaseDetailsActivity() { private fun startDownload() { when (status) { Status.PAUSED -> { - fetch.resumeGroup(app.getGroupId(this@AppDetailsActivity)) + updateService?.fetch?.resumeGroup(app.getGroupId(this@AppDetailsActivity)) } Status.DOWNLOADING -> { flip(1) toast("Already downloading") } Status.COMPLETED -> { - fetch.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { + updateService?.fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { verifyAndInstall(it.downloads) } } @@ -519,10 +539,10 @@ class AppDetailsActivity : BaseDetailsActivity() { if (requestList.isNotEmpty()) { /*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*/ - fetch.enqueue( + updateService?.fetch?.enqueue( requestList ) { status = Status.ADDED @@ -604,6 +624,9 @@ class AppDetailsActivity : BaseDetailsActivity() { btn.setText(R.string.action_open) btn.addOnClickListener { openApp() } } + if (!uninstallActionEnabled) { + invalidateOptionsMenu() + } } else { if (app.isFree) { btn.setText(R.string.action_install) @@ -619,6 +642,9 @@ class AppDetailsActivity : BaseDetailsActivity() { startDownload() } } + if (uninstallActionEnabled) { + invalidateOptionsMenu() + } } } } @@ -636,16 +662,13 @@ class AppDetailsActivity : BaseDetailsActivity() { } private fun attachFetch() { - downloadManager = DownloadManager.with(this) - fetch = downloadManager.fetch - - fetch.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup -> + updateService?.fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup -> if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) { status = Status.COMPLETED - } else if (downloadManager.isDownloading(fetchGroup)) { + } else if (updateService?.downloadManager?.isDownloading(fetchGroup) == true) { status = Status.DOWNLOADING flip(1) - } else if (downloadManager.isCanceled(fetchGroup)) { + } else if (updateService?.downloadManager?.isCanceled(fetchGroup) == true) { status = Status.CANCELLED } else if (fetchGroup.pausedDownloads.isNotEmpty()) { status = Status.PAUSED @@ -716,12 +739,14 @@ class AppDetailsActivity : BaseDetailsActivity() { status = download.status flip(0) updateProgress(fetchGroup, -1, -1) - inProgressMarker.delete() - completionMarker.createNewFile() try { - verifyAndInstall(fetchGroup.downloads) - } catch (e: Exception) { - Log.e(e.stackTraceToString()) + inProgressMarker.delete() + completionMarker.createNewFile() + } catch (ex: Exception) { + ex.printStackTrace() + } + 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 { - fetch.cancelGroup( + updateService?.fetch?.cancelGroup( 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() { diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt index 310e86df9..29a9cf221 100644 --- a/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt @@ -108,7 +108,7 @@ class AppMenuSheet : BaseBottomSheet() { R.id.action_uninstall -> { task { - AppInstaller(requireContext()) + AppInstaller.getInstance(requireContext()) .getPreferredInstaller().uninstall(app.packageName) } } diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt index 407b482ac..196a6f1fc 100644 --- a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -285,7 +285,7 @@ class UpdatesFragment : BaseFragment() { if (filesExist) { task { - AppInstaller(requireContext()) + AppInstaller.getInstance(requireContext()) .getPreferredInstaller() .install( packageName,