diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b753c3608..8a883a4af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -134,6 +134,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-service:$lifeVersion") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifeVersion") implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifeVersion") + implementation("androidx.lifecycle:lifecycle-process:$lifeVersion") //Arch Navigation val navVersion = "2.7.5" diff --git a/app/src/main/java/com/aurora/store/data/installer/SessionInstallerBase.kt b/app/src/main/java/com/aurora/store/data/installer/SessionInstallerBase.kt index b4a48305e..9c06dc7a8 100644 --- a/app/src/main/java/com/aurora/store/data/installer/SessionInstallerBase.kt +++ b/app/src/main/java/com/aurora/store/data/installer/SessionInstallerBase.kt @@ -28,6 +28,7 @@ import android.net.Uri import androidx.core.content.FileProvider import com.aurora.extensions.isSAndAbove import com.aurora.store.BuildConfig +import com.aurora.store.data.receiver.InstallReceiver import com.aurora.store.util.Log import java.io.File @@ -61,12 +62,17 @@ abstract class SessionInstallerBase(context: Context) : InstallerBase(context) { } } - val callBackIntent = Intent(context, InstallerService::class.java) + val callBackIntent = Intent(context, InstallReceiver::class.java).apply { + action = InstallReceiver.ACTION_INSTALL_STATUS + setPackage(context.packageName) + putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName) + addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + } val flags = if (isSAndAbove()) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_UPDATE_CURRENT - val pendingIntent = PendingIntent.getService( + val pendingIntent = PendingIntent.getBroadcast( context, sessionId, callBackIntent, diff --git a/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt index 5638de693..9c0497632 100644 --- a/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt +++ b/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt @@ -18,35 +18,148 @@ */ package com.aurora.store.data.receiver +import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.pm.PackageInstaller import android.util.Log +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.store.R +import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.util.NotificationUtil import com.aurora.store.util.PathUtil import java.io.File +import org.greenrobot.eventbus.EventBus import kotlin.io.path.pathString class InstallReceiver : BroadcastReceiver() { + companion object { + const val ACTION_INSTALL_APP = "com.aurora.store.data.receiver.InstallReceiver.INSTALL_APP" + const val ACTION_INSTALL_STATUS = + "com.aurora.store.data.receiver.InstallReceiver.INSTALL_STATUS" + } + private val TAG = InstallReceiver::class.java.simpleName override fun onReceive(context: Context, intent: Intent) { - 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).pathString) - AppInstaller.getInstance(context).getPreferredInstaller() - .install( - packageName, - downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") } - ) - } catch (exception: Exception) { - Log.e(TAG, "Failed to install $packageName") + when (intent.action) { + 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 + ).pathString + ) + 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 status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1) + val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + + if (inForeground() && status == PackageInstaller.STATUS_PENDING_USER_ACTION) { + promptUser(intent, context) + } else { + postStatus(status, packageName, extra, context) + notifyInstallation(context, packageName!!, status) + } } } } + + private fun notifyInstallation(context: Context, packageName: String, status: Int) { + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val content = if (status == PackageInstaller.STATUS_SUCCESS) { + context.getString(R.string.installer_status_success) + } else { + AppInstaller.getErrorString(context, status) + } + val notification = NotificationUtil.getInstallNotification( + context, + App(packageName).apply { + if (status == PackageInstaller.STATUS_SUCCESS) { + val appInfo = context.packageManager.getApplicationInfo(packageName, 0) + displayName = context.packageManager.getApplicationLabel(appInfo).toString() + } + }, + content + ) + notificationManager.notify(packageName.hashCode(), notification) + } + + private fun promptUser(intent: Intent, context: Context) { + val confirmationIntent = + IntentCompat.getParcelableExtra(intent, Intent.EXTRA_INTENT, Intent::class.java) + + confirmationIntent?.let { + it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending") + it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + try { + context.startActivity(it) + } catch (exception: Exception) { + Log.e(TAG, "Failed to trigger installation!", exception) + } + } + } + + private fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) { + when (status) { + PackageInstaller.STATUS_SUCCESS -> { + EventBus.getDefault().post( + InstallerEvent.Success( + packageName, + context.getString(R.string.installer_status_success) + ) + ) + } + + PackageInstaller.STATUS_FAILURE_ABORTED -> { + EventBus.getDefault().post( + InstallerEvent.Cancelled( + packageName, + AppInstaller.getErrorString(context, status) + ) + ) + } + + else -> { + EventBus.getDefault().post( + InstallerEvent.Failed( + packageName, + AppInstaller.getErrorString(context, status), + extra + ) + ) + } + } + } + + private fun inForeground(): Boolean { + return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED) + } } diff --git a/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt b/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt index 7a67de495..ccab8d80d 100644 --- a/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt +++ b/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt @@ -135,10 +135,10 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame // Notify for installation Intent(appContext, InstallReceiver::class.java).also { + it.action = InstallReceiver.ACTION_INSTALL_APP it.putExtra(Constants.STRING_APP, app.packageName) it.putExtra(Constants.STRING_VERSION, app.versionCode) appContext.sendBroadcast(it) - } // Remove the app from the list diff --git a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt index 75951f7b2..d4615c6b7 100644 --- a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt +++ b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt @@ -268,12 +268,15 @@ object NotificationUtil { } fun getInstallNotification(context: Context, app: App, content: String?): Notification { - val builder = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERT) - builder.color = context.getStyledAttributeColor(R.color.colorAccent) - builder.setSmallIcon(R.drawable.ic_install) - builder.setContentTitle(app.displayName) - builder.setContentText(content) - builder.setSubText(app.packageName) + val builder = + NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERT).apply { + color = context.getStyledAttributeColor(R.color.colorAccent) + setSmallIcon(R.drawable.ic_install) + setContentTitle(app.displayName) + setContentText(content) + setContentIntent(getContentIntentForDetails(context, app)) + setSubText(app.packageName) + } return builder.build() }