Skip to content

Commit

Permalink
feat: send notifs when install ready if backgrounded
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine committed Mar 6, 2024
1 parent 21d4d57 commit 5ddd74b
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 13 deletions.
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
Expand Down Expand Up @@ -41,6 +42,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/Theme.AliucordManager.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.aliucord.manager.installer.steps

import android.content.Context
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.manager.PreferencesManager
import com.aliucord.manager.ui.util.InstallNotifications
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.delay
import org.koin.core.component.KoinComponent
Expand All @@ -14,7 +19,10 @@ import org.koin.core.component.inject
*/
const val MINIMUM_STEP_DELAY: Long = 600L

const val ERROR_NOTIF_ID = 200002

abstract class StepRunner : KoinComponent {
private val context: Context by inject()
private val preferences: PreferencesManager by inject()

abstract val steps: ImmutableList<Step>
Expand All @@ -39,7 +47,10 @@ abstract class StepRunner : KoinComponent {
suspend fun executeAll(): Throwable? {
for (step in steps) {
val error = step.executeCatching(this@StepRunner)
if (error != null) return error
if (error != null) {
showErrorNotification()
return error
}

// Skip minimum run time when in dev mode
if (!preferences.devMode && step.durationMs < MINIMUM_STEP_DELAY) {
Expand All @@ -49,4 +60,16 @@ abstract class StepRunner : KoinComponent {

return null
}

private fun showErrorNotification() {
// If app backgrounded
if (ProcessLifecycleOwner.get().lifecycle.currentState == Lifecycle.State.CREATED) {
InstallNotifications.createNotification(
context = context,
id = ERROR_NOTIF_ID,
title = R.string.notif_install_fail_title,
description = R.string.notif_install_fail_desc,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.aliucord.manager.installer.steps.install

import android.content.Context
import androidx.lifecycle.*
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
Expand All @@ -9,13 +11,17 @@ import com.aliucord.manager.installer.steps.patch.CopyDependenciesStep
import com.aliucord.manager.installers.InstallerResult
import com.aliucord.manager.manager.InstallerManager
import com.aliucord.manager.manager.PreferencesManager
import com.aliucord.manager.ui.util.InstallNotifications
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

private const val READY_NOTIF_ID = 200001

/**
* Install the final APK with the system's PackageManager.
*/
class InstallStep : Step(), KoinComponent {
private val context: Context by inject()
private val installers: InstallerManager by inject()
private val prefs: PreferencesManager by inject()

Expand All @@ -25,6 +31,19 @@ class InstallStep : Step(), KoinComponent {
override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk

// If app backgrounded, show notification
if (ProcessLifecycleOwner.get().lifecycle.currentState == Lifecycle.State.CREATED) {
InstallNotifications.createNotification(
context = context,
id = READY_NOTIF_ID,
title = R.string.notif_install_ready_title,
description = R.string.notif_install_ready_desc,
)
}

// Wait until app resumed
ProcessLifecycleOwner.get().lifecycle.withResumed {}

val result = installers.getActiveInstaller().waitInstall(
apks = listOf(apk),
silent = !prefs.devMode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.aliucord.manager.ui.components

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.WindowManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalContext
import com.aliucord.manager.util.findActivity

/**
* Maintain an active screen wakelock as long as [active] is true and this component is in scope.
Expand All @@ -28,12 +26,3 @@ fun Wakelock(active: Boolean = false) {
}
}
}

private fun Context.findActivity(): Activity? {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
Expand All @@ -33,9 +34,14 @@ class InstallOptionsScreen(

@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val model = getScreenModel<InstallOptionsModel>()

LaunchedEffect(Unit) {
InstallNotifications.requestPermissions(context)
}

Scaffold(
topBar = { InstallOptionsAppBar() },
) { paddingValues ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.aliucord.manager.ui.util

import android.Manifest
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.annotation.StringRes
import androidx.core.app.*
import androidx.core.content.ContextCompat
import com.aliucord.manager.*
import com.aliucord.manager.util.findActivity

object InstallNotifications {
private const val CHANNEL_ID = "installation"

/**
* Creates or replaces a notification with id [id] that brings
* up the existing [MainActivity] when clicked upon.
*
* @param id A unique notification ID for different notifications
* @param title Main notification title
* @param description Notification description
*/
fun createNotification(
context: Context,
id: Int,
@StringRes title: Int,
@StringRes description: Int,
) {
val manager = NotificationManagerCompat.from(context)

// Create the target notification channel
if (Build.VERSION.SDK_INT >= 26 && manager.getNotificationChannel(CHANNEL_ID) == null) {
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManager.IMPORTANCE_HIGH)
.setName(context.getString(R.string.notif_group_install_title))
.setDescription(context.getString(R.string.notif_group_install_desc))
.build()

manager.createNotificationChannel(channel)
}

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setDefaults(Notification.DEFAULT_LIGHTS or Notification.DEFAULT_SOUND)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_aliucord_logo)
.setContentTitle(context.getString(title))
.setContentText(context.getString(description))
.setContentIntent(
PendingIntent.getActivity(
/* context = */ context,
/* requestCode = */ 0,
/* intent = */
Intent(context, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT),
/* flags = */ PendingIntent.FLAG_IMMUTABLE,
)
)
.build()

try {
manager.notify(id, notification)
} catch (e: SecurityException) {
Log.w(BuildConfig.TAG, "Failed to send install notification", e)
}
}

/**
* Request the `POST_NOTIFICATIONS` permission if needed.
*/
fun requestPermissions(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return

val granted = ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
val activity = context.findActivity() ?: return

if (granted != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
0,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.aliucord.manager.util

import android.app.Activity
import android.content.*
import android.os.Environment
import android.util.Log
Expand Down Expand Up @@ -44,3 +45,12 @@ fun Context.getPackageVersion(pkg: String): Pair<String, Int> {
return packageManager.getPackageInfo(pkg, 0)
.let { it.versionName to it.versionCode }
}

fun Context.findActivity(): Activity? {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
return null
}
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,11 @@
<string name="installopts_icon_desc">Changes the app\'s icon to use Aliucord branding instead of Discord\'s own.</string>
<string name="installopts_divider_basic">Basic</string>
<string name="installopts_divider_advanced">Advanced</string>

<string name="notif_group_install_title">Active installation</string>
<string name="notif_group_install_desc">Progress notifications sent during a minimized installation</string>
<string name="notif_install_ready_title">Aliucord installation ready!</string>
<string name="notif_install_ready_desc">Click to install…</string>
<string name="notif_install_fail_title">Aliucord failed to install</string>
<string name="notif_install_fail_desc">Click to view more info…</string>
</resources>
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ accompanist-systemUiController = { module = "com.google.accompanist:accompanist-
# AndroidX
androidx-activity = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splashscreen" }

Expand Down Expand Up @@ -80,6 +81,7 @@ androidx = [
"androidx-core",
"androidx-activity",
"androidx-lifecycle",
"androidx-lifecycle-process",
"androidx-splashscreen",
]
compose = [
Expand Down

0 comments on commit 5ddd74b

Please sign in to comment.