1

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:

Android statsu bar icon text

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()?

SemicolonSpace
  • 1,007
  • 11
  • 20
  • Related: [16055073](https://stackoverflow.com/q/16055073/16586783), [23836920](https://stackoverflow.com/q/23836920/16586783) – Arun Kumar B Feb 15 '22 at 07:53
  • could you also share your view xml? According to the ArrayIndexOutOfBoundsException and the previous inflation it seems that this is a view topic... – Tobias Lukoschek Feb 15 '22 at 12:46
  • if you have debugged that the issue is with using bitmap in place of png, then try once with passing png from drawable folder to cross check this onc and let me know your findings – Astha Garg Feb 15 '22 at 13:46
  • @TobiasLukoschek I'm not using any xml. – SemicolonSpace Feb 16 '22 at 07:02
  • @AsthaGarg I searched for the issue and most accepted answers say pass the png icon from drawable to setSmallIcon(). That is why I said how to convert icon to png format. I'm using the app on my physical device for the last 1 year and I never got this crash. I cannot reproduce it also. – SemicolonSpace Feb 16 '22 at 07:06
  • Good that you are not facing this issue anymore, so are you using the bitmap or png icon ? – Astha Garg Feb 16 '22 at 07:48
  • 1
    @AsthaGarg No. I'm NOT getting this crash ON MY PHYSICAL DEVICE. I'm getting the crash reports on Firebase. – SemicolonSpace Feb 16 '22 at 08:16

0 Answers0