From 375ab23edb4f8d562a7f9560848bf83650554f29 Mon Sep 17 00:00:00 2001 From: Matthew Crossman Date: Tue, 4 Jan 2022 20:26:07 +1100 Subject: [PATCH] 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. --- src/main/kotlin/com/looker/droidify/Common.kt | 2 + .../droidify/installer/DefaultInstaller.kt | 43 ++++++-- .../droidify/installer/InstallerService.kt | 47 +++++--- .../droidify/screen/SettingsFragment.kt | 7 +- .../droidify/service/DownloadService.kt | 102 ++++-------------- .../looker/droidify/service/SyncService.kt | 28 ++--- 6 files changed, 112 insertions(+), 117 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index 446a2f83..a6bc57c0 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -4,10 +4,12 @@ object Common { const val NOTIFICATION_CHANNEL_SYNCING = "syncing" const val NOTIFICATION_CHANNEL_UPDATES = "updates" const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading" + const val NOTIFICATION_CHANNEL_INSTALLER = "installed" const val NOTIFICATION_ID_SYNCING = 1 const val NOTIFICATION_ID_UPDATES = 2 const val NOTIFICATION_ID_DOWNLOADING = 3 + const val NOTIFICATION_ID_INSTALLER = 4 const val PREFS_LANGUAGE = "languages" const val PREFS_LANGUAGE_DEFAULT = "system" diff --git a/src/main/kotlin/com/looker/droidify/installer/DefaultInstaller.kt b/src/main/kotlin/com/looker/droidify/installer/DefaultInstaller.kt index a600ab09..acbf2c9a 100644 --- a/src/main/kotlin/com/looker/droidify/installer/DefaultInstaller.kt +++ b/src/main/kotlin/com/looker/droidify/installer/DefaultInstaller.kt @@ -11,10 +11,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File import java.io.FileNotFoundException +import java.io.IOException 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) companion object { @@ -48,27 +50,48 @@ class DefaultInstaller(context: Context) : BaseInstaller(context) { override suspend fun uninstall(packageName: String) = mDefaultUninstaller(packageName) 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 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 -> - activeSession.openWrite("package", 0, cacheFile.length()).use { packageStream -> + activeSession.openWrite(packageName, 0, cacheFile.length()).use { packageStream -> try { cacheFile.inputStream().use { fileStream -> fileStream.copyTo(packageStream) } - } catch (error: FileNotFoundException) { - Log.w("DefaultInstaller", "Cache file for DefaultInstaller does not seem to exist.") + } catch (e: FileNotFoundException) { + 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) { diff --git a/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt b/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt index 2be4a9e8..306c2ce0 100644 --- a/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt +++ b/src/main/kotlin/com/looker/droidify/installer/InstallerService.kt @@ -1,16 +1,17 @@ package com.looker.droidify.installer +import android.app.NotificationChannel +import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Intent import android.content.pm.PackageInstaller import android.content.pm.PackageManager -import android.net.Uri import android.os.IBinder import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat -import com.looker.droidify.Common.NOTIFICATION_CHANNEL_DOWNLOADING -import com.looker.droidify.Common.NOTIFICATION_ID_DOWNLOADING +import com.looker.droidify.Common.NOTIFICATION_CHANNEL_INSTALLER +import com.looker.droidify.Common.NOTIFICATION_ID_INSTALLER import com.looker.droidify.R import com.looker.droidify.MainActivity import com.looker.droidify.utility.Utils @@ -27,6 +28,20 @@ class InstallerService : Service() { const val KEY_ACTION = "installerAction" const val KEY_APP_NAME = "appName" 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 { @@ -61,12 +76,18 @@ class InstallerService : Service() { 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 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 installerAction = intent.getStringExtra(KEY_ACTION) // get application name for notifications - val appLabel = intent.getStringExtra(KEY_APP_NAME) + val appLabel = session?.appLabel ?: intent.getStringExtra(KEY_APP_NAME) ?: try { if (name != null) packageManager.getApplicationLabel( packageManager.getApplicationInfo( @@ -78,11 +99,11 @@ class InstallerService : Service() { null } - val notificationTag = "download-$name" + val notificationTag = "${NOTIFICATION_TAG_PREFIX}$name" // start building val builder = NotificationCompat - .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) + .Builder(this, NOTIFICATION_CHANNEL_INSTALLER) .setAutoCancel(true) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -93,7 +114,7 @@ class InstallerService : Service() { PackageInstaller.STATUS_PENDING_USER_ACTION -> { // request user action with "downloaded" notification that triggers a working prompt notificationManager.notify( - notificationTag, NOTIFICATION_ID_DOWNLOADING, builder + notificationTag, NOTIFICATION_ID_INSTALLER, builder .setSmallIcon(android.R.drawable.stat_sys_download_done) .setContentIntent(installIntent(intent)) .setContentTitle(getString(R.string.downloaded_FORMAT, appLabel)) @@ -104,20 +125,19 @@ class InstallerService : Service() { PackageInstaller.STATUS_SUCCESS -> { if (installerAction == ACTION_UNINSTALL) // remove any notification for this app - notificationManager.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING) + 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_DOWNLOADING, + NOTIFICATION_ID_INSTALLER, notification ) - Thread.sleep(5000) - notificationManager.cancel(notificationTag, NOTIFICATION_ID_DOWNLOADING) } } PackageInstaller.STATUS_FAILURE_ABORTED -> { @@ -132,7 +152,7 @@ class InstallerService : Service() { .build() notificationManager.notify( notificationTag, - NOTIFICATION_ID_DOWNLOADING, + NOTIFICATION_ID_INSTALLER, notification ) } @@ -163,7 +183,6 @@ class InstallerService : Service() { 0, Intent(this, MainActivity::class.java) .setAction(MainActivity.ACTION_INSTALL) - .setData(Uri.parse("package:$name")) .putExtra(Intent.EXTRA_INTENT, promptIntent) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE diff --git a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt index 83caa65c..9d636faa 100644 --- a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt @@ -41,6 +41,11 @@ class SettingsFragment : ScreenFragment() { private var preferenceBinding: PreferenceItemBinding? = null private val preferences = mutableMapOf, Preference<*>>() + override fun onResume() { + super.onResume() + preferences.forEach { (_, preference) -> preference.update() } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) preferenceBinding = PreferenceItemBinding.inflate(layoutInflater) @@ -216,7 +221,7 @@ class SettingsFragment : ScreenFragment() { } } - private fun LinearLayoutCompat.addCategory( + private inline fun LinearLayoutCompat.addCategory( title: String, callback: LinearLayoutCompat.() -> Unit, ) { diff --git a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt index f045bd38..aaf4041b 100644 --- a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt +++ b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt @@ -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() { 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() 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() { } 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() { 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() { 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() { 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() { 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() { .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() { 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() { 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)) diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 4715366a..153267d8 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -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() { 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() { 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() { 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() { 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() { 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() { 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() { val maxUpdates = 5 fun 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() { 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