1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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,
)
}
}