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 053effdff..63b23f1c0 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 @@ -42,11 +42,12 @@ 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 +import java.util.concurrent.atomic.AtomicInteger 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 @@ -96,190 +97,169 @@ class ServiceInstaller(context: Context) : InstallerBase(context) { } override fun uninstall(packageName: String) { - 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 + val attachedToServiceTimeout = AtomicBoolean(false) + + AuroraApplication.enqueuedInstalls.add(packageName) + + serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + attachedToServiceTimeout.set(true) + 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) { + } - 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) { - - } - - 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 { + override fun handleResultX( + packageName: String, + returnCode: Int, + extra: String? + ) { removeFromInstallQueue(packageName) - readyWithAction = true - postError( - packageName, - context.getString(R.string.installer_status_failure), - context.getString(R.string.installer_service_misconfigured) - ) + handleCallbackUninstall(packageName, returnCode, extra) } } - override fun onServiceDisconnected(name: ComponentName) { + try { + service.deletePackageX( + packageName, + 2, + BuildConfig.APPLICATION_ID, + callback + ) + } catch (e: RemoteException) { + Log.e("Failed to connect Aurora Services") removeFromInstallQueue(packageName) - Log.e("Disconnected from Aurora Services") - readyWithAction = true } + } else { + removeFromInstallQueue(packageName) + postError( + packageName, + context.getString(R.string.installer_status_failure), + context.getString(R.string.installer_service_misconfigured) + ) } - - val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) - intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) - - context.bindService( - intent, - serviceConnection, - Context.BIND_AUTO_CREATE - ) } - while (!readyWithAction) { - Thread.sleep(1000) + + 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 + ) + + Handler(Looper.getMainLooper()).postDelayed({ + if (!attachedToServiceTimeout.get()) { + removeFromInstallQueue(packageName) + } + }, 25 * 1000) } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private fun xInstall(packageName: String, uriList: List, fileList: List) { - 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 + val attachedToServiceTimeout = AtomicBoolean(false) + + AuroraApplication.enqueuedInstalls.add(packageName) + + serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + attachedToServiceTimeout.set(true) + 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) { + } - 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) { - - } - - override fun handleResultX( - packageName: String, - returnCode: Int, - extra: String? - ) { - removeFromInstallQueue(packageName) - readyWithAction = true - handleCallback(packageName, returnCode, extra) - } - } + override fun handleResultX( + packageName: String, + returnCode: Int, + extra: String? + ) { + removeFromInstallQueue(packageName) + handleCallback(packageName, returnCode, extra) + } + } + try { + if (service.isMoreMethodImplemented) { try { - if (service.isMoreMethodImplemented) { - try { - service.installSplitPackageMore( - packageName, - uriList, - ACTION_INSTALL_REPLACE_EXISTING, - BuildConfig.APPLICATION_ID, - callback, - fileList - ) - } catch (e: RemoteException) { - removeFromInstallQueue(packageName) - readyWithAction = true - postError(packageName, e.localizedMessage, e.stackTraceToString()) - } - } 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) - readyWithAction = true - postError(packageName, e.localizedMessage, e.stackTraceToString()) - } + service.installSplitPackageMore( + packageName, + uriList, + ACTION_INSTALL_REPLACE_EXISTING, + BuildConfig.APPLICATION_ID, + callback, + fileList + ) + } catch (e: RemoteException) { + removeFromInstallQueue(packageName) + postError(packageName, e.localizedMessage, e.stackTraceToString()) } } else { - removeFromInstallQueue(packageName) - readyWithAction = true - postError( + throw Exception("New method not implemented") + } + } catch (th: Throwable) { + th.printStackTrace() + try { + service.installSplitPackageX( packageName, - context.getString(R.string.installer_status_failure), - context.getString(R.string.installer_service_misconfigured) + uriList, + ACTION_INSTALL_REPLACE_EXISTING, + BuildConfig.APPLICATION_ID, + callback ) + } catch (e: RemoteException) { + removeFromInstallQueue(packageName) + postError(packageName, e.localizedMessage, e.stackTraceToString()) } } - - override fun onServiceDisconnected(name: ComponentName) { - removeFromInstallQueue(packageName) - readyWithAction = true - Log.e("Disconnected from Aurora Services") - } + } else { + removeFromInstallQueue(packageName) + postError( + packageName, + context.getString(R.string.installer_status_failure), + context.getString(R.string.installer_service_misconfigured) + ) } - - val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) - intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) - - context.bindService( - intent, - serviceConnection, - Context.BIND_AUTO_CREATE - ) } - while (!readyWithAction) { - Thread.sleep(1000) + + override fun onServiceDisconnected(name: ComponentName) { + removeFromInstallQueue(packageName) + Log.e("Disconnected from Aurora Services") } - 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 + ) + Handler(Looper.getMainLooper()).postDelayed({ + if (!attachedToServiceTimeout.get()) { + removeFromInstallQueue(packageName) + } + }, 25 * 1000) } private fun handleCallbackUninstall(packageName: String, returnCode: Int, extra: String?) { 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 211a711a8..27dcb8c26 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 @@ -36,7 +36,7 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.util.* -import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.timerTask class UpdateService: LifecycleService() { @@ -46,9 +46,9 @@ class UpdateService: LifecycleService() { private lateinit var fetchListener: FetchGroupListener private var fetchActiveDownloadObserver = object : FetchObserver { override fun onChanged(data: Boolean, reason: Reason) { - if (!data && installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { + if (!data && isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { Handler(Looper.getMainLooper()).postDelayed ({ - if (installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { + if (isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { stopSelf() } }, 5 * 1000) @@ -73,7 +73,97 @@ class UpdateService: LifecycleService() { } } - private var installing = CopyOnWriteArrayList() + // HashMap> + private var downloadsInCompletedGroup = HashMap>() + + private var installing = HashSet() + 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 @@ -130,7 +220,7 @@ class UpdateService: LifecycleService() { it.onAdded(groupId, download, fetchGroup) } if (download.tag != null) { - installing.remove(download.tag) + removeFromInstalling(download.tag, runFromCurrentThread = true) } if (!hasActiveDownloadObserver) { hasActiveDownloadObserver = true @@ -243,7 +333,18 @@ class UpdateService: LifecycleService() { if (fetchListeners.isEmpty()) { fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true) } - if (fetchGroup.groupDownloadProgress == 100) { + 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) @@ -251,6 +352,9 @@ class UpdateService: LifecycleService() { 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) }*/ @@ -369,7 +473,7 @@ class UpdateService: LifecycleService() { } fun updateApp(app: App, removeExisiting: Boolean = false) { - installing.add(app.packageName) + putInInstalling(app.packageName) task { val files = purchaseHelper.purchase( app.packageName, @@ -390,7 +494,7 @@ class UpdateService: LifecycleService() { Log.i("Updating ${app.displayName}") } } else { - installing.remove(app.packageName) + removeFromInstalling(app.packageName) Log.e("Failed to download : ${app.displayName}") appMetadataListeners.forEach { it.onAppMetadataStatusError(getString(R.string.purchase_session_expired), app) @@ -401,7 +505,7 @@ class UpdateService: LifecycleService() { } } } failUi { failException -> - installing.remove(app.packageName) + removeFromInstalling(app.packageName) var reason = "Unknown" when (failException) { @@ -438,9 +542,9 @@ class UpdateService: LifecycleService() { var timer: Timer? = null val timerTaskRun: Runnable = Runnable { Handler(Looper.getMainLooper()).post { - if (installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { + if (isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { fetch.hasActiveDownloads(true) { hasActiveDownloads -> - if (!hasActiveDownloads && installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { + if (!hasActiveDownloads && isEmptyInstalling() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { Handler(Looper.getMainLooper()).post { stopSelf() } @@ -452,7 +556,11 @@ class UpdateService: LifecycleService() { @Synchronized private fun install(packageName: String, files: List?) { - installing.add(packageName) + if (containsInInstalling(packageName)) { + println("Already installing $packageName!") + return + } + putInInstalling(packageName) files?.let { downloads -> var filesExist = true @@ -472,11 +580,15 @@ class UpdateService: LifecycleService() { .map { it.file }.toList() ) } catch (th: Throwable) { + removeFromInstalling(packageName) th.printStackTrace() } }.fail { + removeFromInstalling(packageName) Log.e(it.stackTraceToString()) } + } else { + removeFromInstalling(packageName) } } } @@ -495,7 +607,7 @@ class UpdateService: LifecycleService() { is InstallerEvent.Failed -> event.packageName else -> null }?.run { - installing.remove(this) + removeFromInstalling(this) } synchronized(timerLock) { if (timer != null) { @@ -556,14 +668,15 @@ class UpdateService: LifecycleService() { override fun onUnbind(intent: Intent?): Boolean { fetchListeners.clear() appMetadataListeners.clear() - if (installing.isEmpty()) { - fetch.hasActiveDownloads(true) { hasActiveDownloads -> - if (!hasActiveDownloads && installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) { - Handler(Looper.getMainLooper()).post { - stopSelf() - } - } + synchronized(timerLock) { + if (timer != null) { + timer!!.cancel() + timer = null } + if (timer == null) { + timer = Timer() + } + timer!!.schedule(timerTask { timerTaskRun.run() }, 5 * 1000) } return true } 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 37db9a5ab..6b604198c 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 @@ -77,21 +77,32 @@ class AppDetailsActivity : BaseDetailsActivity() { private var fetch: Fetch? = null private var downloadManager: DownloadManager? = null + private var attachToServiceCalled = false private var updateService: UpdateService? = null - private var pendingAddListener = true + 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) { + if (::fetchGroupListener.isInitialized && ::appMetadataListener.isInitialized && pendingAddListeners) { updateService!!.registerFetchListener(fetchGroupListener) // appMetadataListener needs to be initialized after the fetchGroupListener updateService!!.registerAppMetadataListener(appMetadataListener) - pendingAddListener = false + 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 @@ -103,6 +114,7 @@ class AppDetailsActivity : BaseDetailsActivity() { private var isNone = false private var status = Status.NONE private var isInstalled: Boolean = false + private var isUpdatable: Boolean = false private var autoDownload: Boolean = false private var downloadOnly: Boolean = false @@ -466,6 +478,8 @@ class AppDetailsActivity : BaseDetailsActivity() { } } + val listOfActionsWhenServiceAttaches = ArrayList() + private fun purchase() { bottomSheetBehavior.isHideable = false bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED @@ -475,7 +489,14 @@ class AppDetailsActivity : BaseDetailsActivity() { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) { - updateService?.updateApp(app, removeExisiting = true) + if (updateService == null) { + listOfActionsWhenServiceAttaches.add({ + updateService?.updateApp(app, removeExisiting = true) + }) + getUpdateServiceInstance() + } else { + updateService?.updateApp(app, removeExisiting = true) + } } } @@ -485,9 +506,6 @@ class AppDetailsActivity : BaseDetailsActivity() { downloadedBytesPerSecond: Long ) { runOnUiThread { - if (isInstalled) { - return@runOnUiThread - } val progress = if (fetchGroup.groupDownloadProgress > 0) fetchGroup.groupDownloadProgress else @@ -538,7 +556,7 @@ class AppDetailsActivity : BaseDetailsActivity() { B.layoutDetailsInstall.btnDownload.let { btn -> if (isInstalled) { - val isUpdatable = PackageUtil.isUpdatable( + isUpdatable = PackageUtil.isUpdatable( this, app.packageName, app.versionCode.toLong() @@ -729,16 +747,19 @@ class AppDetailsActivity : BaseDetailsActivity() { app.getGroupId(this@AppDetailsActivity) ) } - if (pendingAddListener && updateService != null) { - pendingAddListener = false + 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) { + if (updateService == null && !attachToServiceCalled) { + attachToServiceCalled = true val intent = Intent(this, UpdateService::class.java) startService(intent) bindService( @@ -752,6 +773,8 @@ class AppDetailsActivity : BaseDetailsActivity() { override fun onPause() { if (updateService != null) { updateService = null + attachToServiceCalled = false + pendingAddListeners = true unbindService(serviceConnection) } super.onPause() @@ -761,6 +784,8 @@ class AppDetailsActivity : BaseDetailsActivity() { super.onDestroy() if (updateService != null) { updateService = null + attachToServiceCalled = false + pendingAddListeners = true unbindService(serviceConnection) } } 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 cf6c1b6ea..630aa4e10 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 @@ -57,15 +57,26 @@ class UpdatesFragment : BaseFragment() { private lateinit var B: FragmentUpdatesBinding private lateinit var VM: UpdatesViewModel + val listOfActionsWhenServiceAttaches = ArrayList() 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 } } @@ -131,6 +142,7 @@ class UpdatesFragment : BaseFragment() { override fun onPause() { if (updateService != null) { updateService = null + attachToServiceCalled = false requireContext().unbindService(serviceConnection) } super.onPause() @@ -140,6 +152,7 @@ class UpdatesFragment : BaseFragment() { super.onDestroy() if (updateService != null) { updateService = null + attachToServiceCalled = false requireContext().unbindService(serviceConnection) } } @@ -228,9 +241,17 @@ class UpdatesFragment : BaseFragment() { private var updateService: UpdateService? = null - private fun updateSingle(app: App) { + fun runInService(runnable: Runnable) { + if (updateService == null) { + listOfActionsWhenServiceAttaches.add(runnable) + getUpdateServiceInstance() + } else { + runnable.run() + } + } - if (updateService != null) { + private fun updateSingle(app: App) { + runInService { VM.updateState(app.getGroupId(requireContext()), State.QUEUED) updateService?.updateApp(app) @@ -238,21 +259,28 @@ class UpdatesFragment : BaseFragment() { } private fun cancelSingle(app: App) { - updateService?.fetch?.cancelGroup(app.getGroupId(requireContext())) + runInService { + updateService?.fetch?.cancelGroup(app.getGroupId(requireContext())) + } } private fun updateAll() { - updateService?.updateAll(updateFileMap) - VM.updateAllEnqueued = true + runInService { + updateService?.updateAll(updateFileMap) + VM.updateAllEnqueued = true + } } private fun cancelAll() { - VM.updateAllEnqueued = false - updateFileMap.values.forEach { updateService?.fetch?.cancelGroup(it.app.getGroupId(requireContext())) } + runInService { + VM.updateAllEnqueued = false + updateFileMap.values.forEach { updateService?.fetch?.cancelGroup(it.app.getGroupId(requireContext())) } + } } fun getUpdateServiceInstance() { - if (updateService == null) { + if (updateService == null && !attachToServiceCalled) { + attachToServiceCalled = true val intent = Intent(requireContext(), UpdateService::class.java) requireContext().startService(intent) requireContext().bindService(