Add events notifications

This commit is contained in:
BinTianqi
2025-03-09 12:19:09 +08:00
parent ada12bf3dc
commit 10e34a4e96
10 changed files with 192 additions and 21 deletions

View File

@@ -397,6 +397,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
}
composable<AuthSettings> { AuthSettingsScreen(::navigateUp) }
composable<ApiSettings> { ApiSettings(::navigateUp) }
composable<Notifications> { NotificationsScreen(::navigateUp) }
composable<About> { AboutScreen(::navigateUp) }
composable<Authenticate>(

View File

@@ -1,19 +1,13 @@
package com.bintianqi.owndroid
import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
/**
* ### Notification channels
* - LockTaskMode
*
* ### Notification IDs
* - 1: Stop lock task mode
*/
object NotificationUtils {
fun checkPermission(context: Context): Boolean {
return if(Build.VERSION.SDK_INT >= 33)
@@ -23,7 +17,33 @@ object NotificationUtils {
fun registerChannels(context: Context) {
if(Build.VERSION.SDK_INT < 26) return
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH)
nm.createNotificationChannel(channel)
val lockTaskMode = NotificationChannel(Channel.LOCK_TASK_MODE, context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH)
val events = NotificationChannel(Channel.EVENTS, context.getString(R.string.events), NotificationManager.IMPORTANCE_HIGH)
nm.createNotificationChannels(listOf(lockTaskMode, events))
}
fun notify(context: Context, id: Int, notification: Notification) {
val sp = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sp.getBoolean("n_$id", true) && checkPermission(context)) {
registerChannels(context)
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(id, notification)
}
}
object Channel {
const val LOCK_TASK_MODE = "LockTaskMode"
const val EVENTS = "Events"
}
object ID {
const val LOCK_TASK_MODE = 1
const val PASSWORD_CHANGED = 2
const val USER_ADDED = 3
const val USER_STARTED = 4
const val USER_SWITCHED = 5
const val USER_STOPPED = 6
const val USER_REMOVED = 7
const val BUG_REPORT_SHARED = 8
const val BUG_REPORT_SHARING_DECLINED = 9
const val BUG_REPORT_FAILED = 10
const val SYSTEM_UPDATE_PENDING = 11
}
}

View File

@@ -8,6 +8,8 @@ import android.content.Context
import android.content.Intent
import android.os.Build.VERSION
import android.os.PersistableBundle
import android.os.UserHandle
import android.os.UserManager
import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handleNetworkLogs
@@ -18,6 +20,9 @@ import com.bintianqi.owndroid.dpm.setDefaultAffiliationID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class Receiver : DeviceAdminReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -82,21 +87,101 @@ class Receiver : DeviceAdminReceiver() {
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
super.onLockTaskModeEntering(context, intent, pkg)
if(!NotificationUtils.checkPermission(context)) return
NotificationUtils.registerChannels(context)
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val intent = Intent(context, this::class.java).apply { action = "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE" }
val intent = Intent(context, this::class.java).setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, "LockTaskMode")
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.LOCK_TASK_MODE)
.setContentTitle(context.getText(R.string.lock_task_mode))
.setSmallIcon(R.drawable.lock_fill0)
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build())
.setPriority(NotificationCompat.PRIORITY_HIGH)
nm.notify(1, builder.build())
NotificationUtils.notify(context, NotificationUtils.ID.LOCK_TASK_MODE, builder.build())
}
override fun onLockTaskModeExiting(context: Context, intent: Intent) {
super.onLockTaskModeExiting(context, intent)
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.cancel(1)
nm.cancel(NotificationUtils.ID.LOCK_TASK_MODE)
}
override fun onPasswordChanged(context: Context, intent: Intent, userHandle: UserHandle) {
super.onPasswordChanged(context, intent, userHandle)
sendUserRelatedNotification(context, userHandle, NotificationUtils.ID.PASSWORD_CHANGED, R.string.password_changed, R.drawable.password_fill0)
}
override fun onUserAdded(context: Context, intent: Intent, addedUser: UserHandle) {
super.onUserAdded(context, intent, addedUser)
sendUserRelatedNotification(context, addedUser, NotificationUtils.ID.USER_ADDED, R.string.user_added, R.drawable.person_add_fill0)
}
override fun onUserStarted(context: Context, intent: Intent, startedUser: UserHandle) {
super.onUserStarted(context, intent, startedUser)
sendUserRelatedNotification(context, startedUser, NotificationUtils.ID.USER_STARTED, R.string.user_started, R.drawable.person_fill0)
}
override fun onUserSwitched(context: Context, intent: Intent, switchedUser: UserHandle) {
super.onUserSwitched(context, intent, switchedUser)
sendUserRelatedNotification(context, switchedUser, NotificationUtils.ID.USER_SWITCHED, R.string.user_switched, R.drawable.person_fill0)
}
override fun onUserStopped(context: Context, intent: Intent, stoppedUser: UserHandle) {
super.onUserStopped(context, intent, stoppedUser)
sendUserRelatedNotification(context, stoppedUser, NotificationUtils.ID.USER_STOPPED, R.string.user_stopped, R.drawable.person_fill0)
}
override fun onUserRemoved(context: Context, intent: Intent, removedUser: UserHandle) {
super.onUserRemoved(context, intent, removedUser)
sendUserRelatedNotification(context, removedUser, NotificationUtils.ID.USER_REMOVED, R.string.user_removed, R.drawable.person_remove_fill0)
}
override fun onBugreportShared(context: Context, intent: Intent, hash: String) {
super.onBugreportShared(context, intent, hash)
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
.setContentTitle(context.getString(R.string.bug_report_shared))
.setContentText("SHA-256 hash: $hash")
.setSmallIcon(R.drawable.bug_report_fill0)
NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_SHARED, builder.build())
}
override fun onBugreportSharingDeclined(context: Context, intent: Intent) {
super.onBugreportSharingDeclined(context, intent)
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
.setContentTitle(context.getString(R.string.bug_report_sharing_declined))
.setSmallIcon(R.drawable.bug_report_fill0)
NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_SHARING_DECLINED, builder.build())
}
override fun onBugreportFailed(context: Context, intent: Intent, failureCode: Int) {
super.onBugreportFailed(context, intent, failureCode)
val message = when(failureCode) {
BUGREPORT_FAILURE_FAILED_COMPLETING -> R.string.bug_report_failure_failed_completing
BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE -> R.string.bug_report_failure_no_longer_available
else -> R.string.place_holder
}
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
.setContentTitle(context.getString(R.string.bug_report_failed))
.setContentText(context.getString(message))
.setSmallIcon(R.drawable.bug_report_fill0)
NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_FAILED, builder.build())
}
override fun onSystemUpdatePending(context: Context, intent: Intent, receivedTime: Long) {
super.onSystemUpdatePending(context, intent, receivedTime)
val time = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(receivedTime))
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
.setContentTitle(context.getString(R.string.system_update_pending))
.setContentText(context.getString(R.string.received_time) + ": $time")
.setSmallIcon(R.drawable.system_update_fill0)
NotificationUtils.notify(context, NotificationUtils.ID.SYSTEM_UPDATE_PENDING, builder.build())
}
private fun sendUserRelatedNotification(context: Context, userHandle: UserHandle, id: Int, title: Int, icon: Int) {
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
val serial = um.getSerialNumberForUser(userHandle)
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
.setContentTitle(context.getString(title))
.setContentText(context.getString(R.string.serial_number) + ": $serial")
.setSmallIcon(icon)
NotificationUtils.notify(context, id, builder.build())
}
}

View File

@@ -57,6 +57,7 @@ fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) }
FunctionItem(title = R.string.security, icon = R.drawable.lock_fill0) { onNavigate(AuthSettings) }
FunctionItem(title = R.string.api, icon = R.drawable.apps_fill0) { onNavigate(ApiSettings) }
FunctionItem(R.string.notifications, icon = R.drawable.notifications_fill0) { onNavigate(Notifications) }
FunctionItem(title = R.string.export_logs, icon = R.drawable.description_fill0) {
val time = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date(System.currentTimeMillis()))
exportLogsLauncher.launch("owndroid_log_$time")
@@ -219,6 +220,25 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
}
}
@Serializable object Notifications
@Composable
fun NotificationsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.notifications, 0.dp, onNavigateUp) {
val sp = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val map = mapOf(
NotificationUtils.ID.PASSWORD_CHANGED to R.string.password_changed, NotificationUtils.ID.USER_ADDED to R.string.user_added,
NotificationUtils.ID.USER_STARTED to R.string.user_started, NotificationUtils.ID.USER_SWITCHED to R.string.user_switched,
NotificationUtils.ID.USER_STOPPED to R.string.user_stopped, NotificationUtils.ID.USER_REMOVED to R.string.user_removed,
NotificationUtils.ID.BUG_REPORT_SHARED to R.string.bug_report_shared,
NotificationUtils.ID.BUG_REPORT_SHARING_DECLINED to R.string.bug_report_sharing_declined,
NotificationUtils.ID.BUG_REPORT_FAILED to R.string.bug_report_failed,
NotificationUtils.ID.SYSTEM_UPDATE_PENDING to R.string.system_update_pending
)
map.forEach { (k, v) ->
SwitchItem(v, getState = { sp.getBoolean("n_$k", true) }, onCheckedChange = { sp.edit { putBoolean("n_$k", it) } })
}
}
@Serializable object About
@Composable

View File

@@ -1841,6 +1841,7 @@ fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) {
}
}
var uri by remember { mutableStateOf<Uri?>(null) }
var errorMessage by remember { mutableStateOf<String?>(null) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it }
MyScaffold(R.string.install_system_update, 8.dp, onNavigateUp) {
Button(
@@ -1859,11 +1860,7 @@ fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) {
dpm.installSystemUpdate(receiver, uri!!, executor, callback)
Toast.makeText(context, R.string.start_install_system_update, Toast.LENGTH_SHORT).show()
} catch(e: Exception) {
Toast.makeText(
context,
context.getString(R.string.install_system_update_failed) + e.cause.toString(),
Toast.LENGTH_SHORT
).show()
errorMessage = e.message
}
},
modifier = Modifier.fillMaxWidth()
@@ -1874,4 +1871,5 @@ fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) {
Spacer(Modifier.padding(vertical = 10.dp))
Notes(R.string.auto_reboot_after_install_succeed)
}
ErrorDialog(errorMessage) { errorMessage = null }
}