1
+ package com .hoxfon .react .RNTwilioVoice ;
2
+
3
+ import android .annotation .TargetApi ;
4
+ import android .app .Notification ;
5
+ import android .app .NotificationChannel ;
6
+ import android .app .NotificationManager ;
7
+ import android .app .PendingIntent ;
8
+ import android .app .Service ;
9
+ import android .content .Context ;
10
+ import android .content .Intent ;
11
+ import android .graphics .Color ;
12
+ import android .os .Build ;
13
+ import android .os .Bundle ;
14
+ import android .os .IBinder ;
15
+ import android .util .Log ;
16
+
17
+ import androidx .core .app .NotificationCompat ;
18
+ import androidx .lifecycle .Lifecycle ;
19
+ import androidx .lifecycle .ProcessLifecycleOwner ;
20
+ import androidx .localbroadcastmanager .content .LocalBroadcastManager ;
21
+
22
+ import com .twilio .voice .CallInvite ;
23
+
24
+ import static com .hoxfon .react .RNTwilioVoice .CallNotificationManager .getMainActivityClass ;
25
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_ACCEPT ;
26
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_CANCEL_CALL ;
27
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_INCOMING_CALL ;
28
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_INCOMING_CALL_NOTIFICATION ;
29
+ import static com .hoxfon .react .RNTwilioVoice .Constants .ACTION_REJECT ;
30
+ import static com .hoxfon .react .RNTwilioVoice .Constants .CALL_SID_KEY ;
31
+ import static com .hoxfon .react .RNTwilioVoice .Constants .INCOMING_CALL_INVITE ;
32
+ import static com .hoxfon .react .RNTwilioVoice .Constants .INCOMING_CALL_NOTIFICATION_ID ;
33
+
34
+ public class IncomingCallNotificationService extends Service {
35
+
36
+ private static final String TAG = IncomingCallNotificationService .class .getSimpleName ();
37
+
38
+ @ Override
39
+ public int onStartCommand (Intent intent , int flags , int startId ) {
40
+ String action = intent .getAction ();
41
+
42
+ CallInvite callInvite = intent .getParcelableExtra (INCOMING_CALL_INVITE );
43
+ int notificationId = intent .getIntExtra (INCOMING_CALL_NOTIFICATION_ID , 0 );
44
+
45
+ switch (action ) {
46
+ case ACTION_INCOMING_CALL :
47
+ handleIncomingCall (callInvite , notificationId );
48
+ break ;
49
+ case ACTION_ACCEPT :
50
+ accept (callInvite , notificationId );
51
+ break ;
52
+ case ACTION_REJECT :
53
+ reject (callInvite );
54
+ break ;
55
+ case ACTION_CANCEL_CALL :
56
+ handleCancelledCall (intent );
57
+ break ;
58
+ default :
59
+ break ;
60
+ }
61
+ return START_NOT_STICKY ;
62
+ }
63
+
64
+ @ Override
65
+ public IBinder onBind (Intent intent ) {
66
+ return null ;
67
+ }
68
+
69
+ private Notification createNotification (CallInvite callInvite , int notificationId , int channelImportance ) {
70
+ Intent intent = new Intent (this , getMainActivityClass (this ));
71
+ intent .setAction (ACTION_INCOMING_CALL_NOTIFICATION );
72
+ intent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
73
+ intent .putExtra (INCOMING_CALL_INVITE , callInvite );
74
+ intent .addFlags (Intent .FLAG_ACTIVITY_CLEAR_TOP );
75
+ PendingIntent pendingIntent =
76
+ PendingIntent .getActivity (this , notificationId , intent , PendingIntent .FLAG_UPDATE_CURRENT );
77
+ /*
78
+ * Pass the notification id and call sid to use as an identifier to cancel the
79
+ * notification later
80
+ */
81
+ Bundle extras = new Bundle ();
82
+ extras .putString (CALL_SID_KEY , callInvite .getCallSid ());
83
+
84
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
85
+ return buildNotification (callInvite .getFrom () + " is calling." ,
86
+ pendingIntent ,
87
+ extras ,
88
+ callInvite ,
89
+ notificationId ,
90
+ createChannel (channelImportance ));
91
+ } else {
92
+ return new NotificationCompat .Builder (this )
93
+ .setSmallIcon (R .drawable .ic_call_white_24dp )
94
+ .setContentTitle ("Incoming call" )
95
+ .setContentText (callInvite .getFrom () + " is calling." )
96
+ .setAutoCancel (true )
97
+ .setExtras (extras )
98
+ .setContentIntent (pendingIntent )
99
+ .setGroup ("test_app_notification" )
100
+ .setColor (Color .rgb (214 , 10 , 37 )).build ();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Build a notification.
106
+ *
107
+ * @param text the text of the notification
108
+ * @param pendingIntent the body, pending intent for the notification
109
+ * @param extras extras passed with the notification
110
+ * @return the builder
111
+ */
112
+ @ TargetApi (Build .VERSION_CODES .O )
113
+ private Notification buildNotification (String text , PendingIntent pendingIntent , Bundle extras ,
114
+ final CallInvite callInvite ,
115
+ int notificationId ,
116
+ String channelId ) {
117
+ Intent rejectIntent = new Intent (getApplicationContext (), IncomingCallNotificationService .class );
118
+ rejectIntent .setAction (ACTION_REJECT );
119
+ rejectIntent .putExtra (INCOMING_CALL_INVITE , callInvite );
120
+ rejectIntent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
121
+ PendingIntent piRejectIntent = PendingIntent .getService (getApplicationContext (), 0 , rejectIntent , PendingIntent .FLAG_UPDATE_CURRENT );
122
+
123
+ Intent acceptIntent = new Intent (getApplicationContext (), IncomingCallNotificationService .class );
124
+ acceptIntent .setAction (ACTION_ACCEPT );
125
+ acceptIntent .putExtra (INCOMING_CALL_INVITE , callInvite );
126
+ acceptIntent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
127
+ PendingIntent piAcceptIntent = PendingIntent .getService (getApplicationContext (), 0 , acceptIntent , PendingIntent .FLAG_UPDATE_CURRENT );
128
+
129
+ Notification .Builder builder =
130
+ new Notification .Builder (getApplicationContext (), channelId )
131
+ .setSmallIcon (R .drawable .ic_call_white_24dp )
132
+ .setContentTitle ("Incoming call" )
133
+ .setContentText (text )
134
+ .setCategory (Notification .CATEGORY_CALL )
135
+ .setFullScreenIntent (pendingIntent , true )
136
+ .setExtras (extras )
137
+ .setAutoCancel (true )
138
+ .addAction (android .R .drawable .ic_menu_delete , getString (R .string .decline ), piRejectIntent )
139
+ .addAction (android .R .drawable .ic_menu_call , getString (R .string .answer ), piAcceptIntent )
140
+ .setFullScreenIntent (pendingIntent , true );
141
+
142
+ return builder .build ();
143
+ }
144
+
145
+ @ TargetApi (Build .VERSION_CODES .O )
146
+ private String createChannel (int channelImportance ) {
147
+ NotificationChannel callInviteChannel = new NotificationChannel (Constants .VOICE_CHANNEL_HIGH_IMPORTANCE ,
148
+ "Primary Voice Channel" , NotificationManager .IMPORTANCE_HIGH );;
149
+ String channelId = Constants .VOICE_CHANNEL_HIGH_IMPORTANCE ;
150
+
151
+ if (channelImportance == NotificationManager .IMPORTANCE_LOW ) {
152
+ callInviteChannel = new NotificationChannel (Constants .VOICE_CHANNEL_LOW_IMPORTANCE ,
153
+ "Primary Voice Channel" , NotificationManager .IMPORTANCE_LOW );;
154
+ channelId = Constants .VOICE_CHANNEL_LOW_IMPORTANCE ;
155
+ }
156
+ callInviteChannel .setLightColor (Color .GREEN );
157
+ callInviteChannel .setLockscreenVisibility (Notification .VISIBILITY_PRIVATE );
158
+ NotificationManager notificationManager = (NotificationManager ) getSystemService (Context .NOTIFICATION_SERVICE );
159
+ notificationManager .createNotificationChannel (callInviteChannel );
160
+
161
+ return channelId ;
162
+ }
163
+
164
+ private void accept (CallInvite callInvite , int notificationId ) {
165
+ endForeground ();
166
+ Intent activeCallIntent = new Intent (this , getMainActivityClass (this ));
167
+ activeCallIntent .addFlags (Intent .FLAG_ACTIVITY_SINGLE_TOP );
168
+ activeCallIntent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
169
+ activeCallIntent .putExtra (INCOMING_CALL_INVITE , callInvite );
170
+ activeCallIntent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
171
+ activeCallIntent .setAction (ACTION_ACCEPT );
172
+ startActivity (activeCallIntent );
173
+ }
174
+
175
+ private void reject (CallInvite callInvite ) {
176
+ endForeground ();
177
+ callInvite .reject (getApplicationContext ());
178
+ }
179
+
180
+ private void handleCancelledCall (Intent intent ) {
181
+ endForeground ();
182
+ LocalBroadcastManager .getInstance (this ).sendBroadcast (intent );
183
+ }
184
+
185
+ private void handleIncomingCall (CallInvite callInvite , int notificationId ) {
186
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
187
+ setCallInProgressNotification (callInvite , notificationId );
188
+ }
189
+ sendCallInviteToActivity (callInvite , notificationId );
190
+ }
191
+
192
+ private void endForeground () {
193
+ stopForeground (true );
194
+ }
195
+
196
+ private void setCallInProgressNotification (CallInvite callInvite , int notificationId ) {
197
+ if (isAppVisible ()) {
198
+ Log .i (TAG , "setCallInProgressNotification - app is visible." );
199
+ startForeground (notificationId , createNotification (callInvite , notificationId , NotificationManager .IMPORTANCE_LOW ));
200
+ } else {
201
+ Log .i (TAG , "setCallInProgressNotification - app is NOT visible." );
202
+ startForeground (notificationId , createNotification (callInvite , notificationId , NotificationManager .IMPORTANCE_HIGH ));
203
+ }
204
+ }
205
+
206
+ /*
207
+ * Send the CallInvite to the VoiceActivity. Start the activity if it is not running already.
208
+ */
209
+ private void sendCallInviteToActivity (CallInvite callInvite , int notificationId ) {
210
+ if (Build .VERSION .SDK_INT >= 29 && !isAppVisible ()) {
211
+ return ;
212
+ }
213
+ Intent intent = new Intent (this , getMainActivityClass (this ));
214
+ intent .setAction (ACTION_INCOMING_CALL );
215
+ intent .putExtra (INCOMING_CALL_NOTIFICATION_ID , notificationId );
216
+ intent .putExtra (INCOMING_CALL_INVITE , callInvite );
217
+ intent .addFlags (Intent .FLAG_ACTIVITY_SINGLE_TOP );
218
+ intent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
219
+ this .startActivity (intent );
220
+ }
221
+
222
+ private boolean isAppVisible () {
223
+ return ProcessLifecycleOwner
224
+ .get ()
225
+ .getLifecycle ()
226
+ .getCurrentState ()
227
+ .isAtLeast (Lifecycle .State .STARTED );
228
+ }
229
+ }
0 commit comments