DownloadWorker: Move app installation logic to new onSuccess method

Move app installation logic to onSuccess method in worker under a NonCancellable
context and convert the receiver class to be a generic status handler for anything
except success which should be handled in PackageManagerReceiver.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2023-12-27 18:05:12 +05:30
parent 763bf45a70
commit 14c8da2db5
5 changed files with 46 additions and 74 deletions

View File

@@ -135,7 +135,7 @@
</provider> </provider>
<receiver <receiver
android:name=".data.receiver.InstallReceiver" android:name=".data.receiver.InstallerStatusReceiver"
android:exported="false" /> android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@@ -28,7 +28,7 @@ import android.net.Uri
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.aurora.extensions.isSAndAbove import com.aurora.extensions.isSAndAbove
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import com.aurora.store.data.receiver.InstallReceiver import com.aurora.store.data.receiver.InstallerStatusReceiver
import com.aurora.store.util.Log import com.aurora.store.util.Log
import java.io.File import java.io.File
@@ -62,8 +62,8 @@ abstract class SessionInstallerBase(context: Context) : InstallerBase(context) {
} }
} }
val callBackIntent = Intent(context, InstallReceiver::class.java).apply { val callBackIntent = Intent(context, InstallerStatusReceiver::class.java).apply {
action = InstallReceiver.ACTION_INSTALL_STATUS action = InstallerStatusReceiver.ACTION_INSTALL_STATUS
setPackage(context.packageName) setPackage(context.packageName)
putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName) putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName)
addFlags(Intent.FLAG_RECEIVER_FOREGROUND) addFlags(Intent.FLAG_RECEIVER_FOREGROUND)

View File

@@ -25,91 +25,56 @@ import android.content.Intent
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.util.Log import android.util.Log
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.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.util.CommonUtil.inForeground
import com.aurora.store.util.NotificationUtil import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PathUtil
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@AndroidEntryPoint @AndroidEntryPoint
class InstallReceiver : BroadcastReceiver() { class InstallerStatusReceiver : BroadcastReceiver() {
companion object { companion object {
const val ACTION_INSTALL_APP = "com.aurora.store.data.receiver.InstallReceiver.INSTALL_APP"
const val ACTION_INSTALL_STATUS = const val ACTION_INSTALL_STATUS =
"com.aurora.store.data.receiver.InstallReceiver.INSTALL_STATUS" "com.aurora.store.data.receiver.InstallReceiver.INSTALL_STATUS"
} }
private val TAG = InstallReceiver::class.java.simpleName private val TAG = InstallerStatusReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when (intent.action) { if (intent.action == ACTION_INSTALL_STATUS) {
ACTION_INSTALL_APP -> {
val packageName = intent.extras?.getString(Constants.STRING_APP) ?: String()
val version = intent.extras?.getInt(Constants.STRING_VERSION)
if (packageName.isNotBlank() && version != null) {
try {
val downloadDir =
File(PathUtil.getAppDownloadDir(context, packageName, version).path)
AppInstaller.getInstance(context).getPreferredInstaller()
.install(
packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install $packageName")
}
}
}
ACTION_INSTALL_STATUS -> {
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1) val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
// Exit early if package was successfully installed, nothing to do
if (status == PackageInstaller.STATUS_SUCCESS) return
if (inForeground() && status == PackageInstaller.STATUS_PENDING_USER_ACTION) { if (inForeground() && status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
promptUser(intent, context) promptUser(intent, context)
} else { } else {
postStatus(status, packageName, extra, context) postStatus(status, packageName, extra, context)
notifyInstallation(context, packageName!!, status) notifyUser(context, packageName!!, status)
}
} }
} }
} }
private fun notifyInstallation(context: Context, packageName: String, status: Int) { private fun notifyUser(context: Context, packageName: String, status: Int) {
val notificationManager = val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val content = if (status == PackageInstaller.STATUS_SUCCESS) { val notification = NotificationUtil.getInstallerStatusNotification(
context.getString(R.string.installer_status_success)
} else {
AppInstaller.getErrorString(context, status)
}
val notification = NotificationUtil.getInstallNotification(
context, context,
App(packageName).apply { App(packageName),
if (status == PackageInstaller.STATUS_SUCCESS) { AppInstaller.getErrorString(context, status)
val appInfo = context.packageManager.getApplicationInfo(packageName, 0)
displayName = context.packageManager.getApplicationLabel(appInfo).toString()
}
},
content
) )
notificationManager.notify(packageName.hashCode(), notification) notificationManager.notify(packageName.hashCode(), notification)
} }
private fun promptUser(intent: Intent, context: Context) { private fun promptUser(intent: Intent, context: Context) {
val confirmationIntent = IntentCompat.getParcelableExtra(intent, Intent.EXTRA_INTENT, Intent::class.java)?.let {
IntentCompat.getParcelableExtra(intent, Intent.EXTRA_INTENT, Intent::class.java)
confirmationIntent?.let {
it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending") it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -153,8 +118,4 @@ class InstallReceiver : BroadcastReceiver() {
} }
} }
} }
private fun inForeground(): Boolean {
return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)
}
} }

View File

@@ -3,24 +3,22 @@ package com.aurora.store.data.work
import android.app.NotificationManager import android.app.NotificationManager
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.util.Log import android.util.Log
import androidx.hilt.work.HiltWorker import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.aurora.Constants
import com.aurora.extensions.copyTo import com.aurora.extensions.copyTo
import com.aurora.extensions.isQAndAbove import com.aurora.extensions.isQAndAbove
import com.aurora.extensions.requiresObbDir import com.aurora.extensions.requiresObbDir
import com.aurora.gplayapi.helpers.PurchaseHelper import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.DownloadInfo import com.aurora.store.data.model.DownloadInfo
import com.aurora.store.data.model.DownloadStatus import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.model.Request import com.aurora.store.data.model.Request
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.receiver.InstallReceiver
import com.aurora.store.data.room.download.Download import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.download.DownloadDao import com.aurora.store.data.room.download.DownloadDao
import com.aurora.store.util.DownloadWorkerUtil import com.aurora.store.util.DownloadWorkerUtil
@@ -143,17 +141,30 @@ class DownloadWorker @AssistedInject constructor(
// Mark download as completed // Mark download as completed
notifyStatus(DownloadStatus.COMPLETED) notifyStatus(DownloadStatus.COMPLETED)
Log.i(TAG, "Finished downloading ${download.packageName}") Log.i(TAG, "Finished downloading ${download.packageName}")
onSuccess()
// Notify for installation
Intent(appContext, InstallReceiver::class.java).also {
it.action = InstallReceiver.ACTION_INSTALL_APP
it.putExtra(Constants.STRING_APP, download.packageName)
it.putExtra(Constants.STRING_VERSION, download.versionCode)
appContext.sendBroadcast(it)
}
return Result.success() return Result.success()
} }
private suspend fun onSuccess() {
withContext(NonCancellable) {
try {
val downloadDir = PathUtil.getAppDownloadDir(
appContext,
download.packageName,
download.versionCode
)
AppInstaller.getInstance(appContext)
.getPreferredInstaller()
.install(
download.packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install ${download.packageName}", exception)
}
}
}
private suspend fun onFailure() { private suspend fun onFailure() {
withContext(NonCancellable) { withContext(NonCancellable) {
Log.i(TAG, "Cleaning up!") Log.i(TAG, "Cleaning up!")

View File

@@ -163,7 +163,7 @@ object NotificationUtil {
return builder.build() return builder.build()
} }
fun getInstallNotification(context: Context, app: App, content: String?): Notification { fun getInstallerStatusNotification(context: Context, app: App, content: String?): Notification {
val builder = val builder =
NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERT).apply { NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERT).apply {
color = context.getStyledAttributeColor(R.color.colorAccent) color = context.getStyledAttributeColor(R.color.colorAccent)