mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-06-20 14:19:19 +00:00
Improve install notifications, improve DefaultInstaller, misc. clean-up
Installer notifications have their own channel, their tags have been fixed, and the timeout has been properly set instead of using sleep. Ensured that DefaultInstaller's sessions use unique file names. Also improved error handling by including broken pipes and by preventing post-copy operations if an error has occurred. Some cleaning up has been done in Common, DownloadService, and SyncService. A few changes have been cherry-picked from master.
This commit is contained in:
@ -3,14 +3,14 @@ package com.looker.droidify.service
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.ContextThemeWrapper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.looker.droidify.BuildConfig
|
||||
import com.looker.droidify.Common
|
||||
import com.looker.droidify.Common.NOTIFICATION_ID_DOWNLOADING
|
||||
import com.looker.droidify.Common.NOTIFICATION_ID_SYNCING
|
||||
import com.looker.droidify.Common.NOTIFICATION_CHANNEL_DOWNLOADING
|
||||
import com.looker.droidify.MainActivity
|
||||
import com.looker.droidify.R
|
||||
import com.looker.droidify.content.Cache
|
||||
@ -19,61 +19,27 @@ import com.looker.droidify.entity.Repository
|
||||
import com.looker.droidify.installer.AppInstaller
|
||||
import com.looker.droidify.network.Downloader
|
||||
import com.looker.droidify.utility.Utils
|
||||
import com.looker.droidify.utility.Utils.rootInstallerEnabled
|
||||
import com.looker.droidify.utility.extension.android.*
|
||||
import com.looker.droidify.utility.extension.resources.*
|
||||
import com.looker.droidify.utility.extension.text.*
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import kotlin.math.*
|
||||
|
||||
class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
companion object {
|
||||
private const val ACTION_OPEN = "${BuildConfig.APPLICATION_ID}.intent.action.OPEN"
|
||||
private const val ACTION_INSTALL = "${BuildConfig.APPLICATION_ID}.intent.action.INSTALL"
|
||||
private const val ACTION_CANCEL = "${BuildConfig.APPLICATION_ID}.intent.action.CANCEL"
|
||||
private const val EXTRA_CACHE_FILE_NAME =
|
||||
"${BuildConfig.APPLICATION_ID}.intent.extra.CACHE_FILE_NAME"
|
||||
|
||||
private val mutableDownloadState = MutableSharedFlow<State.Downloading>()
|
||||
private val downloadState = mutableDownloadState.asSharedFlow()
|
||||
}
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
class Receiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action.orEmpty()
|
||||
when {
|
||||
action.startsWith("$ACTION_OPEN.") -> {
|
||||
val packageName = action.substring(ACTION_OPEN.length + 1)
|
||||
context.startActivity(
|
||||
Intent(context, MainActivity::class.java)
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse("package:$packageName"))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
}
|
||||
action.startsWith("$ACTION_INSTALL.") -> {
|
||||
val packageName = action.substring(ACTION_INSTALL.length + 1)
|
||||
val cacheFileName = intent.getStringExtra(EXTRA_CACHE_FILE_NAME)
|
||||
context.startActivity(
|
||||
Intent(context, MainActivity::class.java)
|
||||
.setAction(MainActivity.ACTION_INSTALL)
|
||||
.setData(Uri.parse("package:$packageName"))
|
||||
.putExtra(MainActivity.EXTRA_CACHE_FILE_NAME, cacheFileName)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
private val mainDispatcher = Dispatchers.Main
|
||||
|
||||
sealed class State(val packageName: String, val name: String) {
|
||||
class Pending(packageName: String, name: String) : State(packageName, name)
|
||||
@ -121,7 +87,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
} else {
|
||||
cancelTasks(packageName)
|
||||
cancelCurrentTask(packageName)
|
||||
notificationManager.cancel(task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING)
|
||||
notificationManager.cancel(task.notificationTag, NOTIFICATION_ID_DOWNLOADING)
|
||||
tasks += task
|
||||
if (currentTask == null) {
|
||||
handleDownload()
|
||||
@ -146,16 +112,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
|
||||
if (Android.sdk(26)) {
|
||||
NotificationChannel(
|
||||
Common.NOTIFICATION_CHANNEL_DOWNLOADING,
|
||||
NOTIFICATION_CHANNEL_DOWNLOADING,
|
||||
getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
.apply { setShowBadge(false) }
|
||||
.let(notificationManager::createNotificationChannel)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
downloadState.collect { publishForegroundState(false, it) }
|
||||
}
|
||||
downloadState.onEach { publishForegroundState(false, it) }.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@ -176,7 +140,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
private fun cancelTasks(packageName: String?) {
|
||||
tasks.removeAll {
|
||||
(packageName == null || it.packageName == packageName) && run {
|
||||
scope.launch { mutableStateSubject.emit(State.Cancel(it.packageName, it.name)) }
|
||||
scope.launch(mainDispatcher) {
|
||||
mutableStateSubject.emit(
|
||||
State.Cancel(
|
||||
it.packageName,
|
||||
it.name
|
||||
)
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -186,7 +157,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
currentTask?.let {
|
||||
if (packageName == null || it.task.packageName == packageName) {
|
||||
currentTask = null
|
||||
scope.launch {
|
||||
scope.launch(mainDispatcher) {
|
||||
mutableStateSubject.emit(
|
||||
State.Cancel(
|
||||
it.task.packageName,
|
||||
@ -209,9 +180,9 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
|
||||
private fun showNotificationError(task: Task, errorType: ErrorType) {
|
||||
notificationManager.notify(task.notificationTag,
|
||||
Common.NOTIFICATION_ID_DOWNLOADING,
|
||||
NOTIFICATION_ID_DOWNLOADING,
|
||||
NotificationCompat
|
||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
.setColor(
|
||||
@ -276,36 +247,9 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
.build())
|
||||
}
|
||||
|
||||
private fun showNotificationInstall(task: Task) {
|
||||
notificationManager.notify(
|
||||
task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING, NotificationCompat
|
||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setColor(
|
||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||
.getColorFromAttr(R.attr.colorPrimary).defaultColor
|
||||
)
|
||||
.setContentIntent(installIntent(task))
|
||||
.setContentTitle(getString(R.string.downloaded_FORMAT, task.name))
|
||||
.setContentText(getString(R.string.tap_to_install_DESC))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun installIntent(task: Task): PendingIntent = PendingIntent.getBroadcast(
|
||||
this,
|
||||
0,
|
||||
Intent(this, Receiver::class.java)
|
||||
.setAction("$ACTION_INSTALL.${task.packageName}")
|
||||
.putExtra(EXTRA_CACHE_FILE_NAME, task.release.cacheFileName),
|
||||
if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
else PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
private fun publishSuccess(task: Task) {
|
||||
var consumed = false
|
||||
scope.launch {
|
||||
scope.launch(mainDispatcher) {
|
||||
mutableStateSubject.emit(State.Success(task.packageName, task.name, task.release))
|
||||
consumed = true
|
||||
}
|
||||
@ -367,7 +311,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
|
||||
private val stateNotificationBuilder by lazy {
|
||||
NotificationCompat
|
||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setColor(
|
||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||
@ -389,7 +333,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||
private fun publishForegroundState(force: Boolean, state: State) {
|
||||
if (force || currentTask != null) {
|
||||
currentTask = currentTask?.copy(lastState = state)
|
||||
startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||
startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||
when (state) {
|
||||
is State.Connecting -> {
|
||||
setContentTitle(getString(R.string.downloading_FORMAT, state.name))
|
||||
|
@ -7,14 +7,16 @@ import android.app.job.JobParameters
|
||||
import android.app.job.JobService
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.view.ContextThemeWrapper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.looker.droidify.BuildConfig
|
||||
import com.looker.droidify.Common
|
||||
import com.looker.droidify.Common.NOTIFICATION_ID_UPDATES
|
||||
import com.looker.droidify.Common.NOTIFICATION_ID_SYNCING
|
||||
import com.looker.droidify.Common.NOTIFICATION_CHANNEL_SYNCING
|
||||
import com.looker.droidify.Common.NOTIFICATION_CHANNEL_UPDATES
|
||||
import com.looker.droidify.MainActivity
|
||||
import com.looker.droidify.R
|
||||
import com.looker.droidify.content.Preferences
|
||||
@ -123,7 +125,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
fun setUpdateNotificationBlocker(fragment: Fragment?) {
|
||||
updateNotificationBlockerFragment = fragment?.let(::WeakReference)
|
||||
if (fragment != null) {
|
||||
notificationManager.cancel(Common.NOTIFICATION_ID_UPDATES)
|
||||
notificationManager.cancel(NOTIFICATION_ID_UPDATES)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,13 +166,13 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
|
||||
if (Android.sdk(26)) {
|
||||
NotificationChannel(
|
||||
Common.NOTIFICATION_CHANNEL_SYNCING,
|
||||
NOTIFICATION_CHANNEL_SYNCING,
|
||||
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
.apply { setShowBadge(false) }
|
||||
.let(notificationManager::createNotificationChannel)
|
||||
NotificationChannel(
|
||||
Common.NOTIFICATION_CHANNEL_UPDATES,
|
||||
NOTIFICATION_CHANNEL_UPDATES,
|
||||
getString(R.string.updates), NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
.let(notificationManager::createNotificationChannel)
|
||||
@ -215,8 +217,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
|
||||
private fun showNotificationError(repository: Repository, exception: Exception) {
|
||||
notificationManager.notify(
|
||||
"repository-${repository.id}", Common.NOTIFICATION_ID_SYNCING, NotificationCompat
|
||||
.Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING)
|
||||
"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)
|
||||
@ -242,7 +244,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
|
||||
private val stateNotificationBuilder by lazy {
|
||||
NotificationCompat
|
||||
.Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING)
|
||||
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
|
||||
.setSmallIcon(R.drawable.ic_sync)
|
||||
.setColor(
|
||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||
@ -253,7 +255,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
this,
|
||||
0,
|
||||
Intent(this, this::class.java).setAction(ACTION_CANCEL),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
if (Android.sdk(23))
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
else
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
@ -265,7 +267,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
if (force || currentTask?.lastState != state) {
|
||||
currentTask = currentTask?.copy(lastState = state)
|
||||
if (started == Started.MANUAL) {
|
||||
startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||
startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||
when (state) {
|
||||
is State.Connecting -> {
|
||||
setContentTitle(getString(R.string.syncing_FORMAT, state.name))
|
||||
@ -474,8 +476,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
val maxUpdates = 5
|
||||
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
|
||||
notificationManager.notify(
|
||||
Common.NOTIFICATION_ID_UPDATES, NotificationCompat
|
||||
.Builder(this, Common.NOTIFICATION_CHANNEL_UPDATES)
|
||||
NOTIFICATION_ID_UPDATES, NotificationCompat
|
||||
.Builder(this, NOTIFICATION_CHANNEL_UPDATES)
|
||||
.setSmallIcon(R.drawable.ic_new_releases)
|
||||
.setContentTitle(getString(R.string.new_updates_available))
|
||||
.setContentText(
|
||||
@ -494,7 +496,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
||||
0,
|
||||
Intent(this, MainActivity::class.java)
|
||||
.setAction(MainActivity.ACTION_UPDATES),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
if (Android.sdk(23))
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
else
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
|
Reference in New Issue
Block a user