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