mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +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:
parent
e1fc3c656a
commit
375ab23edb
@ -4,10 +4,12 @@ object Common {
|
|||||||
const val NOTIFICATION_CHANNEL_SYNCING = "syncing"
|
const val NOTIFICATION_CHANNEL_SYNCING = "syncing"
|
||||||
const val NOTIFICATION_CHANNEL_UPDATES = "updates"
|
const val NOTIFICATION_CHANNEL_UPDATES = "updates"
|
||||||
const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading"
|
const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading"
|
||||||
|
const val NOTIFICATION_CHANNEL_INSTALLER = "installed"
|
||||||
|
|
||||||
const val NOTIFICATION_ID_SYNCING = 1
|
const val NOTIFICATION_ID_SYNCING = 1
|
||||||
const val NOTIFICATION_ID_UPDATES = 2
|
const val NOTIFICATION_ID_UPDATES = 2
|
||||||
const val NOTIFICATION_ID_DOWNLOADING = 3
|
const val NOTIFICATION_ID_DOWNLOADING = 3
|
||||||
|
const val NOTIFICATION_ID_INSTALLER = 4
|
||||||
|
|
||||||
const val PREFS_LANGUAGE = "languages"
|
const val PREFS_LANGUAGE = "languages"
|
||||||
const val PREFS_LANGUAGE_DEFAULT = "system"
|
const val PREFS_LANGUAGE_DEFAULT = "system"
|
||||||
|
@ -11,10 +11,12 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class DefaultInstaller(context: Context) : BaseInstaller(context) {
|
class DefaultInstaller(context: Context) : BaseInstaller(context) {
|
||||||
|
|
||||||
private val sessionInstaller = context.packageManager.packageInstaller
|
private val packageManager = context.packageManager
|
||||||
|
private val sessionInstaller = packageManager.packageInstaller
|
||||||
private val intent = Intent(context, InstallerService::class.java)
|
private val intent = Intent(context, InstallerService::class.java)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -48,27 +50,48 @@ class DefaultInstaller(context: Context) : BaseInstaller(context) {
|
|||||||
override suspend fun uninstall(packageName: String) = mDefaultUninstaller(packageName)
|
override suspend fun uninstall(packageName: String) = mDefaultUninstaller(packageName)
|
||||||
|
|
||||||
private fun mDefaultInstaller(cacheFile: File) {
|
private fun mDefaultInstaller(cacheFile: File) {
|
||||||
|
// clean up inactive sessions
|
||||||
|
sessionInstaller.mySessions
|
||||||
|
.filter { session -> !session.isActive }
|
||||||
|
.forEach { session -> sessionInstaller.abandonSession(session.sessionId) }
|
||||||
|
|
||||||
|
// start new session
|
||||||
val id = sessionInstaller.createSession(sessionParams)
|
val id = sessionInstaller.createSession(sessionParams)
|
||||||
|
|
||||||
val session = sessionInstaller.openSession(id)
|
val session = sessionInstaller.openSession(id)
|
||||||
|
|
||||||
|
// get package name
|
||||||
|
val packageInfo = packageManager.getPackageArchiveInfo(cacheFile.absolutePath, 0)
|
||||||
|
val packageName = packageInfo?.packageName ?: "unknown-package"
|
||||||
|
|
||||||
|
// error flags
|
||||||
|
var hasErrors = false
|
||||||
|
|
||||||
session.use { activeSession ->
|
session.use { activeSession ->
|
||||||
activeSession.openWrite("package", 0, cacheFile.length()).use { packageStream ->
|
activeSession.openWrite(packageName, 0, cacheFile.length()).use { packageStream ->
|
||||||
try {
|
try {
|
||||||
cacheFile.inputStream().use { fileStream ->
|
cacheFile.inputStream().use { fileStream ->
|
||||||
fileStream.copyTo(packageStream)
|
fileStream.copyTo(packageStream)
|
||||||
}
|
}
|
||||||
} catch (error: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
Log.w("DefaultInstaller", "Cache file for DefaultInstaller does not seem to exist.")
|
Log.w(
|
||||||
|
"DefaultInstaller",
|
||||||
|
"Cache file for DefaultInstaller does not seem to exist."
|
||||||
|
)
|
||||||
|
hasErrors = true
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(
|
||||||
|
"DefaultInstaller",
|
||||||
|
"Failed to perform cache to package copy due to a bad pipe."
|
||||||
|
)
|
||||||
|
hasErrors = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getService(context, id, intent, flags)
|
|
||||||
|
|
||||||
session.commit(pendingIntent.intentSender)
|
|
||||||
}
|
}
|
||||||
cacheFile.delete()
|
|
||||||
|
if (!hasErrors) {
|
||||||
|
session.commit(PendingIntent.getService(context, id, intent, flags).intentSender)
|
||||||
|
cacheFile.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun mDefaultUninstaller(packageName: String) {
|
private suspend fun mDefaultUninstaller(packageName: String) {
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package com.looker.droidify.installer
|
package com.looker.droidify.installer
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
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.content.pm.PackageManager
|
||||||
import android.net.Uri
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.looker.droidify.Common.NOTIFICATION_CHANNEL_DOWNLOADING
|
import com.looker.droidify.Common.NOTIFICATION_CHANNEL_INSTALLER
|
||||||
import com.looker.droidify.Common.NOTIFICATION_ID_DOWNLOADING
|
import com.looker.droidify.Common.NOTIFICATION_ID_INSTALLER
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.MainActivity
|
import com.looker.droidify.MainActivity
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
@ -27,6 +28,20 @@ class InstallerService : Service() {
|
|||||||
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 = 10000
|
||||||
|
private const val NOTIFICATION_TAG_PREFIX = "install-"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
if (Android.sdk(26)) {
|
||||||
|
NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_INSTALLER,
|
||||||
|
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
.let(notificationManager::createNotificationChannel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
@ -61,12 +76,18 @@ class InstallerService : Service() {
|
|||||||
private fun notifyStatus(intent: Intent) {
|
private fun notifyStatus(intent: Intent) {
|
||||||
// unpack from intent
|
// unpack from intent
|
||||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||||
val name = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
val sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
|
||||||
|
|
||||||
|
// 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 message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
val installerAction = intent.getStringExtra(KEY_ACTION)
|
val installerAction = intent.getStringExtra(KEY_ACTION)
|
||||||
|
|
||||||
// get application name for notifications
|
// get application name for notifications
|
||||||
val appLabel = intent.getStringExtra(KEY_APP_NAME)
|
val appLabel = session?.appLabel ?: intent.getStringExtra(KEY_APP_NAME)
|
||||||
?: try {
|
?: try {
|
||||||
if (name != null) packageManager.getApplicationLabel(
|
if (name != null) packageManager.getApplicationLabel(
|
||||||
packageManager.getApplicationInfo(
|
packageManager.getApplicationInfo(
|
||||||
@ -78,11 +99,11 @@ class InstallerService : Service() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val notificationTag = "download-$name"
|
val notificationTag = "${NOTIFICATION_TAG_PREFIX}$name"
|
||||||
|
|
||||||
// start building
|
// start building
|
||||||
val builder = NotificationCompat
|
val builder = NotificationCompat
|
||||||
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
.Builder(this, NOTIFICATION_CHANNEL_INSTALLER)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -93,7 +114,7 @@ class InstallerService : Service() {
|
|||||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
// request user action with "downloaded" notification that triggers a working prompt
|
// request user action with "downloaded" notification that triggers a working prompt
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
notificationTag, NOTIFICATION_ID_DOWNLOADING, builder
|
notificationTag, NOTIFICATION_ID_INSTALLER, builder
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
.setContentIntent(installIntent(intent))
|
.setContentIntent(installIntent(intent))
|
||||||
.setContentTitle(getString(R.string.downloaded_FORMAT, appLabel))
|
.setContentTitle(getString(R.string.downloaded_FORMAT, appLabel))
|
||||||
@ -104,20 +125,19 @@ class InstallerService : Service() {
|
|||||||
PackageInstaller.STATUS_SUCCESS -> {
|
PackageInstaller.STATUS_SUCCESS -> {
|
||||||
if (installerAction == ACTION_UNINSTALL)
|
if (installerAction == ACTION_UNINSTALL)
|
||||||
// remove any notification for this app
|
// remove any notification for this app
|
||||||
notificationManager.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING)
|
notificationManager.cancel(notificationTag, NOTIFICATION_ID_INSTALLER)
|
||||||
else {
|
else {
|
||||||
val notification = builder
|
val notification = builder
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
.setContentTitle(getString(R.string.installed))
|
.setContentTitle(getString(R.string.installed))
|
||||||
.setContentText(appLabel)
|
.setContentText(appLabel)
|
||||||
|
.setTimeoutAfter(INSTALLED_NOTIFICATION_TIMEOUT)
|
||||||
.build()
|
.build()
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
notificationTag,
|
notificationTag,
|
||||||
NOTIFICATION_ID_DOWNLOADING,
|
NOTIFICATION_ID_INSTALLER,
|
||||||
notification
|
notification
|
||||||
)
|
)
|
||||||
Thread.sleep(5000)
|
|
||||||
notificationManager.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||||
@ -132,7 +152,7 @@ class InstallerService : Service() {
|
|||||||
.build()
|
.build()
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
notificationTag,
|
notificationTag,
|
||||||
NOTIFICATION_ID_DOWNLOADING,
|
NOTIFICATION_ID_INSTALLER,
|
||||||
notification
|
notification
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -163,7 +183,6 @@ class InstallerService : Service() {
|
|||||||
0,
|
0,
|
||||||
Intent(this, MainActivity::class.java)
|
Intent(this, MainActivity::class.java)
|
||||||
.setAction(MainActivity.ACTION_INSTALL)
|
.setAction(MainActivity.ACTION_INSTALL)
|
||||||
.setData(Uri.parse("package:$name"))
|
|
||||||
.putExtra(Intent.EXTRA_INTENT, promptIntent)
|
.putExtra(Intent.EXTRA_INTENT, promptIntent)
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||||
if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
@ -41,6 +41,11 @@ class SettingsFragment : ScreenFragment() {
|
|||||||
private var preferenceBinding: PreferenceItemBinding? = null
|
private var preferenceBinding: PreferenceItemBinding? = null
|
||||||
private val preferences = mutableMapOf<Preferences.Key<*>, Preference<*>>()
|
private val preferences = mutableMapOf<Preferences.Key<*>, Preference<*>>()
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
preferences.forEach { (_, preference) -> preference.update() }
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
preferenceBinding = PreferenceItemBinding.inflate(layoutInflater)
|
preferenceBinding = PreferenceItemBinding.inflate(layoutInflater)
|
||||||
@ -216,7 +221,7 @@ class SettingsFragment : ScreenFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LinearLayoutCompat.addCategory(
|
private inline fun LinearLayoutCompat.addCategory(
|
||||||
title: String,
|
title: String,
|
||||||
callback: LinearLayoutCompat.() -> Unit,
|
callback: LinearLayoutCompat.() -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -3,14 +3,14 @@ package com.looker.droidify.service
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
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
|
||||||
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.MainActivity
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.content.Cache
|
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.installer.AppInstaller
|
||||||
import com.looker.droidify.network.Downloader
|
import com.looker.droidify.network.Downloader
|
||||||
import com.looker.droidify.utility.Utils
|
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.android.*
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.*
|
||||||
import com.looker.droidify.utility.extension.text.*
|
import com.looker.droidify.utility.extension.text.*
|
||||||
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.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
class DownloadService : ConnectionService<DownloadService.Binder>() {
|
class DownloadService : ConnectionService<DownloadService.Binder>() {
|
||||||
companion object {
|
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 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 mutableDownloadState = MutableSharedFlow<State.Downloading>()
|
||||||
private val downloadState = mutableDownloadState.asSharedFlow()
|
private val downloadState = mutableDownloadState.asSharedFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val scope = CoroutineScope(Dispatchers.Default)
|
private val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
private val mainDispatcher = Dispatchers.Main
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class State(val packageName: String, val name: String) {
|
sealed class State(val packageName: String, val name: String) {
|
||||||
class Pending(packageName: String, name: String) : State(packageName, name)
|
class Pending(packageName: String, name: String) : State(packageName, name)
|
||||||
@ -121,7 +87,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
} else {
|
} else {
|
||||||
cancelTasks(packageName)
|
cancelTasks(packageName)
|
||||||
cancelCurrentTask(packageName)
|
cancelCurrentTask(packageName)
|
||||||
notificationManager.cancel(task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING)
|
notificationManager.cancel(task.notificationTag, NOTIFICATION_ID_DOWNLOADING)
|
||||||
tasks += task
|
tasks += task
|
||||||
if (currentTask == null) {
|
if (currentTask == null) {
|
||||||
handleDownload()
|
handleDownload()
|
||||||
@ -146,16 +112,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
if (Android.sdk(26)) {
|
if (Android.sdk(26)) {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
Common.NOTIFICATION_CHANNEL_DOWNLOADING,
|
NOTIFICATION_CHANNEL_DOWNLOADING,
|
||||||
getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW
|
getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
.apply { setShowBadge(false) }
|
.apply { setShowBadge(false) }
|
||||||
.let(notificationManager::createNotificationChannel)
|
.let(notificationManager::createNotificationChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
downloadState.onEach { publishForegroundState(false, it) }.launchIn(scope)
|
||||||
downloadState.collect { publishForegroundState(false, it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@ -176,7 +140,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
private fun cancelTasks(packageName: String?) {
|
private fun cancelTasks(packageName: String?) {
|
||||||
tasks.removeAll {
|
tasks.removeAll {
|
||||||
(packageName == null || it.packageName == packageName) && run {
|
(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
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,7 +157,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
currentTask?.let {
|
currentTask?.let {
|
||||||
if (packageName == null || it.task.packageName == packageName) {
|
if (packageName == null || it.task.packageName == packageName) {
|
||||||
currentTask = null
|
currentTask = null
|
||||||
scope.launch {
|
scope.launch(mainDispatcher) {
|
||||||
mutableStateSubject.emit(
|
mutableStateSubject.emit(
|
||||||
State.Cancel(
|
State.Cancel(
|
||||||
it.task.packageName,
|
it.task.packageName,
|
||||||
@ -209,9 +180,9 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
private fun showNotificationError(task: Task, errorType: ErrorType) {
|
private fun showNotificationError(task: Task, errorType: ErrorType) {
|
||||||
notificationManager.notify(task.notificationTag,
|
notificationManager.notify(task.notificationTag,
|
||||||
Common.NOTIFICATION_ID_DOWNLOADING,
|
NOTIFICATION_ID_DOWNLOADING,
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
.setColor(
|
.setColor(
|
||||||
@ -276,36 +247,9 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
.build())
|
.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) {
|
private fun publishSuccess(task: Task) {
|
||||||
var consumed = false
|
var consumed = false
|
||||||
scope.launch {
|
scope.launch(mainDispatcher) {
|
||||||
mutableStateSubject.emit(State.Success(task.packageName, task.name, task.release))
|
mutableStateSubject.emit(State.Success(task.packageName, task.name, task.release))
|
||||||
consumed = true
|
consumed = true
|
||||||
}
|
}
|
||||||
@ -367,7 +311,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
private val stateNotificationBuilder by lazy {
|
private val stateNotificationBuilder by lazy {
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -389,7 +333,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
private fun publishForegroundState(force: Boolean, state: State) {
|
private fun publishForegroundState(force: Boolean, state: State) {
|
||||||
if (force || currentTask != null) {
|
if (force || currentTask != null) {
|
||||||
currentTask = currentTask?.copy(lastState = state)
|
currentTask = currentTask?.copy(lastState = state)
|
||||||
startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||||
when (state) {
|
when (state) {
|
||||||
is State.Connecting -> {
|
is State.Connecting -> {
|
||||||
setContentTitle(getString(R.string.downloading_FORMAT, state.name))
|
setContentTitle(getString(R.string.downloading_FORMAT, state.name))
|
||||||
|
@ -7,14 +7,16 @@ 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.graphics.Color
|
||||||
import android.os.Build
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.style.ForegroundColorSpan
|
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
|
||||||
import com.looker.droidify.BuildConfig
|
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.MainActivity
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.content.Preferences
|
import com.looker.droidify.content.Preferences
|
||||||
@ -123,7 +125,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
fun setUpdateNotificationBlocker(fragment: Fragment?) {
|
fun setUpdateNotificationBlocker(fragment: Fragment?) {
|
||||||
updateNotificationBlockerFragment = fragment?.let(::WeakReference)
|
updateNotificationBlockerFragment = fragment?.let(::WeakReference)
|
||||||
if (fragment != null) {
|
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)) {
|
if (Android.sdk(26)) {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
Common.NOTIFICATION_CHANNEL_SYNCING,
|
NOTIFICATION_CHANNEL_SYNCING,
|
||||||
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
.apply { setShowBadge(false) }
|
.apply { setShowBadge(false) }
|
||||||
.let(notificationManager::createNotificationChannel)
|
.let(notificationManager::createNotificationChannel)
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
Common.NOTIFICATION_CHANNEL_UPDATES,
|
NOTIFICATION_CHANNEL_UPDATES,
|
||||||
getString(R.string.updates), NotificationManager.IMPORTANCE_LOW
|
getString(R.string.updates), NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
.let(notificationManager::createNotificationChannel)
|
.let(notificationManager::createNotificationChannel)
|
||||||
@ -215,8 +217,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
|
|
||||||
private fun showNotificationError(repository: Repository, exception: Exception) {
|
private fun showNotificationError(repository: Repository, exception: Exception) {
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
"repository-${repository.id}", Common.NOTIFICATION_ID_SYNCING, NotificationCompat
|
"repository-${repository.id}", NOTIFICATION_ID_SYNCING, NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING)
|
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -242,7 +244,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
|
|
||||||
private val stateNotificationBuilder by lazy {
|
private val stateNotificationBuilder by lazy {
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING)
|
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
|
||||||
.setSmallIcon(R.drawable.ic_sync)
|
.setSmallIcon(R.drawable.ic_sync)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -253,7 +255,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
this,
|
this,
|
||||||
0,
|
0,
|
||||||
Intent(this, this::class.java).setAction(ACTION_CANCEL),
|
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
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
else
|
else
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
@ -265,7 +267,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
if (force || currentTask?.lastState != state) {
|
if (force || currentTask?.lastState != state) {
|
||||||
currentTask = currentTask?.copy(lastState = state)
|
currentTask = currentTask?.copy(lastState = state)
|
||||||
if (started == Started.MANUAL) {
|
if (started == Started.MANUAL) {
|
||||||
startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||||
when (state) {
|
when (state) {
|
||||||
is State.Connecting -> {
|
is State.Connecting -> {
|
||||||
setContentTitle(getString(R.string.syncing_FORMAT, state.name))
|
setContentTitle(getString(R.string.syncing_FORMAT, state.name))
|
||||||
@ -474,8 +476,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
val maxUpdates = 5
|
val maxUpdates = 5
|
||||||
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
|
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
Common.NOTIFICATION_ID_UPDATES, NotificationCompat
|
NOTIFICATION_ID_UPDATES, NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_UPDATES)
|
.Builder(this, NOTIFICATION_CHANNEL_UPDATES)
|
||||||
.setSmallIcon(R.drawable.ic_new_releases)
|
.setSmallIcon(R.drawable.ic_new_releases)
|
||||||
.setContentTitle(getString(R.string.new_updates_available))
|
.setContentTitle(getString(R.string.new_updates_available))
|
||||||
.setContentText(
|
.setContentText(
|
||||||
@ -494,7 +496,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
0,
|
0,
|
||||||
Intent(this, MainActivity::class.java)
|
Intent(this, MainActivity::class.java)
|
||||||
.setAction(MainActivity.ACTION_UPDATES),
|
.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
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
else
|
else
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
Loading…
x
Reference in New Issue
Block a user