Skip to content

Commit 71d7019

Browse files
Merge pull request #14 from leandrosimoes/feature/notification-icon-and-image
Feature/notification icon and image
2 parents 61a48c8 + 1b01d5d commit 71d7019

File tree

6 files changed

+212
-40
lines changed

6 files changed

+212
-40
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ RNAndroidNotificationListener.requestPermission()
6363
const headlessNotificationListener = async ({ notification }) => {/**
6464
* This notification is a JSON string in the follow format:
6565
* {
66+
* "time": string,
6667
* "app": string,
6768
* "title": string,
6869
* "titleBig": string,
@@ -78,7 +79,9 @@ const headlessNotificationListener = async ({ notification }) => {/**
7879
* "title": string,
7980
* "text": string
8081
* }
81-
* ]
82+
* ],
83+
* "icon": string (base64),
84+
* "image": string (base64), // WARNING! THIS MAY NOT WORK FOR SOME APPLICATIONS SUCH TELEGRAM AND WHATSAPP
8285
* }
8386
*
8487
* Note that this properties depends on the sender configuration

android/src/main/java/com/lesimoes/androidnotificationlistener/RNAndroidNotificationListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void onNotificationPosted(StatusBarNotification sbn) {
2727

2828
Intent serviceIntent = new Intent(context, RNAndroidNotificationListenerHeadlessJsTaskService.class);
2929

30-
RNNotification notification = new RNNotification(sbn);
30+
RNNotification notification = new RNNotification(context, sbn);
3131

3232
Gson gson = new Gson();
3333
String serializedNotification = gson.toJson(notification);
Lines changed: 134 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
package com.lesimoes.androidnotificationlistener;
22

3+
import android.graphics.BitmapFactory;
34
import android.service.notification.StatusBarNotification;
45
import android.text.TextUtils;
56
import android.app.Notification;
67
import android.util.Log;
78
import java.util.ArrayList;
9+
import android.content.pm.PackageManager;
10+
import android.content.res.Resources;
11+
import android.graphics.drawable.Drawable;
12+
import android.graphics.Bitmap;
13+
import java.io.ByteArrayOutputStream;
14+
import android.util.Base64;
15+
import android.content.Context;
16+
import java.lang.Exception;
17+
import android.graphics.drawable.Icon;
18+
import android.graphics.drawable.BitmapDrawable;
819

920
public class RNNotification {
1021
private static final String TAG = "RNAndroidNotificationListener";
@@ -20,35 +31,52 @@ public class RNNotification {
2031
protected String audioContentsURI;
2132
protected String imageBackgroundURI;
2233
protected String extraInfoText;
34+
protected String icon;
35+
protected String image;
36+
protected String time;
2337

24-
public RNNotification(StatusBarNotification sbn) {
38+
public RNNotification(Context context, StatusBarNotification sbn) {
2539
Notification notification = sbn.getNotification();
2640

2741
if (notification != null && notification.extras != null) {
2842
String packageName = sbn.getPackageName();
2943

30-
CharSequence titleChars = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
31-
CharSequence titleBigChars = notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
32-
CharSequence textChars = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
33-
CharSequence subTextChars = notification.extras.getCharSequence(Notification.EXTRA_SUB_TEXT);
34-
CharSequence summaryTextChars = notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT);
35-
CharSequence bigTextChars = notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
36-
CharSequence audioContentsURIChars = notification.extras.getCharSequence(Notification.EXTRA_AUDIO_CONTENTS_URI);
37-
CharSequence imageBackgroundURIChars = notification.extras.getCharSequence(Notification.EXTRA_BACKGROUND_IMAGE_URI);
38-
CharSequence extraInfoTextChars = notification.extras.getCharSequence(Notification.EXTRA_INFO_TEXT);
39-
CharSequence[] lines = notification.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
40-
44+
this.time = Long.toString(sbn.getPostTime());
4145
this.app = TextUtils.isEmpty(packageName) ? "Unknown App" : packageName;
42-
this.title = titleChars != null ? titleChars.toString().trim() : "";
43-
this.titleBig = titleBigChars != null ? titleBigChars.toString().trim() : "";
44-
this.text = textChars != null ? textChars.toString().trim() : "";
45-
this.subText = subTextChars != null ? subTextChars.toString().trim() : "";
46-
this.summaryText = summaryTextChars != null ? summaryTextChars.toString().trim() : "";
47-
this.bigText = bigTextChars != null ? bigTextChars.toString().trim() : "";
48-
this.audioContentsURI = audioContentsURIChars != null ? audioContentsURIChars.toString().trim() : "";
49-
this.imageBackgroundURI = imageBackgroundURIChars != null ? imageBackgroundURIChars.toString().trim() : "";
50-
this.extraInfoText = extraInfoTextChars != null ? extraInfoTextChars.toString().trim() : "";
51-
this.groupedMessages = new ArrayList<RNGroupedNotification>();
46+
this.title = this.getPropertySafely(notification, Notification.EXTRA_TITLE);
47+
this.titleBig = this.getPropertySafely(notification, Notification.EXTRA_TITLE_BIG);
48+
this.text = this.getPropertySafely(notification, Notification.EXTRA_TEXT);
49+
this.subText = this.getPropertySafely(notification, Notification.EXTRA_SUB_TEXT);
50+
this.summaryText = this.getPropertySafely(notification, Notification.EXTRA_SUMMARY_TEXT);
51+
this.bigText = this.getPropertySafely(notification, Notification.EXTRA_BIG_TEXT);
52+
this.audioContentsURI = this.getPropertySafely(notification, Notification.EXTRA_AUDIO_CONTENTS_URI);
53+
this.imageBackgroundURI = this.getPropertySafely(notification, Notification.EXTRA_BACKGROUND_IMAGE_URI);
54+
this.extraInfoText = this.getPropertySafely(notification, Notification.EXTRA_INFO_TEXT);
55+
56+
this.icon = this.getNotificationIcon(context, notification, packageName);
57+
this.image = this.getNotificationImage(notification);
58+
this.groupedMessages = this.getGroupedNotifications(notification);
59+
} else {
60+
Log.d(TAG, "The notification received has no data");
61+
}
62+
}
63+
64+
private String getPropertySafely(Notification notification, String propKey) {
65+
try {
66+
CharSequence propCharSequence = notification.extras.getCharSequence(propKey);
67+
68+
return propCharSequence == null ? "" : propCharSequence.toString().trim();
69+
} catch (Exception e) {
70+
Log.d(TAG, e.getMessage());
71+
return "";
72+
}
73+
}
74+
75+
private ArrayList<RNGroupedNotification> getGroupedNotifications(Notification notification) {
76+
ArrayList<RNGroupedNotification> result = new ArrayList<RNGroupedNotification>();
77+
78+
try {
79+
CharSequence[] lines = notification.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
5280

5381
if (lines != null && lines.length > 0) {
5482
for (CharSequence line : lines) {
@@ -58,8 +86,90 @@ public RNNotification(StatusBarNotification sbn) {
5886
}
5987
}
6088
}
61-
} else {
62-
Log.d(TAG, "The notification received has no data");
89+
90+
return result;
91+
} catch (Exception e) {
92+
Log.d(TAG, e.getMessage());
93+
return result;
94+
}
95+
}
96+
97+
private String getNotificationIcon(Context context, Notification notification, String packageName) {
98+
try {
99+
int iconId = notification.extras.getInt(Notification.EXTRA_SMALL_ICON);
100+
101+
String result = "";
102+
103+
if (iconId <= 0) {
104+
Icon iconInstance = notification.getSmallIcon();
105+
Drawable iconDrawable = iconInstance.loadDrawable(context);
106+
Bitmap iconBitmap = ((BitmapDrawable) iconDrawable).getBitmap();
107+
108+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
109+
iconBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
110+
111+
result = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
112+
} else {
113+
PackageManager manager = context.getPackageManager();
114+
Resources resources = manager.getResourcesForApplication(packageName);
115+
116+
Drawable iconDrawable = resources.getDrawable(iconId);
117+
Bitmap iconBitmap = ((BitmapDrawable) iconDrawable).getBitmap();
118+
119+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
120+
iconBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
121+
122+
result = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
123+
}
124+
125+
return TextUtils.isEmpty(result) ? result : "data:image/png;base64," + result;
126+
} catch (Exception e) {
127+
Log.d(TAG, e.getMessage());
128+
return "";
129+
}
130+
}
131+
132+
private String getNotificationImage(Notification notification) {
133+
try {
134+
if (!notification.extras.containsKey(Notification.EXTRA_PICTURE)) return "";
135+
136+
Bitmap imageBitmap = (Bitmap) notification.extras.get(Notification.EXTRA_PICTURE);
137+
138+
BitmapFactory.Options options = new BitmapFactory.Options();
139+
options.inSampleSize = this.calculateInSampleSize(options, 100,100);
140+
options.inJustDecodeBounds = false;
141+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
142+
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream);
143+
144+
String result = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
145+
146+
return TextUtils.isEmpty(result) ? result : "data:image/png;base64," + result;
147+
} catch (Exception e) {
148+
Log.d(TAG, e.getMessage());
149+
return "";
63150
}
64151
}
152+
153+
public int calculateInSampleSize(
154+
BitmapFactory.Options options, int reqWidth, int reqHeight) {
155+
// Raw height and width of image
156+
final int height = options.outHeight;
157+
final int width = options.outWidth;
158+
int inSampleSize = 1;
159+
160+
if (height > reqHeight || width > reqWidth) {
161+
162+
final int halfHeight = height / 2;
163+
final int halfWidth = width / 2;
164+
165+
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
166+
// height and width larger than the requested height and width.
167+
while ((halfHeight / inSampleSize) >= reqHeight
168+
&& (halfWidth / inSampleSize) >= reqWidth) {
169+
inSampleSize *= 2;
170+
}
171+
}
172+
173+
return inSampleSize;
174+
}
65175
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-native-android-notification-listener",
33
"title": "React Native Android Notification Listener",
4-
"version": "3.0.0",
4+
"version": "3.1.0",
55
"description": "React Native Android Notification Listener - Listen for status bar notifications from all applications",
66
"main": "index.js",
77
"typings": "index.d.ts",

sample/App.js

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect } from 'react'
2-
import { SafeAreaView, Text, Button, AppState, View, FlatList } from 'react-native'
2+
import { SafeAreaView, Text, Image, Button, AppState, View, FlatList } from 'react-native'
33
import RNAndroidNotificationListener from 'react-native-android-notification-listener'
44
import AsyncStorage from '@react-native-async-storage/async-storage'
55

@@ -8,6 +8,7 @@ import styles from './App.styles.js'
88
let interval = null
99

1010
const Notification = ({
11+
time,
1112
app,
1213
title,
1314
titleBig,
@@ -18,19 +19,40 @@ const Notification = ({
1819
audioContentsURI,
1920
imageBackgroundURI,
2021
extraInfoText,
22+
icon,
23+
image,
2124
}) => {
2225
return (
23-
<View style={styles.notification}>
24-
<Text>{`app: ${app}`}</Text>
25-
<Text>{`title: ${title}`}</Text>
26-
<Text>{`text: ${text}`}</Text>
27-
{!!titleBig && <Text>{`titleBig: ${titleBig}`}</Text>}
28-
{!!subText && <Text>{`subText: ${subText}`}</Text>}
29-
{!!summaryText && <Text>{`summaryText: ${summaryText}`}</Text>}
30-
{!!bigText && <Text>{`bigText: ${bigText}`}</Text>}
31-
{!!audioContentsURI && <Text>{`audioContentsURI: ${audioContentsURI}`}</Text>}
32-
{!!imageBackgroundURI && <Text>{`imageBackgroundURI: ${imageBackgroundURI}`}</Text>}
33-
{!!extraInfoText && <Text>{`extraInfoText: ${extraInfoText}`}</Text>}
26+
<View style={styles.notificationWrapper}>
27+
<View style={styles.notification}>
28+
<View style={styles.imagesWrapper}>
29+
{!!icon && (
30+
<View style={styles.notificationIconWrapper}>
31+
<Image source={{ uri: icon }} style={styles.notificationIcon} />
32+
</View>
33+
)}
34+
{!!image && (
35+
<View style={styles.notificationImageWrapper}>
36+
<Image source={{ uri: image }} style={styles.notificationImage} />
37+
</View>
38+
)}
39+
</View>
40+
<View style={styles.notificationInfoWrapper}>
41+
<Text>{`app: ${app}`}</Text>
42+
<Text>{`title: ${title}`}</Text>
43+
<Text>{`text: ${text}`}</Text>
44+
{!!time && <Text>{`time: ${time}`}</Text>}
45+
{!!titleBig && <Text>{`titleBig: ${titleBig}`}</Text>}
46+
{!!subText && <Text>{`subText: ${subText}`}</Text>}
47+
{!!summaryText && <Text>{`summaryText: ${summaryText}`}</Text>}
48+
{!!bigText && <Text>{`bigText: ${bigText}`}</Text>}
49+
{!!audioContentsURI && <Text>{`audioContentsURI: ${audioContentsURI}`}</Text>}
50+
{!!imageBackgroundURI && (
51+
<Text>{`imageBackgroundURI: ${imageBackgroundURI}`}</Text>
52+
)}
53+
{!!extraInfoText && <Text>{`extraInfoText: ${extraInfoText}`}</Text>}
54+
</View>
55+
</View>
3456
</View>
3557
)
3658
}

sample/App.styles.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,51 @@ export default StyleSheet.create({
2020
alignItems: 'center',
2121
marginBottom: 20,
2222
},
23-
notification: {
23+
notificationWrapper: {
24+
flexDirection: 'column',
2425
width: 300,
2526
backgroundColor: '#f2f2f2',
2627
padding: 20,
2728
marginTop: 20,
2829
borderRadius: 5,
2930
elevation: 2,
3031
},
32+
notification: {
33+
flexDirection: 'row',
34+
},
35+
imagesWrapper: {
36+
flexDirection: 'column',
37+
},
38+
notificationInfoWrapper: {
39+
flex: 1,
40+
},
41+
notificationIconWrapper: {
42+
backgroundColor: '#aaa',
43+
width: 50,
44+
height: 50,
45+
borderRadius: 25,
46+
alignItems: 'center',
47+
marginRight: 15,
48+
justifyContent: 'center',
49+
},
50+
notificationIcon: {
51+
width: 30,
52+
height: 30,
53+
resizeMode: 'contain',
54+
},
55+
notificationImageWrapper: {
56+
width: 50,
57+
height: 50,
58+
borderRadius: 25,
59+
alignItems: 'center',
60+
marginRight: 15,
61+
justifyContent: 'center',
62+
},
63+
notificationImage: {
64+
width: 40,
65+
height: 40,
66+
resizeMode: 'contain',
67+
},
3168
buttomWrapper: {
3269
flex: 1,
3370
justifyContent: 'center',

0 commit comments

Comments
 (0)