@@ -43,8 +43,10 @@ import com.looker.droidify.utility.common.log
4343import com.looker.droidify.utility.notifications.createInstallNotification
4444import com.looker.droidify.utility.notifications.installNotification
4545import dagger.hilt.android.AndroidEntryPoint
46+ import kotlinx.coroutines.CompletableDeferred
4647import kotlinx.coroutines.CoroutineScope
4748import kotlinx.coroutines.Job
49+ import kotlinx.coroutines.NonCancellable
4850import kotlinx.coroutines.flow.MutableStateFlow
4951import kotlinx.coroutines.flow.asStateFlow
5052import kotlinx.coroutines.flow.collectLatest
@@ -55,14 +57,17 @@ import kotlinx.coroutines.flow.update
5557import kotlinx.coroutines.launch
5658import kotlinx.coroutines.sync.Mutex
5759import kotlinx.coroutines.sync.withLock
60+ import kotlinx.coroutines.withContext
5861import kotlinx.coroutines.yield
5962import java.io.File
63+ import java.util.concurrent.ConcurrentHashMap
6064import javax.inject.Inject
6165import com.looker.droidify.R.string as stringRes
6266
6367@AndroidEntryPoint
6468class DownloadService : ConnectionService <DownloadService .Binder >() {
6569 companion object {
70+ private const val TAG = " DroidifyUpdateAll"
6671 private const val ACTION_CANCEL = " ${BuildConfig .APPLICATION_ID } .intent.action.CANCEL"
6772 }
6873
@@ -130,9 +135,46 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
130135 private var currentTask: CurrentTask ? = null
131136
132137 private val lock = Mutex ()
138+ private val updateCompletions = ConcurrentHashMap <String , CompletableDeferred <Boolean >>()
133139
134140 inner class Binder : android.os.Binder () {
135141 val downloadState = _downloadState .asStateFlow()
142+
143+ suspend fun enqueueAndAwait (
144+ packageName : String ,
145+ name : String ,
146+ repository : Repository ,
147+ release : Release ,
148+ isUpdate : Boolean = false,
149+ ): Boolean {
150+ val task = Task (
151+ packageName = packageName,
152+ name = name,
153+ release = release,
154+ url = release.getDownloadUrl(repository),
155+ authentication = repository.authentication,
156+ isUpdate = isUpdate,
157+ )
158+ val deferred = CompletableDeferred <Boolean >()
159+ updateCompletions[packageName] = deferred
160+ log(" enqueueAndAwait: $packageName " , TAG )
161+ return try {
162+ if (Cache .getReleaseFile(this @DownloadService, release.cacheFileName).exists()) {
163+ log(" Using cached APK for $packageName (await)" , TAG )
164+ publishSuccess(task)
165+ } else {
166+ enqueueDownload(task)
167+ }
168+ withContext(NonCancellable ) { deferred.await() }
169+ } catch (e: Exception ) {
170+ log(" enqueueAndAwait failed: $packageName — ${e.message} " , TAG , Log .ERROR )
171+ signalUpdateComplete(packageName, false )
172+ false
173+ } finally {
174+ updateCompletions.remove(packageName)
175+ }
176+ }
177+
136178 fun enqueue (
137179 packageName : String ,
138180 name : String ,
@@ -149,11 +191,17 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
149191 isUpdate = isUpdate,
150192 )
151193 if (Cache .getReleaseFile(this @DownloadService, release.cacheFileName).exists()) {
194+ log(" Using cached APK for $packageName " , TAG )
152195 lifecycleScope.launch { publishSuccess(task) }
153196 return
154197 }
155- cancelTasks(packageName)
156- cancelCurrentTask(packageName)
198+ enqueueDownload(task)
199+ }
200+
201+ private fun enqueueDownload (task : Task ) {
202+ log(" Queued download for ${task.packageName} (pending=${tasks.size} )" , TAG )
203+ cancelTasks(task.packageName)
204+ cancelCurrentTask(task.packageName)
157205 notificationManager?.cancel(
158206 task.notificationTag,
159207 Constants .NOTIFICATION_ID_DOWNLOADING ,
@@ -162,7 +210,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
162210 if (currentTask == null ) {
163211 handleDownload()
164212 } else {
165- updateCurrentQueue { add(packageName) }
213+ updateCurrentQueue { add(task. packageName) }
166214 }
167215 }
168216
@@ -298,18 +346,42 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
298346 val currentInstaller = installerType.first()
299347 updateCurrentQueue { add(" " ) }
300348 updateCurrentState(State .Success (task.packageName, task.release))
349+ log(
350+ " Download complete: ${task.packageName} , installer=$currentInstaller , queue=${tasks.size} " ,
351+ TAG ,
352+ )
301353 val autoInstallWithSessionInstaller =
302354 SdkCheck .canAutoInstall(task.release.targetSdkVersion) &&
303355 currentInstaller == InstallerType .SESSION &&
304356 task.isUpdate
305357
306358 showNotificationInstall(task)
307- if (currentInstaller == InstallerType .ROOT ||
359+ val success = if (currentInstaller == InstallerType .ROOT ||
308360 currentInstaller == InstallerType .SHIZUKU ||
361+ currentInstaller == InstallerType .DHIZUKU ||
309362 autoInstallWithSessionInstaller
310363 ) {
311364 val installItem = task.packageName installFrom task.release.cacheFileName
312- installer install installItem
365+ val result = withContext(NonCancellable ) {
366+ installer.installAndAwait(installItem)
367+ }
368+ log(
369+ " Auto-install finished: ${task.packageName} -> $result " ,
370+ TAG ,
371+ if (result == InstallState .Installed ) Log .DEBUG else Log .WARN ,
372+ )
373+ result == InstallState .Installed
374+ } else {
375+ true
376+ }
377+ signalUpdateComplete(task.packageName, success)
378+ }
379+
380+ private fun signalUpdateComplete (packageName : String , success : Boolean ) {
381+ val pending = updateCompletions.remove(packageName)
382+ if (pending != null ) {
383+ log(" Update step complete: $packageName success=$success " , TAG )
384+ pending.complete(success)
313385 }
314386 }
315387
@@ -436,6 +508,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
436508 task : Task ,
437509 target : File ,
438510 ) = launch {
511+ var publishCalled = false
439512 try {
440513 val response = downloader.downloadToFile(
441514 url = task.url,
@@ -458,13 +531,15 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
458531 target.delete()
459532 updateCurrentState(State .Error (task.packageName))
460533 showErrorNotification(task, could_not_validate_FORMAT, result.message)
534+ signalUpdateComplete(task.packageName, false )
461535 return @launch
462536 }
463537 val releaseFile = Cache .getReleaseFile(
464538 this @DownloadService,
465539 task.release.cacheFileName,
466540 )
467541 target.renameTo(releaseFile)
542+ publishCalled = true
468543 publishSuccess(task)
469544 }
470545
@@ -478,8 +553,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
478553 is NetworkResponse .Error .Unknown -> unknown_error_DESC
479554 }
480555 showErrorNotification(task, could_not_download_FORMAT, getString(description))
556+ signalUpdateComplete(task.packageName, false )
481557 }
482558 }
559+ } catch (e: Exception ) {
560+ log(" Download failed: ${task.packageName} — ${e.message} " , TAG , Log .ERROR )
561+ if (! publishCalled) {
562+ signalUpdateComplete(task.packageName, false )
563+ }
483564 } finally {
484565 lock.withLock { currentTask = null }
485566 handleDownload()
0 commit comments