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