diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ae7513c89..3a7800276 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -145,8 +145,8 @@
android:name=".service.DownloadService"
android:foregroundServiceType="dataSync" />
-
() {
id = Constants.NOTIFICATION_CHANNEL_DOWNLOADING,
name = getString(stringRes.downloading),
)
+ createNotificationChannel(
+ id = NOTIFICATION_CHANNEL_INSTALL,
+ name = getString(R.string.install)
+ )
lifecycleScope.launch {
_downloadState
@@ -267,20 +278,16 @@ class DownloadService : ConnectionService() {
.putExtra(MainActivity.EXTRA_CACHE_FILE_NAME, task.release.cacheFileName)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
.toPendingIntent(this)
- notificationManager?.notify(
- task.notificationTag,
- Constants.NOTIFICATION_ID_INSTALL,
- NotificationCompat
- .Builder(this, Constants.NOTIFICATION_CHANNEL_INSTALL)
- .setAutoCancel(true)
- .setOngoing(false)
- .setSmallIcon(android.R.drawable.stat_sys_download_done)
- .setColor(Color.GREEN)
- .setOnlyAlertOnce(true)
- .setContentIntent(intent)
- .setContentTitle(getString(stringRes.downloaded_FORMAT, task.name))
- .setContentText(getString(stringRes.tap_to_install_DESC))
- .build()
+ val notification = createInstallNotification(
+ appName = task.name,
+ state = InstallState.Pending,
+ autoCancel = true,
+ ) {
+ setContentIntent(intent)
+ }
+ notificationManager?.installNotification(
+ packageName = task.packageName,
+ notification = notification,
)
}
diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml
index 3e8ec94ce..a6bb7b5ac 100644
--- a/core/common/src/main/res/values/strings.xml
+++ b/core/common/src/main/res/values/strings.xml
@@ -106,6 +106,10 @@
Shizuku is not installed
Installed
Installing
+ Installation Failed
+ Failed to install %s
+ Uninstalled
+ %s has been uninstalled
Could not check integrity.
Invalid address
Invalid fingerprint format
diff --git a/installer/src/main/java/com/looker/installer/InstallManager.kt b/installer/src/main/java/com/looker/installer/InstallManager.kt
index e24653f5c..ac3b0d853 100644
--- a/installer/src/main/java/com/looker/installer/InstallManager.kt
+++ b/installer/src/main/java/com/looker/installer/InstallManager.kt
@@ -1,11 +1,9 @@
package com.looker.installer
import android.content.Context
-import com.looker.core.common.Constants
import com.looker.core.common.PackageName
import com.looker.core.common.extension.addAndCompute
import com.looker.core.common.extension.filter
-import com.looker.core.common.extension.notificationManager
import com.looker.core.common.extension.updateAsMutable
import com.looker.core.datastore.SettingsRepository
import com.looker.core.datastore.get
@@ -28,7 +26,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
-// TODO: Fix the stuck state, and other installer
class InstallManager(
private val context: Context,
settingsRepository: SettingsRepository
@@ -92,10 +89,6 @@ class InstallManager(
it.install(item)
}
updateState { put(item.packageName, success) }
- context.notificationManager?.cancel(
- "download-${item.packageName.name}",
- Constants.NOTIFICATION_ID_DOWNLOADING
- )
currentQueue.remove(item.packageName.name)
}
}
diff --git a/installer/src/main/java/com/looker/installer/installers/session/SessionInstaller.kt b/installer/src/main/java/com/looker/installer/installers/session/SessionInstaller.kt
index 44d7abe2a..fb0dba33b 100644
--- a/installer/src/main/java/com/looker/installer/installers/session/SessionInstaller.kt
+++ b/installer/src/main/java/com/looker/installer/installers/session/SessionInstaller.kt
@@ -23,7 +23,7 @@ import kotlin.coroutines.resume
internal class SessionInstaller(private val context: Context) : Installer {
private val installer = context.packageManager.packageInstaller
- private val intent = Intent(context, SessionInstallerService::class.java)
+ private val intent = Intent(context, SessionInstallerReceiver::class.java)
companion object {
private var installerCallbacks: PackageInstaller.SessionCallback? = null
@@ -73,7 +73,7 @@ internal class SessionInstaller(private val context: Context) : Installer {
}
}
- val pendingIntent = PendingIntent.getService(context, id, intent, flags)
+ val pendingIntent = PendingIntent.getBroadcast(context, id, intent, flags)
if (cont.isActive) activeSession.commit(pendingIntent.intentSender)
}
@@ -90,8 +90,8 @@ internal class SessionInstaller(private val context: Context) : Installer {
@SuppressLint("MissingPermission")
override suspend fun uninstall(packageName: PackageName) =
suspendCancellableCoroutine { cont ->
- intent.putExtra(SessionInstallerService.ACTION_UNINSTALL, true)
- val pendingIntent = PendingIntent.getService(context, -1, intent, flags)
+ intent.putExtra(SessionInstallerReceiver.ACTION_UNINSTALL, true)
+ val pendingIntent = PendingIntent.getBroadcast(context, -1, intent, flags)
installer.uninstall(packageName.name, pendingIntent.intentSender)
cont.resume(Unit)
diff --git a/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerReceiver.kt b/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerReceiver.kt
index e48194195..b037a7bd3 100644
--- a/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerReceiver.kt
+++ b/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerReceiver.kt
@@ -4,17 +4,27 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
-import android.graphics.Color
-import androidx.core.app.NotificationCompat
import com.looker.core.common.Constants.NOTIFICATION_CHANNEL_INSTALL
-import com.looker.core.common.Constants.NOTIFICATION_ID_DOWNLOADING
-import com.looker.core.common.Constants.NOTIFICATION_ID_INSTALL
import com.looker.core.common.R
import com.looker.core.common.createNotificationChannel
import com.looker.core.common.extension.getPackageName
import com.looker.core.common.extension.notificationManager
+import com.looker.core.common.toPackageName
+import com.looker.installer.InstallManager
+import com.looker.installer.model.InstallState
+import com.looker.installer.notification.createInstallNotification
+import com.looker.installer.notification.installNotification
+import com.looker.installer.notification.removeInstallNotification
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+@AndroidEntryPoint
class SessionInstallerReceiver : BroadcastReceiver() {
+
+ // This is a cyclic dependency injection, I know but this is the best option for now
+ @Inject
+ lateinit var installManager: InstallManager
+
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
@@ -50,55 +60,47 @@ class SessionInstallerReceiver : BroadcastReceiver() {
val appName = packageManager.getPackageName(packageName)
- val notificationTag = "download-$packageName"
-
- val builder = NotificationCompat
- .Builder(context, NOTIFICATION_CHANNEL_INSTALL)
- .setAutoCancel(true)
-
- when(status) {
- PackageInstaller.STATUS_SUCCESS -> {
- if (isUninstall) {
- // remove any notification for this app
- notificationManager?.cancel(notificationTag, NOTIFICATION_ID_INSTALL)
- } else {
- val notification = builder
- .setSmallIcon(R.drawable.ic_check)
- .setColor(Color.GREEN)
- .setContentTitle("Installed")
- .setTimeoutAfter(5_000)
- .setContentText(appName)
- .build()
- notificationManager?.notify(
- notificationTag,
- NOTIFICATION_ID_INSTALL,
- notification
+ if (packageName != null) {
+ when (status) {
+ PackageInstaller.STATUS_SUCCESS -> {
+ notificationManager?.removeInstallNotification(packageName)
+ val notification = context.createInstallNotification(
+ appName = (appName ?: packageName.substringAfterLast('.')).toString(),
+ state = InstallState.Installed,
+ isUninstall = isUninstall,
+ ) {
+ setTimeoutAfter(SUCCESS_TIMEOUT)
+ }
+ notificationManager?.installNotification(
+ packageName = packageName.toString(),
+ notification = notification,
)
}
- }
- PackageInstaller.STATUS_FAILURE_ABORTED -> {
- // do nothing if user cancels
- }
+ PackageInstaller.STATUS_FAILURE_ABORTED -> {
+ installManager.remove(packageName.toPackageName())
+ }
- else -> {
- // problem occurred when installing/uninstalling package
- val notification = builder
- .setSmallIcon(android.R.drawable.stat_notify_error)
- .setColor(Color.GREEN)
- .setContentTitle("Unknown Error")
- .setContentText(message)
- .build()
- notificationManager?.notify(
- notificationTag,
- NOTIFICATION_ID_DOWNLOADING,
- notification
- )
+ else -> {
+ installManager.remove(packageName.toPackageName())
+ val notification = context.createInstallNotification(
+ appName = appName.toString(),
+ state = InstallState.Failed,
+ ) {
+ setContentText(message)
+ }
+ notificationManager?.installNotification(
+ packageName = packageName,
+ notification = notification
+ )
+ }
}
}
}
companion object {
- private const val ACTION_UNINSTALL = "action_uninstall"
+ const val ACTION_UNINSTALL = "action_uninstall"
+
+ private const val SUCCESS_TIMEOUT = 5_000L
}
}
diff --git a/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerService.kt b/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerService.kt
deleted file mode 100644
index 95a48e2ac..000000000
--- a/installer/src/main/java/com/looker/installer/installers/session/SessionInstallerService.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.looker.installer.installers.session
-
-import android.app.Service
-import android.content.Intent
-import android.content.pm.PackageInstaller
-import android.content.pm.PackageManager
-import android.graphics.Color
-import android.os.IBinder
-import android.view.ContextThemeWrapper
-import androidx.core.app.NotificationCompat
-import com.looker.core.common.Constants.NOTIFICATION_CHANNEL_DOWNLOADING
-import com.looker.core.common.Constants.NOTIFICATION_ID_DOWNLOADING
-import com.looker.core.common.R as CommonR
-import com.looker.core.common.extension.notificationManager
-
-class SessionInstallerService : Service() {
- companion object {
- const val ACTION_UNINSTALL = "action_uninstall"
- }
-
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
- val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
-
- if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
- // prompts user to enable unknown source
- val promptIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
-
- promptIntent?.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)
-
- startActivity(it)
- }
- } else {
- notifyStatus(intent)
- }
-
- stopSelf()
- return START_NOT_STICKY
- }
-
- override fun onBind(intent: Intent?): IBinder? = null
-
- /**
- * Notifies user of installer outcome.
- */
- private fun notifyStatus(intent: Intent) {
- // unpack from intent
- val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
- val name = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
- val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
- val isUninstall = intent.getBooleanExtra(ACTION_UNINSTALL, false)
-
- // get application name for notifications
- val appLabel = try {
- if (name != null) {
- packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(
- name,
- PackageManager.GET_META_DATA
- )
- )
- } else {
- null
- }
- } catch (_: Exception) {
- null
- }
-
- val notificationTag = "download-$name"
-
- // start building
- val builder = NotificationCompat
- .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
- .setAutoCancel(true)
-
- when (status) {
- PackageInstaller.STATUS_SUCCESS -> {
- if (isUninstall) {
- // remove any notification for this app
- notificationManager?.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING)
- } else {
- val notification = builder
- .setSmallIcon(CommonR.drawable.ic_check)
- .setColor(Color.GREEN)
- .setContentTitle("Installed")
- .setContentText(appLabel)
- .build()
- notificationManager?.notify(
- notificationTag,
- NOTIFICATION_ID_DOWNLOADING,
- notification
- )
- }
- }
-
- PackageInstaller.STATUS_FAILURE_ABORTED -> {
- // do nothing if user cancels
- }
-
- else -> {
- // problem occurred when installing/uninstalling package
- val notification = builder
- .setSmallIcon(android.R.drawable.stat_notify_error)
- .setColor(Color.GREEN)
- .setContentTitle("Unknown Error")
- .setContentText(message)
- .build()
- notificationManager?.notify(
- notificationTag,
- NOTIFICATION_ID_DOWNLOADING,
- notification
- )
- }
- }
- }
-}
diff --git a/installer/src/main/java/com/looker/installer/notification/InstallNotification.kt b/installer/src/main/java/com/looker/installer/notification/InstallNotification.kt
new file mode 100644
index 000000000..7f48e5b03
--- /dev/null
+++ b/installer/src/main/java/com/looker/installer/notification/InstallNotification.kt
@@ -0,0 +1,91 @@
+package com.looker.installer.notification
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.Context
+import android.graphics.Color
+import androidx.core.app.NotificationCompat
+import com.looker.core.common.Constants.NOTIFICATION_CHANNEL_INSTALL
+import com.looker.core.common.Constants.NOTIFICATION_ID_INSTALL
+import com.looker.installer.model.InstallState
+import com.looker.installer.model.InstallState.Failed
+import com.looker.installer.model.InstallState.Installed
+import com.looker.installer.model.InstallState.Installing
+import com.looker.installer.model.InstallState.Pending
+import com.looker.core.common.R as CommonR
+
+fun NotificationManager.installNotification(
+ packageName: String,
+ notification: Notification,
+) {
+ notify(
+ installTag(packageName),
+ NOTIFICATION_ID_INSTALL,
+ notification
+ )
+}
+
+fun NotificationManager.removeInstallNotification(
+ packageName: String,
+) {
+ cancel(installTag(packageName), NOTIFICATION_ID_INSTALL)
+}
+
+fun installTag(name: String): String = "install-${name.trim().replace(' ', '_')}"
+
+private const val SUCCESS_TIMEOUT = 5_000L
+
+fun Context.createInstallNotification(
+ appName: String,
+ state: InstallState,
+ isUninstall: Boolean = false,
+ autoCancel: Boolean = true,
+ block: NotificationCompat.Builder.() -> Unit = {},
+): Notification {
+ return NotificationCompat
+ .Builder(this, NOTIFICATION_CHANNEL_INSTALL)
+ .apply {
+ setAutoCancel(autoCancel)
+ setOngoing(false)
+ setOnlyAlertOnce(true)
+ setColor(Color.GREEN)
+ val (title, text) = if (isUninstall) {
+ setTimeoutAfter(SUCCESS_TIMEOUT)
+ setSmallIcon(CommonR.drawable.ic_delete)
+ getString(CommonR.string.uninstalled_application) to
+ getString(CommonR.string.uninstalled_application_DESC, appName)
+ } else {
+ when (state) {
+ Failed -> {
+ setSmallIcon(CommonR.drawable.ic_bug_report)
+ getString(CommonR.string.installation_failed) to
+ getString(CommonR.string.installation_failed_DESC, appName)
+ }
+
+ Pending -> {
+ setSmallIcon(CommonR.drawable.ic_download)
+ getString(CommonR.string.downloaded_FORMAT, appName) to
+ getString(CommonR.string.tap_to_install_DESC)
+ }
+
+ Installing -> {
+ setSmallIcon(CommonR.drawable.ic_download)
+ setProgress(-1, -1, true)
+ getString(CommonR.string.installing) to
+ appName
+ }
+
+ Installed -> {
+ setTimeoutAfter(SUCCESS_TIMEOUT)
+ setSmallIcon(CommonR.drawable.ic_check)
+ getString(CommonR.string.installed) to
+ appName
+ }
+ }
+ }
+ setContentTitle(title)
+ setContentText(text)
+ block()
+ }
+ .build()
+}