I'm new to coding. I have made a net speed indicator app for fun. It displays the network speed as a status bar icon. It also allows users to set data usage limits (like 100 GB per month) and sends a notification when they reach the limit.
The final output looks like this:
I followed this answer to display the status bar icon.
Here is the Notifications class I use:
class Notifications(private val context: Context) {
private val notificationChannelPrimary =
"Primary Channel Notification" // This is the persistent channel that shows live data usage
val notificationPersistentChannelID = 15 // To send notifications of persistent channel
private val notificationChannelDataLimit = "Data Limit Warning Notifications"
private val notificationDataLimitID = 10
private val intentNotification = Intent(context, MainActivity::class.java)
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private var mNotificationBuilder: Notification.Builder? = null
private var tempValues = arrayOf(String())
// The following variables are for creating status bar icon
private val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
private val canvas = Canvas(bitmap)
private val paintSpeed = Paint() // This is for speed value
private val paintUnit = Paint() // This is for speed unit
init {
// Set the paint styles
paintSpeed.color = Color.WHITE
paintSpeed.isAntiAlias = true
paintSpeed.textSize = 60f
paintSpeed.textAlign = Paint.Align.CENTER
paintSpeed.typeface = Typeface.DEFAULT_BOLD
paintUnit.color = Color.WHITE
paintUnit.isAntiAlias = true
paintUnit.textSize = 40f
paintUnit.textAlign = Paint.Align.CENTER
paintUnit.typeface = Typeface.DEFAULT_BOLD
createNotificationChannel()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mNotificationBuilder =
getNotificationBuilder()
}
}
// This method sets the speed to zero when the device is offline
fun setZeroSpeed() {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
canvas.drawText("0", 48f, 52f, paintSpeed)
canvas.drawText("Kb/s", 48f, 95f, paintUnit)
try {
mNotificationBuilder?.setSmallIcon(Icon.createWithBitmap(bitmap))
notificationManager.notify(notificationPersistentChannelID, mNotificationBuilder?.build())
} catch (e: Exception) {
e.printStackTrace()
}
}
// This method shows the speed + data usage on the notification
// It is called every second (from a worker thread)
fun updateDataUsage(
dataSpeed: Long,
remainingData: String?,
planDetails: String?
) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
// BytesConversion.convertSpeed() method takes long value and returns speed with unit array (like 2 Mb/s)
tempValues = BytesConversion.convertSpeed(dataSpeed)
canvas.drawText(tempValues[0], 50f, 50f, paintSpeed)
canvas.drawText(tempValues[1], 50f, 95f, paintUnit)
// If the argument of setContentTitle is null, it's not displaying anything in the title
mNotificationBuilder?.setContentTitle(remainingData)
mNotificationBuilder?.setContentText(planDetails)
try {
mNotificationBuilder?.setSmallIcon(Icon.createWithBitmap(bitmap))
notificationManager.notify(notificationPersistentChannelID, mNotificationBuilder?.build())
} catch (e: Exception) {
}
}
// This method sends notifications when the data usage limit is reached
// You don't need to check supported or unsupported
// because it works in all the devices
// It's just a normal notification
fun sendDataLimitWarning(dataUsageTitle: String?, dataUsageDescription: String?) {
val notificationDataLimit = getNotificationBuilderDataLimit()
notificationDataLimit.setContentText(dataUsageDescription)
notificationDataLimit.setContentTitle(dataUsageTitle)
notificationManager.notify(
notificationDataLimitID,
notificationDataLimit.build()
)
}
// Notification channel is only available from Android Oreo
// So, check the condition
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannels = mutableListOf<NotificationChannel>()
val notificationChannel1 = NotificationChannel(
notificationChannelPrimary, "Persistent Notification",
NotificationManager.IMPORTANCE_HIGH
)
notificationChannel1.description = "Notification that shows data usage and speed"
notificationChannel1.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
val notificationChannel2 = NotificationChannel(
notificationChannelDataLimit, "Data Limit Warning",
NotificationManager.IMPORTANCE_HIGH
)
notificationChannel2.description = "Notification that shows data limit warnings"
notificationChannel2.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
notificationChannel2.enableVibration(true)
notificationChannel2.enableLights(true)
notificationChannels.add(notificationChannel1)
notificationChannels.add(notificationChannel2)
notificationManager.createNotificationChannels(notificationChannels)
}
}
// This is for sending data limit warnings
private fun getNotificationBuilderDataLimit(): NotificationCompat.Builder {
val pendingIntentDataLimit = PendingIntent.getActivity(
context,
notificationDataLimitID,
intentNotification,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return NotificationCompat.Builder(
context,
notificationChannelDataLimit
)
.setContentTitle("Data Limit Warning")
.setSmallIcon(R.drawable.icon_notification)
.setColor(ContextCompat.getColor(context, R.color.primary_color))
.setContentIntent(pendingIntentDataLimit)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationManager.IMPORTANCE_HIGH)
}
// This is for sending data + speed notification
// Speed indicator requires Notification.Builder
@RequiresApi(Build.VERSION_CODES.O)
fun getNotificationBuilder(
): Notification.Builder {
val pendingIntentNotification = PendingIntent.getActivity(
context,
notificationPersistentChannelID,
intentNotification,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return Notification.Builder(
context,
notificationChannelPrimary
)
.setSmallIcon(R.drawable.icon_notification)
.setContentIntent(pendingIntentNotification)
.setAutoCancel(false)
.setStyle(Notification.BigTextStyle())
.setOnlyAlertOnce(true)
.setColor(ContextCompat.getColor(context, R.color.primary_color))
}
}
The problem is that I'm getting the following crashes a lot.
Fatal Exception: android.app.RemoteServiceException: Bad notification(tag=null, id=15) posted from package [package_name], crashing app(uid=10769, pid=16295): Couldn't inflate contentViewsjava.util.ConcurrentModificationException
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1894)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7156)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
and
Fatal Exception: android.app.RemoteServiceException: Bad notification(tag=null, id=15) posted from package [package_name], crashing app(uid=10337, pid=27920): Couldn't inflate contentViewsjava.lang.ArrayIndexOutOfBoundsException: src.length=8 srcPos=0 dst.length=8 dstPos=2 length=8
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2168)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:227)
at android.app.ActivityThread.main(ActivityThread.java:7822)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1026)
I searched for "Bad notification posted crash", I found that mNotificationBuilder.setSmallIcon() argument must be a PNG icon (I'm calling it in updateDataUsage() method). Can someone please tell me how to convert the bitmap to PNG and pass it to setSmallIcon()?