Skip to content

Commit

Permalink
Add package icon
Browse files Browse the repository at this point in the history
  • Loading branch information
ismartcoding committed Dec 2, 2023
1 parent 2203152 commit 398db0d
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ data class DNotification(
val id: String,
val onlyOnce: Boolean,
val isClearable: Boolean,
val appId: String,
val appName: String,
val time: Instant,
val silent: Boolean,
val title: String,
val text: String,
val body: String,
val actions: List<String>
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import android.app.Notification
import android.content.Context
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat
import com.ismartcoding.lib.extensions.getString2
import com.ismartcoding.plain.data.DNotification
import com.ismartcoding.plain.features.pkg.PackageHelper
import kotlinx.datetime.Instant

fun StatusBarNotification.toDNotification(context: Context): DNotification {
val appName = PackageHelper.getLabel(context, packageName)
val title = notification.extras.getString(Notification.EXTRA_TITLE) ?: ""
val text = notification.extras.getString(Notification.EXTRA_TEXT) ?: ""
val title = notification.extras.getString2(Notification.EXTRA_TITLE)
val text = notification.extras.getString2(Notification.EXTRA_TEXT)
val actions = mutableListOf<String>()

if (notification.actions != null) {
Expand All @@ -21,7 +22,7 @@ fun StatusBarNotification.toDNotification(context: Context): DNotification {
}

// Check whether it is a reply action. We have special treatment for them
if (action.remoteInputs.isNotEmpty()) {
if (action.remoteInputs != null && action.remoteInputs.isNotEmpty()) {
continue
}

Expand All @@ -33,11 +34,12 @@ fun StatusBarNotification.toDNotification(context: Context): DNotification {
id = key,
onlyOnce = notification.flags and NotificationCompat.FLAG_ONLY_ALERT_ONCE != 0,
isClearable = isClearable,
appId = packageName,
appName = appName.ifEmpty { packageName },
time = Instant.fromEpochMilliseconds(postTime),
silent = notification.flags and Notification.FLAG_INSISTENT != 0,
title = title,
text = text,
body = text,
actions = actions
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.Log
import com.ismartcoding.lib.helpers.SearchHelper
import com.ismartcoding.lib.logcat.LogCat
import com.ismartcoding.plain.packageManager
Expand All @@ -17,6 +19,8 @@ import javax.security.cert.X509Certificate

object PackageHelper {
private val appLabelCache: MutableMap<String, String> = HashMap()
private val appTypeCache: MutableMap<String, String> = HashMap()
private val appCertsCache: MutableMap<String, List<DCertificate>> = HashMap()

fun getPackageStatuses(ids: List<String>): Map<String, Boolean> {
val packages = packageManager.getInstalledPackages(0)
Expand Down Expand Up @@ -54,24 +58,32 @@ object PackageHelper {
val applications = packageManager.getInstalledApplications(0).associateBy { it.packageName }
packages.forEach { pkg ->
val appInfo = applications[pkg.packageName] ?: return@forEach
val signatures = signatures(pkg)
val certs = mutableListOf<DCertificate>()
for (signature in signatures) {
val cert = X509Certificate.getInstance(signature.toByteArray())
certs.add(
DCertificate(
cert.issuerDN.name,
cert.subjectDN.name,
cert.serialNumber.toString(),
Instant.fromEpochMilliseconds(cert.notBefore.time),
Instant.fromEpochMilliseconds(cert.notAfter.time)
var certs = appCertsCache[pkg.packageName]
if (certs == null) {
certs = mutableListOf<DCertificate>()
val signatures = signatures(pkg)
for (signature in signatures) {
val cert = X509Certificate.getInstance(signature.toByteArray())
certs.add(
DCertificate(
cert.issuerDN.name,
cert.subjectDN.name,
cert.serialNumber.toString(),
Instant.fromEpochMilliseconds(cert.notBefore.time),
Instant.fromEpochMilliseconds(cert.notAfter.time)
)
)
)
}
appCertsCache[pkg.packageName] = certs
}

var appType = appTypeCache[pkg.packageName]
if (appType == null) {
val isSystemApp = appInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
appType = if (isSystemApp) "system" else "user"
appTypeCache[pkg.packageName] = appType
}

val file = File(appInfo.publicSourceDir)
val isSystemApp = appInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
val appType = if (isSystemApp) "system" else "user"
if (type.isNotEmpty() && appType != type) {
return@forEach
}
Expand All @@ -87,7 +99,7 @@ object PackageHelper {
appType,
pkg.versionName ?: "",
appInfo.sourceDir,
file.length(),
File(appInfo.publicSourceDir).length(),
certs,
Instant.fromEpochMilliseconds(pkg.firstInstallTime),
Instant.fromEpochMilliseconds(pkg.lastUpdateTime),
Expand Down Expand Up @@ -132,9 +144,14 @@ object PackageHelper {
if (t != null) {
val type = t.value
return packageManager.getInstalledPackages(0).count { app ->
val packageInfo = packageManager.getApplicationInfo(app.packageName, 0)
val isSystemApp = packageInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
val appType = if (isSystemApp) "system" else "user"
val key = app.packageName
var appType = appTypeCache[key]
if (appType == null) {
val packageInfo = packageManager.getApplicationInfo(key, 0)
val isSystemApp = packageInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
appType = if (isSystemApp) "system" else "user"
appTypeCache[key] = type
}
appType == type
}
}
Expand All @@ -158,6 +175,26 @@ object PackageHelper {
return getLabel(packageInfo)
}

private fun drawableToBitmap(drawable: Drawable): Bitmap {
val res = if (drawable.intrinsicWidth > 128 || drawable.intrinsicHeight > 128) {
Bitmap.createBitmap(96, 96, Bitmap.Config.ARGB_8888)
} else if (drawable.intrinsicWidth <= 64 || drawable.intrinsicHeight <= 64) {
Bitmap.createBitmap(96, 96, Bitmap.Config.ARGB_8888)
} else {
Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
}
val canvas = Canvas(res)
drawable.setBounds(0, 0, res.width, res.height)
drawable.draw(canvas)
return res
}

fun getIcon(packageName: String): Bitmap {
val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
val iconDrawable = packageManager.getApplicationIcon(applicationInfo)
return drawableToBitmap(iconDrawable)
}

private fun signatures(packageInfo: PackageInfo): MutableSet<Signature> {
val signatures: MutableSet<Signature> = mutableSetOf()
val signingInfo = packageInfo.signingInfo
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package com.ismartcoding.plain.services

import android.app.Notification
import android.content.Context
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat
import com.ismartcoding.lib.logcat.LogCat
import com.ismartcoding.plain.MainApp
import com.ismartcoding.plain.TempData
import com.ismartcoding.plain.extensions.toDNotification

class NotificationListenerService : NotificationListenerService() {
var isConnected = false
private set

private fun isValidNotification(context: Context, statusBarNotification: StatusBarNotification): Boolean {
private fun isValidNotification(statusBarNotification: StatusBarNotification): Boolean {
val notification = statusBarNotification.notification
if (notification.flags and Notification.FLAG_FOREGROUND_SERVICE != 0 || notification.flags and Notification.FLAG_ONGOING_EVENT != 0 || notification.flags and Notification.FLAG_LOCAL_ONLY != 0 || notification.flags and NotificationCompat.FLAG_GROUP_SUMMARY != 0 //The notification that groups other notifications
) {
Expand All @@ -29,7 +27,7 @@ class NotificationListenerService : NotificationListenerService() {
return false
}

if (context.packageName == packageName) {
if (applicationContext.packageName == packageName) {
// Don't send our own notifications
return false
}
Expand All @@ -38,18 +36,16 @@ class NotificationListenerService : NotificationListenerService() {
}

override fun onNotificationPosted(statusBarNotification: StatusBarNotification) {
val context = MainApp.instance
if (isValidNotification(context, statusBarNotification)) {
val n = statusBarNotification.toDNotification(context)
if (isValidNotification(statusBarNotification)) {
val n = statusBarNotification.toDNotification(applicationContext)
if (!TempData.notifications.any { it.id == n.id }) {
TempData.notifications.add(n)
}
}
}

override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) {
val context = MainApp.instance
if (isValidNotification(context, statusBarNotification)) {
if (isValidNotification(statusBarNotification)) {
TempData.notifications.removeIf { it.id == statusBarNotification.key }
}
}
Expand All @@ -59,11 +55,10 @@ class NotificationListenerService : NotificationListenerService() {
isConnected = true
LogCat.d("NotificationListenerService: onListenerConnected")
val notifications = activeNotifications
val context = MainApp.instance
if (notifications != null) {
for (notification in notifications) {
if (isValidNotification(context, notification)) {
val n = notification.toDNotification(context)
if (isValidNotification(notification)) {
val n = notification.toDNotification(applicationContext)
TempData.notifications.add(n)
}
}
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/ismartcoding/plain/web/HttpModule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ismartcoding.plain.web

import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import com.ismartcoding.lib.channel.sendEvent
Expand Down Expand Up @@ -87,6 +88,7 @@ import io.ktor.websocket.send
import kotlinx.serialization.json.Json
import org.json.JSONArray
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.net.URLEncoder
Expand Down Expand Up @@ -308,6 +310,14 @@ object HttpModule {
} else {
call.respond(HttpStatusCode.NotFound)
}
} else if (path.startsWith("pkgicon://")) {
val packageName = path.substring(10)
val bitmap = PackageHelper.getIcon(packageName)
val bytes = ByteArrayOutputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
it.toByteArray()
}
call.respond(bytes)
} else {
val file = File(path)
if (!file.exists()) {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/ismartcoding/plain/web/SXGraphQL.kt
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ class SXGraphQL(val schema: Schema) {
}
query("notifications") {
resolver { ->
val context = MainApp.instance
Permission.NOTIFICATION_LISTENER.checkAsync(context)
TempData.notifications.map { it.toModel() }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import com.ismartcoding.plain.data.DNotification
import kotlinx.datetime.Instant

data class Notification(
val id: String,
val id: ID,
val onlyOnce: Boolean,
val isClearable: Boolean,
val appId: String,
val appName: String,
val time: Instant,
val silent: Boolean,
val title: String,
val text: String,
val body: String,
val actions: List<String>
)

fun DNotification.toModel(): Notification {
return Notification(id, onlyOnce, isClearable, appName, time, silent, title, text, actions)
return Notification(ID(id), onlyOnce, isClearable, appId, appName, time, silent, title, body, actions)
}
21 changes: 21 additions & 0 deletions lib/src/main/java/com/ismartcoding/lib/extensions/Bundle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.ismartcoding.lib.extensions
import android.content.ContentResolver
import android.os.Bundle
import android.os.Parcelable
import android.text.SpannableString
import android.util.Log
import com.ismartcoding.lib.data.SortBy
import com.ismartcoding.lib.data.enums.SortDirection
import com.ismartcoding.lib.isTPlus
Expand Down Expand Up @@ -42,6 +44,25 @@ fun Bundle.paging(
putInt(ContentResolver.QUERY_ARG_LIMIT, limit)
}

fun Bundle.getString2(key: String): String {
return when (val extra = get(key)) {
null -> {
""
}
is String -> {
extra
}

is SpannableString -> {
extra.toString()
}

else -> {
""
}
}
}

inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? =
when {
isTPlus() -> getParcelable(key, T::class.java)
Expand Down

0 comments on commit 398db0d

Please sign in to comment.