Update: Pack all notify functions in NotificationUtils

This commit is contained in:
machiav3lli 2022-06-23 00:03:42 +02:00
parent 981ae56c44
commit 4a5626971e
4 changed files with 296 additions and 267 deletions

View File

@ -5,20 +5,16 @@ import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.IBinder import android.os.IBinder
import android.view.ContextThemeWrapper
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import com.looker.droidify.NOTIFICATION_CHANNEL_INSTALLER import com.looker.droidify.NOTIFICATION_CHANNEL_INSTALLER
import com.looker.droidify.NOTIFICATION_ID_INSTALLER
import com.looker.droidify.R import com.looker.droidify.R
import com.looker.droidify.ui.activities.MainActivityX import com.looker.droidify.ui.activities.MainActivityX
import com.looker.droidify.utility.Utils import com.looker.droidify.utility.Utils
import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.extension.android.notificationManager import com.looker.droidify.utility.extension.android.notificationManager
import com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.notifyStatus
/** /**
* Runs during or after a PackageInstaller session in order to handle completion, failure, or * Runs during or after a PackageInstaller session in order to handle completion, failure, or
@ -29,8 +25,8 @@ class InstallerService : LifecycleService() {
const val KEY_ACTION = "installerAction" const val KEY_ACTION = "installerAction"
const val KEY_APP_NAME = "appName" const val KEY_APP_NAME = "appName"
const val ACTION_UNINSTALL = "uninstall" const val ACTION_UNINSTALL = "uninstall"
private const val INSTALLED_NOTIFICATION_TIMEOUT: Long = 5000 const val INSTALLED_NOTIFICATION_TIMEOUT: Long = 5000
private const val NOTIFICATION_TAG_PREFIX = "install-" const val NOTIFICATION_TAG_PREFIX = "install-"
} }
override fun onCreate() { override fun onCreate() {
@ -69,99 +65,6 @@ class InstallerService : LifecycleService() {
return START_NOT_STICKY return START_NOT_STICKY
} }
/**
* Notifies user of installer outcome. This can be success, error, or a request for user action
* if installation cannot proceed automatically.
*
* @param intent provided by PackageInstaller to the callback service/activity.
*/
private fun notifyStatus(intent: Intent?) {
// unpack from intent
val status = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
val sessionId = intent?.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1) ?: 0
// get package information from session
val sessionInstaller = this.packageManager.packageInstaller
val session = if (sessionId > 0) sessionInstaller.getSessionInfo(sessionId) else null
val name =
session?.appPackageName ?: intent?.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
val message = intent?.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val installerAction = intent?.getStringExtra(KEY_ACTION)
// get application name for notifications
val appLabel = session?.appLabel ?: intent?.getStringExtra(KEY_APP_NAME)
?: try {
if (name != null) packageManager.getApplicationLabel(
packageManager.getApplicationInfo(
name,
PackageManager.GET_META_DATA
)
) else null
} catch (_: Exception) {
null
}
val notificationTag = "${NOTIFICATION_TAG_PREFIX}$name"
// start building
val builder = NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_INSTALLER)
.setAutoCancel(true)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(R.attr.colorPrimary).defaultColor
)
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
// request user action with "downloaded" notification that triggers a working prompt
notificationManager.notify(
notificationTag, NOTIFICATION_ID_INSTALLER, builder
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(installIntent(intent))
.setContentTitle(getString(R.string.downloaded_FORMAT, appLabel))
.setContentText(getString(R.string.tap_to_install_DESC))
.build()
)
}
PackageInstaller.STATUS_SUCCESS -> {
if (installerAction == ACTION_UNINSTALL)
// remove any notification for this app
notificationManager.cancel(notificationTag, NOTIFICATION_ID_INSTALLER)
else {
val notification = builder
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle(getString(R.string.installed))
.setContentText(appLabel)
.setTimeoutAfter(INSTALLED_NOTIFICATION_TIMEOUT)
.build()
notificationManager.notify(
notificationTag,
NOTIFICATION_ID_INSTALLER,
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)
.setContentTitle(getString(R.string.unknown_error_DESC))
.setContentText(message)
.build()
notificationManager.notify(
notificationTag,
NOTIFICATION_ID_INSTALLER,
notification
)
}
}
}
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
super.onBind(intent) super.onBind(intent)
return null return null
@ -177,7 +80,7 @@ class InstallerService : LifecycleService() {
* @return a pending intent that can be attached to a background-accessible entry point such as * @return a pending intent that can be attached to a background-accessible entry point such as
* a notification * a notification
*/ */
private fun installIntent(intent: Intent): PendingIntent { fun installIntent(intent: Intent): PendingIntent {
// prepare prompt intent // prepare prompt intent
val promptIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT) val promptIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
val name = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) val name = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
@ -194,6 +97,4 @@ class InstallerService : LifecycleService() {
else PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_UPDATE_CURRENT
) )
} }
} }

View File

@ -4,7 +4,6 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.looker.droidify.BuildConfig import com.looker.droidify.BuildConfig
@ -17,7 +16,6 @@ import com.looker.droidify.database.entity.Release
import com.looker.droidify.database.entity.Repository import com.looker.droidify.database.entity.Repository
import com.looker.droidify.installer.AppInstaller import com.looker.droidify.installer.AppInstaller
import com.looker.droidify.network.Downloader import com.looker.droidify.network.Downloader
import com.looker.droidify.ui.activities.MainActivityX
import com.looker.droidify.utility.Utils import com.looker.droidify.utility.Utils
import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.extension.android.notificationManager import com.looker.droidify.utility.extension.android.notificationManager
@ -27,6 +25,7 @@ import com.looker.droidify.utility.extension.resources.getColorFromAttr
import com.looker.droidify.utility.extension.text.formatSize import com.looker.droidify.utility.extension.text.formatSize
import com.looker.droidify.utility.extension.text.hex import com.looker.droidify.utility.extension.text.hex
import com.looker.droidify.utility.extension.text.nullIfEmpty import com.looker.droidify.utility.extension.text.nullIfEmpty
import com.looker.droidify.utility.showNotificationError
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -68,7 +67,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
private val mutableStateSubject = MutableSharedFlow<State>() private val mutableStateSubject = MutableSharedFlow<State>()
private class Task( class Task(
val packageName: String, val name: String, val release: Release, val packageName: String, val name: String, val release: Release,
val url: String, val authentication: String, val url: String, val authentication: String,
) { ) {
@ -181,83 +180,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
} }
} }
private enum class ValidationError { INTEGRITY, FORMAT, METADATA, SIGNATURE, PERMISSIONS } enum class ValidationError { INTEGRITY, FORMAT, METADATA, SIGNATURE, PERMISSIONS }
private sealed class ErrorType { sealed class ErrorType {
object Network : ErrorType() object Network : ErrorType()
object Http : ErrorType() object Http : ErrorType()
class Validation(val validateError: ValidationError) : ErrorType() class Validation(val validateError: ValidationError) : ErrorType()
} }
private fun showNotificationError(task: Task, errorType: ErrorType) {
notificationManager.notify(task.notificationTag,
NOTIFICATION_ID_DOWNLOADING,
NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(R.attr.colorPrimary).defaultColor
)
.setContentIntent(
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivityX::class.java)
.setAction(Intent.ACTION_VIEW)
.setData(Uri.parse("package:${task.packageName}"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
if (Android.sdk(23))
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
else
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.apply {
when (errorType) {
is ErrorType.Network -> {
setContentTitle(
getString(
R.string.could_not_download_FORMAT,
task.name
)
)
setContentText(getString(R.string.network_error_DESC))
}
is ErrorType.Http -> {
setContentTitle(
getString(
R.string.could_not_download_FORMAT,
task.name
)
)
setContentText(getString(R.string.http_error_DESC))
}
is ErrorType.Validation -> {
setContentTitle(
getString(
R.string.could_not_validate_FORMAT,
task.name
)
)
setContentText(
getString(
when (errorType.validateError) {
ValidationError.INTEGRITY -> R.string.integrity_check_error_DESC
ValidationError.FORMAT -> R.string.file_format_error_DESC
ValidationError.METADATA -> R.string.invalid_metadata_error_DESC
ValidationError.SIGNATURE -> R.string.invalid_signature_error_DESC
ValidationError.PERMISSIONS -> R.string.invalid_permissions_error_DESC
}
)
)
}
}::class
}
.build())
}
private fun publishSuccess(task: Task) { private fun publishSuccess(task: Task) {
var consumed = false var consumed = false
scope.launch { scope.launch {

View File

@ -6,9 +6,6 @@ import android.app.PendingIntent
import android.app.job.JobParameters import android.app.job.JobParameters
import android.app.job.JobService import android.app.job.JobService
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -25,13 +22,14 @@ import com.looker.droidify.entity.Order
import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.ProductItem
import com.looker.droidify.entity.Section import com.looker.droidify.entity.Section
import com.looker.droidify.index.RepositoryUpdater import com.looker.droidify.index.RepositoryUpdater
import com.looker.droidify.ui.activities.MainActivityX
import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.RxUtils
import com.looker.droidify.utility.Utils import com.looker.droidify.utility.Utils
import com.looker.droidify.utility.displayUpdatesNotification
import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.extension.android.notificationManager import com.looker.droidify.utility.extension.android.notificationManager
import com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.extension.resources.getColorFromAttr
import com.looker.droidify.utility.extension.text.formatSize import com.looker.droidify.utility.extension.text.formatSize
import com.looker.droidify.utility.showNotificationError
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
@ -234,33 +232,6 @@ class SyncService : ConnectionService<SyncService.Binder>() {
} }
} }
private fun showNotificationError(repository: Repository, exception: Exception) {
notificationManager.notify(
"repository-${repository.id}", NOTIFICATION_ID_SYNCING, NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(android.R.attr.colorPrimary).defaultColor
)
.setContentTitle(getString(R.string.could_not_sync_FORMAT, repository.name))
.setContentText(
getString(
when (exception) {
is RepositoryUpdater.UpdateException -> when (exception.errorType) {
RepositoryUpdater.ErrorType.NETWORK -> R.string.network_error_DESC
RepositoryUpdater.ErrorType.HTTP -> R.string.http_error_DESC
RepositoryUpdater.ErrorType.VALIDATION -> R.string.validation_index_error_DESC
RepositoryUpdater.ErrorType.PARSING -> R.string.parsing_index_error_DESC
}
else -> R.string.unknown_error_DESC
}
)
)
.build()
)
}
private val stateNotificationBuilder by lazy { private val stateNotificationBuilder by lazy {
NotificationCompat NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_SYNCING) .Builder(this, NOTIFICATION_CHANNEL_SYNCING)
@ -489,65 +460,6 @@ class SyncService : ConnectionService<SyncService.Binder>() {
} }
} }
/**
* Displays summary of available updates.
*
* @param productItems list of apps pending updates
*/
private fun displayUpdatesNotification(productItems: List<ProductItem>) {
val maxUpdates = 5
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
notificationManager.notify(
NOTIFICATION_ID_UPDATES, NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_UPDATES)
.setSmallIcon(R.drawable.ic_new_releases)
.setContentTitle(getString(R.string.new_updates_available))
.setContentText(
resources.getQuantityString(
R.plurals.new_updates_DESC_FORMAT,
productItems.size, productItems.size
)
)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(android.R.attr.colorPrimary).defaultColor
)
.setContentIntent(
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivityX::class.java)
.setAction(MainActivityX.ACTION_UPDATES),
if (Android.sdk(23))
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
else
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.setStyle(NotificationCompat.InboxStyle().applyHack {
for (productItem in productItems.take(maxUpdates)) {
val builder = SpannableStringBuilder(productItem.name)
builder.setSpan(
ForegroundColorSpan(Color.BLACK), 0, builder.length,
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
)
builder.append(' ').append(productItem.version)
addLine(builder)
}
if (productItems.size > maxUpdates) {
val summary =
getString(R.string.plus_more_FORMAT, productItems.size - maxUpdates)
if (Android.sdk(24)) {
addLine(summary)
} else {
setSummaryText(summary)
}
}
})
.build()
)
}
class Job : JobService() { class Job : JobService() {
private val jobScope = CoroutineScope(Dispatchers.Default) private val jobScope = CoroutineScope(Dispatchers.Default)
private var syncParams: JobParameters? = null private var syncParams: JobParameters? = null

View File

@ -0,0 +1,286 @@
package com.looker.droidify.utility
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.ContextThemeWrapper
import androidx.core.app.NotificationCompat
import com.looker.droidify.NOTIFICATION_CHANNEL_DOWNLOADING
import com.looker.droidify.NOTIFICATION_CHANNEL_INSTALLER
import com.looker.droidify.NOTIFICATION_CHANNEL_SYNCING
import com.looker.droidify.NOTIFICATION_CHANNEL_UPDATES
import com.looker.droidify.NOTIFICATION_ID_DOWNLOADING
import com.looker.droidify.NOTIFICATION_ID_INSTALLER
import com.looker.droidify.NOTIFICATION_ID_SYNCING
import com.looker.droidify.NOTIFICATION_ID_UPDATES
import com.looker.droidify.R
import com.looker.droidify.database.entity.Repository
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.index.RepositoryUpdater
import com.looker.droidify.installer.InstallerService
import com.looker.droidify.service.DownloadService
import com.looker.droidify.ui.activities.MainActivityX
import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.extension.android.notificationManager
import com.looker.droidify.utility.extension.resources.getColorFromAttr
/**
* Displays summary of available updates.
*
* @param productItems list of apps pending updates
*/
fun Context.displayUpdatesNotification(productItems: List<ProductItem>) {
val maxUpdates = 5
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
notificationManager.notify(
NOTIFICATION_ID_UPDATES, NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_UPDATES)
.setSmallIcon(R.drawable.ic_new_releases)
.setContentTitle(getString(R.string.new_updates_available))
.setContentText(
resources.getQuantityString(
R.plurals.new_updates_DESC_FORMAT,
productItems.size, productItems.size
)
)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(android.R.attr.colorPrimary).defaultColor
)
.setContentIntent(
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivityX::class.java)
.setAction(MainActivityX.ACTION_UPDATES)
.putExtra(
MainActivityX.EXTRA_UPDATES,
productItems.map { it.packageName }.toTypedArray()
),
if (Android.sdk(23))
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
else
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.setStyle(NotificationCompat.InboxStyle().applyHack {
for (productItem in productItems.take(maxUpdates)) {
val builder = SpannableStringBuilder(productItem.name)
builder.setSpan(
ForegroundColorSpan(Color.BLACK), 0, builder.length,
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
)
builder.append(' ').append(productItem.version)
addLine(builder)
}
if (productItems.size > maxUpdates) {
val summary =
getString(R.string.plus_more_FORMAT, productItems.size - maxUpdates)
if (Android.sdk(24)) {
addLine(summary)
} else {
setSummaryText(summary)
}
}
})
.build()
)
}
fun Context.showNotificationError(repository: Repository, exception: Exception) {
notificationManager.notify(
"repository-${repository.id}", NOTIFICATION_ID_SYNCING, NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(android.R.attr.colorPrimary).defaultColor
)
.setContentTitle(getString(R.string.could_not_sync_FORMAT, repository.name))
.setContentText(
getString(
when (exception) {
is RepositoryUpdater.UpdateException -> when (exception.errorType) {
RepositoryUpdater.ErrorType.NETWORK -> R.string.network_error_DESC
RepositoryUpdater.ErrorType.HTTP -> R.string.http_error_DESC
RepositoryUpdater.ErrorType.VALIDATION -> R.string.validation_index_error_DESC
RepositoryUpdater.ErrorType.PARSING -> R.string.parsing_index_error_DESC
}
else -> R.string.unknown_error_DESC
}
)
)
.build()
)
}
fun Context.showNotificationError(
task: DownloadService.Task,
errorType: DownloadService.ErrorType
) {
notificationManager.notify(task.notificationTag,
NOTIFICATION_ID_DOWNLOADING,
NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(R.attr.colorPrimary).defaultColor
)
.setContentIntent(
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivityX::class.java)
.setAction(Intent.ACTION_VIEW)
.setData(Uri.parse("package:${task.packageName}"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
if (Android.sdk(23))
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
else
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.apply {
when (errorType) {
is DownloadService.ErrorType.Network -> {
setContentTitle(
getString(
R.string.could_not_download_FORMAT,
task.name
)
)
setContentText(getString(R.string.network_error_DESC))
}
is DownloadService.ErrorType.Http -> {
setContentTitle(
getString(
R.string.could_not_download_FORMAT,
task.name
)
)
setContentText(getString(R.string.http_error_DESC))
}
is DownloadService.ErrorType.Validation -> {
setContentTitle(
getString(
R.string.could_not_validate_FORMAT,
task.name
)
)
setContentText(
getString(
when (errorType.validateError) {
DownloadService.ValidationError.INTEGRITY -> R.string.integrity_check_error_DESC
DownloadService.ValidationError.FORMAT -> R.string.file_format_error_DESC
DownloadService.ValidationError.METADATA -> R.string.invalid_metadata_error_DESC
DownloadService.ValidationError.SIGNATURE -> R.string.invalid_signature_error_DESC
DownloadService.ValidationError.PERMISSIONS -> R.string.invalid_permissions_error_DESC
}
)
)
}
}::class
}
.build())
}
/**
* Notifies user of installer outcome. This can be success, error, or a request for user action
* if installation cannot proceed automatically.
*
* @param intent provided by PackageInstaller to the callback service/activity.
*/
fun InstallerService.notifyStatus(intent: Intent?) {
// unpack from intent
val status = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
val sessionId = intent?.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1) ?: 0
// get package information from session
val sessionInstaller = this.packageManager.packageInstaller
val session = if (sessionId > 0) sessionInstaller.getSessionInfo(sessionId) else null
val name =
session?.appPackageName ?: intent?.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
val message = intent?.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val installerAction = intent?.getStringExtra(InstallerService.KEY_ACTION)
// get application name for notifications
val appLabel = session?.appLabel ?: intent?.getStringExtra(InstallerService.KEY_APP_NAME)
?: try {
if (name != null) packageManager.getApplicationLabel(
packageManager.getApplicationInfo(
name,
PackageManager.GET_META_DATA
)
) else null
} catch (_: Exception) {
null
}
val notificationTag = "${InstallerService.NOTIFICATION_TAG_PREFIX}$name"
// start building
val builder = NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL_INSTALLER)
.setAutoCancel(true)
.setColor(
ContextThemeWrapper(this, R.style.Theme_Main_Light)
.getColorFromAttr(R.attr.colorPrimary).defaultColor
)
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
// request user action with "downloaded" notification that triggers a working prompt
notificationManager.notify(
notificationTag, NOTIFICATION_ID_INSTALLER, builder
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(installIntent(intent))
.setContentTitle(getString(R.string.downloaded_FORMAT, appLabel))
.setContentText(getString(R.string.tap_to_install_DESC))
.build()
)
}
PackageInstaller.STATUS_SUCCESS -> {
if (installerAction == InstallerService.ACTION_UNINSTALL)
// remove any notification for this app
notificationManager.cancel(notificationTag, NOTIFICATION_ID_INSTALLER)
else {
val notification = builder
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle(getString(R.string.installed))
.setContentText(appLabel)
.setTimeoutAfter(InstallerService.INSTALLED_NOTIFICATION_TIMEOUT)
.build()
notificationManager.notify(
notificationTag,
NOTIFICATION_ID_INSTALLER,
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)
.setContentTitle(getString(R.string.unknown_error_DESC))
.setContentText(message)
.build()
notificationManager.notify(
notificationTag,
NOTIFICATION_ID_INSTALLER,
notification
)
}
}
}