~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-android

1 collaborator
0
1
main
66 lines · 2.5 KB Raw History
package social.grain

import android.app.PendingIntent
import android.content.Intent
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import social.grain.data.push.GrainMessagingService

/**
 * The notification small icon.
 *
 * The system keeps only a small icon's alpha channel, fills that mask with the
 * tint from `setColor`, and throws the colours away. So artwork posts as a
 * silhouette of its own opaque area — and the launcher icon, a full-bleed
 * photograph with every pixel at alpha 255, posted as a solid block. Nothing
 * fails when that happens; the notification just looks broken.
 */
@RunWith(AndroidJUnit4::class)
class NotificationIconTest {

    private val context = InstrumentationRegistry.getInstrumentation().targetContext

    private fun pendingIntent() = PendingIntent.getActivity(
        context,
        0,
        Intent(context, MainActivity::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

    /**
     * A usable mask has both transparent and opaque pixels. All-opaque is the
     * bug above; all-transparent would post nothing at all.
     */
    @Test
    fun smallIconIsAMaskNotArtwork() {
        val notification =
            GrainMessagingService.notification(context, "Title", "Body", pendingIntent())
        val icon = notification.smallIcon.loadDrawable(context)!!
        val bitmap = icon.toBitmap(96, 96)

        val pixels = IntArray(96 * 96).also { bitmap.getPixels(it, 0, 96, 0, 0, 96, 96) }
        val opaque = pixels.count { (it ushr 24) > 250 }
        val clear = pixels.count { (it ushr 24) < 5 }

        assertTrue("Small icon has no transparent pixels — it will post as a solid block", clear > 0)
        assertTrue("Small icon is entirely transparent — nothing would show", opaque > 0)
        // The glyph is a letter in a square, so most of the canvas is empty.
        assertTrue("Small icon covers ${opaque * 100 / pixels.size}% of its canvas", clear > opaque)
    }

    @Test
    fun notificationCarriesTheAccentTint() {
        val notification =
            GrainMessagingService.notification(context, "Title", "Body", pendingIntent())
        assertEquals(
            ContextCompat.getColor(context, R.color.notification_accent),
            notification.color,
        )
    }
}