Improve notifications

This commit is contained in:
darken
2022-01-12 16:37:26 +01:00
parent f822050ea1
commit 8bc1a193c8
16 changed files with 223 additions and 96 deletions
@@ -92,7 +92,7 @@ class BleScanner @Inject constructor(
// AirPods Max
BleScanResult(
address = "7E:E5:C7:65:D2:B5",
rssi = -57,
rssi = -30,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
).run { fakeDevices.add(this) }
@@ -26,6 +26,7 @@ import javax.inject.Inject
class MonitorNotifications @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManager,
private val notificationViewFactory: NotificationViewFactory
) {
private val builder: NotificationCompat.Builder
@@ -45,33 +46,32 @@ class MonitorNotifications @Inject constructor(
PendingIntentCompat.FLAG_IMMUTABLE
)
builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setChannelId(NOTIFICATION_CHANNEL_ID)
.setContentIntent(openPi)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.drawable.ic_device_generic_earbuds)
.setOngoing(true)
.setContentTitle(context.getString(R.string.app_name))
builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID).apply {
setChannelId(NOTIFICATION_CHANNEL_ID)
setContentIntent(openPi)
priority = NotificationCompat.PRIORITY_LOW
setSmallIcon(R.drawable.ic_device_generic_earbuds)
setOngoing(true)
setContentTitle(context.getString(R.string.app_name))
}
}
fun getBuilder(device: PodDevice?): NotificationCompat.Builder {
if (device == null) {
builder.setContentTitle(context.getString(R.string.pods_none_label_short))
builder.setSubText(context.getString(R.string.app_name))
builder.setSmallIcon(R.drawable.ic_device_generic_earbuds)
return builder
return builder.apply {
setContentTitle(context.getString(R.string.pods_none_label_short))
setSubText(context.getString(R.string.app_name))
setSmallIcon(R.drawable.ic_device_generic_earbuds)
}
}
builder.setSmallIcon(device.iconRes)
builder.setSubText(null)
builder.setContentTitle(device.getStatusShort(context))
builder.setStyle(
NotificationCompat.BigTextStyle()
.setBigContentTitle(device.getLabel(context))
.bigText(device.getStatusLong(context).joinToString("\n"))
)
log(TAG, VERBOSE) { "updatingNotification(): $device" }
return builder
return builder.apply {
setSmallIcon(device.iconRes)
setSubText(null)
setStyle(NotificationCompat.DecoratedCustomViewStyle())
setCustomContentView(notificationViewFactory.createContentView(device))
log(TAG, VERBOSE) { "updatingNotification(): $device" }
}
}
fun getNotification(podDevice: PodDevice?): Notification = getBuilder(podDevice).build()
@@ -0,0 +1,70 @@
package eu.darken.capod.monitor.ui
import android.content.Context
import android.widget.RemoteViews
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.R
import eu.darken.capod.pods.core.*
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.SingleApplePods
import javax.inject.Inject
class NotificationViewFactory @Inject constructor(
@ApplicationContext private val context: Context
) {
fun createContentView(device: PodDevice): RemoteViews = when (device) {
is DualApplePods -> createDualApplePods(device)
is SingleApplePods -> createSingleApplePods(device)
is BasicSingleApplePods -> createSingleBasicApplePods(device)
else -> createUnknownDevice(device)
}
private fun createDualApplePods(device: DualApplePods): RemoteViews = RemoteViews(
context.packageName,
R.layout.monitor_notification_dual_pods_small
).apply {
device.getBatteryLevelLeftPod(context)
.let { if (device.isLeftPodCharging) "$it" else it }
.run { setTextViewText(R.id.pod_left, this) }
device.getBatteryLevelCase(context)
.let { if (device.isCaseCharging) "$it" else it }
.run { setTextViewText(R.id.pod_case, this) }
device.getBatteryLevelRightPod(context)
.let { if (device.isRightPodCharging) "$it" else it }
.run { setTextViewText(R.id.pod_right, this) }
}
private fun createSingleApplePods(device: SingleApplePods): RemoteViews = RemoteViews(
context.packageName,
R.layout.monitor_notification_single_pods_small
).apply {
setTextViewText(R.id.headphones_label, device.getLabel(context))
device.getBatteryLevelHeadset(context)
.let { if (!device.isHeadsetBeingCharged) "$it" else it }
.run { setTextViewText(R.id.headphones, this) }
}
private fun createSingleBasicApplePods(device: BasicSingleApplePods): RemoteViews = RemoteViews(
context.packageName,
R.layout.monitor_notification_single_pods_small
).apply {
setTextViewText(R.id.headphones_label, device.getLabel(context))
device.getBatteryLevelHeadset(context)
.run { setTextViewText(R.id.headphones, this) }
}
private fun createUnknownDevice(device: PodDevice): RemoteViews = RemoteViews(
context.packageName,
R.layout.monitor_notification_dual_pods_small
).apply {
setTextViewText(R.id.device, device.getLabel(context))
}
}
@@ -31,10 +31,6 @@ interface PodDevice {
fun getLabel(context: Context): String
fun getStatusShort(context: Context): String
fun getStatusLong(context: Context): List<String>
@get:DrawableRes
val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
@@ -1,23 +1,11 @@
package eu.darken.capod.pods.core.apple
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.lowerNibble
import eu.darken.capod.pods.core.HasSinglePod
import eu.darken.capod.pods.core.getBatteryLevelHeadset
interface BasicSingleApplePods : ApplePods, HasSinglePod {
override fun getStatusShort(context: Context): String = context.getString(
R.string.pods_single_basic_status_short,
getBatteryLevelHeadset(context),
)
override fun getStatusLong(context: Context): List<String> {
return listOf("${context.getString(R.string.pods_single_headphones_label)}: ${getBatteryLevelHeadset(context)}")
}
override val batteryHeadsetPercent: Float?
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
15 -> null
@@ -7,48 +7,13 @@ import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.isBitSet
import eu.darken.capod.common.lowerNibble
import eu.darken.capod.common.upperNibble
import eu.darken.capod.pods.core.*
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasDualPods
import eu.darken.capod.pods.core.HasDualPods.Pod
import eu.darken.capod.pods.core.HasEarDetection
interface DualApplePods : ApplePods, HasDualPods, HasEarDetection, HasCase {
override fun getStatusShort(context: Context): String = context.getString(
R.string.pods_dual_case_status_short,
getBatteryLevelLeftPod(context).let {
if (isLeftPodCharging) "$it*" else it
},
getBatteryLevelCase(context).let {
if (isCaseCharging) "$it*" else it
},
getBatteryLevelRightPod(context).let {
if (isRightPodCharging) "$it*" else it
},
)
override fun getStatusLong(context: Context): List<String> {
val list = mutableListOf<String>()
StringBuilder("${context.getString(R.string.pods_dual_left_label)}: ${getBatteryLevelLeftPod(context)}").apply {
if (isLeftPodCharging) append(", ${context.getString(R.string.pods_charging_label)}")
if (isLeftPodInEar) append(", ${context.getString(R.string.pods_inear_label)}")
list.add(this.toString())
}
StringBuilder("${context.getString(R.string.pods_dual_right_label)}: ${getBatteryLevelRightPod(context)}").apply {
if (isRightPodCharging) append(", ${context.getString(R.string.pods_charging_label)}")
if (isRightPodInEar) append(", ${context.getString(R.string.pods_inear_label)}")
if (microPhonePod == Pod.RIGHT) append(", ${context.getString(R.string.pods_microphone_label)}")
list.add(this.toString())
}
StringBuilder("${context.getString(R.string.pods_case_label)}: ${getBatteryLevelCase(context)}").apply {
if (isCaseCharging) append(", ${context.getString(R.string.pods_charging_label)}")
list.add(this.toString())
}
return list
}
val microPhonePod: Pod
get() = when (rawStatus.isBitSet(5)) {
true -> Pod.LEFT
@@ -1,24 +1,12 @@
package eu.darken.capod.pods.core.apple
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.isBitSet
import eu.darken.capod.common.upperNibble
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.HasSinglePod
import eu.darken.capod.pods.core.getBatteryLevelHeadset
interface SingleApplePods : BasicSingleApplePods, HasEarDetection, HasSinglePod {
override fun getStatusShort(context: Context): String = context.getString(
R.string.pods_single_basic_status_short,
getBatteryLevelHeadset(context),
)
override fun getStatusLong(context: Context): List<String> {
TODO("Not yet implemented")
}
val isHeadphonesBeingWorn: Boolean
get() = rawStatus.isBitSet(1)
@@ -18,11 +18,4 @@ data class UnknownAppleDevice(
return context.getString(R.string.pods_unknown_label)
}
override fun getStatusShort(context: Context): String {
return context.getString(R.string.pods_unknown_label)
}
override fun getStatusLong(context: Context): List<String> {
return listOf(rawDataHex)
}
}
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM11,20v-5.5H9L13,7v5.5h2L11,20z" />
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11.07,12.85c0.77,-1.39 2.25,-2.21 3.11,-3.44c0.91,-1.29 0.4,-3.7 -2.18,-3.7c-1.69,0 -2.52,1.28 -2.87,2.34L6.54,6.96C7.25,4.83 9.18,3 11.99,3c2.35,0 3.96,1.07 4.78,2.41c0.7,1.15 1.11,3.3 0.03,4.9c-1.2,1.77 -2.35,2.31 -2.97,3.45c-0.25,0.46 -0.35,0.76 -0.35,2.24h-2.89C10.58,15.22 10.46,13.95 11.07,12.85zM14,20c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2S14,18.9 14,20z" />
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginHorizontal="8dp"
android:orientation="horizontal">
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/pod_left"
style="@style/TextAppearance.Compat.Notification.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableStart="@drawable/ic_airpod_left_24"
tools:text="100%" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/pod_case"
style="@style/TextAppearance.Compat.Notification.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="8dp"
android:drawableStart="@drawable/ic_airpod_case_24"
tools:text="100%" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/pod_right"
style="@style/TextAppearance.Compat.Notification.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableStart="@drawable/ic_airpod_right_24"
tools:text="100%" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/headphones_label"
style="@style/TextAppearance.Compat.Notification.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
tools:text="AirPods Max" />
<TextView
android:id="@+id/headphones"
style="@style/TextAppearance.Compat.Notification.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:drawableStart="@drawable/ic_device_generic_headphones"
android:gravity="center"
tools:text="100%" />
</LinearLayout>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/device"
style="@style/TextAppearance.Compat.Notification.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:drawableStart="@drawable/ic_baseline_question_mark_24"
android:gravity="center"
tools:text="100%" />
</LinearLayout>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="notification_height_collapsed">64dp</dimen>
</resources>
-1
View File
@@ -36,7 +36,6 @@
<string name="pods_single_headphones_label">Headphones</string>
<string name="pods_dual_left_label">Left pod</string>
<string name="pods_dual_right_label">Right pod</string>
<string name="pods_dual_case_status_short">L %1$s | C %2$s | R %3$s</string>
<string name="pods_case_label">Case</string>
<string name="pods_case_status_open_label">Open</string>
<string name="pods_case_status_closed_label">Closed</string>