@capgo/capacitor-twilio-voice 7.3.0-alpha.1 → 7.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,6 @@ package ee.forgr.capacitor_twilio_voice;
2
2
 
3
3
  import android.Manifest;
4
4
  import android.app.Activity;
5
- import android.app.AlertDialog;
6
- import android.app.KeyguardManager;
7
- import android.app.KeyguardManager;
8
5
  import android.app.NotificationChannel;
9
6
  import android.app.NotificationManager;
10
7
  import android.app.PendingIntent;
@@ -24,18 +21,13 @@ import android.os.Build;
24
21
  import android.os.IBinder;
25
22
  import android.os.VibrationEffect;
26
23
  import android.os.Vibrator;
27
- import android.provider.Settings;
28
24
  import android.util.Base64;
29
25
  import android.util.Log;
30
- import android.widget.Toast;
31
- import androidx.activity.result.ActivityResultLauncher;
32
- import androidx.activity.result.contract.ActivityResultContracts;
33
26
  import androidx.annotation.NonNull;
34
27
  import androidx.annotation.Nullable;
35
28
  import androidx.core.app.ActivityCompat;
36
29
  import androidx.core.app.NotificationCompat;
37
30
  import androidx.core.app.NotificationManagerCompat;
38
- import androidx.core.content.ContextCompat;
39
31
  import com.getcapacitor.JSArray;
40
32
  import com.getcapacitor.JSObject;
41
33
  import com.getcapacitor.Plugin;
@@ -75,11 +67,11 @@ import org.json.JSONObject;
75
67
  )
76
68
  public class CapacitorTwilioVoicePlugin extends Plugin {
77
69
 
70
+ private final String PLUGIN_VERSION = "7.3.2";
71
+
78
72
  private static final String TAG = "CapacitorTwilioVoice";
79
73
  private static final String PREF_ACCESS_TOKEN = "twilio_access_token";
80
74
  private static final String PREF_FCM_TOKEN = "twilio_fcm_token";
81
- private static final String PREFS_NAME = "capacitor_twilio_voice_prefs";
82
- private static final String PREF_MIC_PERMISSION_REQUESTED = "mic_permission_requested";
83
75
 
84
76
  public static CapacitorTwilioVoicePlugin instance;
85
77
 
@@ -111,21 +103,6 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
111
103
  private static final int REQUEST_CODE_RECORD_AUDIO_FOR_ACCEPT = 2001;
112
104
  private String pendingCallSidForPermission;
113
105
 
114
- private enum PendingPermissionAction {
115
- NONE,
116
- OUTGOING_CALL,
117
- ACCEPT_CALL
118
- }
119
-
120
- private PendingPermissionAction pendingPermissionAction = PendingPermissionAction.NONE;
121
- private PluginCall pendingOutgoingCall;
122
- private String pendingOutgoingTo;
123
- private PluginCall pendingPermissionCall;
124
- private long permissionRequestTimestamp = 0L;
125
- private int permissionAttemptCount = 0;
126
- private boolean awaitingSettingsResult = false;
127
- private ActivityResultLauncher<String[]> micPermissionLauncher;
128
-
129
106
  // Voice Call Service
130
107
  private VoiceCallService voiceCallService;
131
108
  private boolean isServiceBound = false;
@@ -173,7 +150,6 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
173
150
  data.put("error", error.getMessage());
174
151
  }
175
152
  notifyListeners("callDisconnected", data);
176
- moveAppToBackgroundIfLocked();
177
153
  }
178
154
 
179
155
  @Override
@@ -264,11 +240,6 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
264
240
  bindToVoiceCallService();
265
241
 
266
242
  Log.d(TAG, "CapacitorTwilioVoice plugin loaded");
267
-
268
- micPermissionLauncher = getBridge().registerForActivityResult(
269
- new ActivityResultContracts.RequestMultiplePermissions(),
270
- (permissions) -> handleMicPermissionResult(permissions)
271
- );
272
243
  }
273
244
 
274
245
  private void bindToVoiceCallService() {
@@ -301,12 +272,12 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
301
272
 
302
273
  // Delay the auto-accept slightly to ensure plugin is fully loaded
303
274
  new android.os.Handler().postDelayed(
304
- () -> {
305
- Log.d(TAG, "Auto-accepting call: " + callSid);
306
- ensureMicPermissionThenAccept(callSid);
307
- },
308
- 500
309
- );
275
+ () -> {
276
+ Log.d(TAG, "Auto-accepting call: " + callSid);
277
+ ensureMicPermissionThenAccept(callSid);
278
+ },
279
+ 500
280
+ );
310
281
  }
311
282
  }
312
283
  }
@@ -340,20 +311,20 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
340
311
  if (callInvite != null) {
341
312
  // Delay sending the event to ensure JavaScript is ready
342
313
  new android.os.Handler().postDelayed(
343
- () -> {
344
- Log.d(TAG, "Sending incoming call event to JavaScript: " + callSid);
345
-
346
- JSObject data = new JSObject();
347
- data.put("callSid", callSid);
348
- data.put("from", callFrom);
349
- data.put("to", callInvite.getTo());
350
- data.put("callerName", callerName != null ? callerName : callFrom);
351
- data.put("openedFromNotification", true);
352
-
353
- notifyListeners("callInviteReceived", data);
354
- },
355
- 1000
356
- ); // Give JavaScript more time to initialize
314
+ () -> {
315
+ Log.d(TAG, "Sending incoming call event to JavaScript: " + callSid);
316
+
317
+ JSObject data = new JSObject();
318
+ data.put("callSid", callSid);
319
+ data.put("from", callFrom);
320
+ data.put("to", callInvite.getTo());
321
+ data.put("callerName", callerName != null ? callerName : callFrom);
322
+ data.put("openedFromNotification", true);
323
+
324
+ notifyListeners("callInviteReceived", data);
325
+ },
326
+ 1000
327
+ ); // Give JavaScript more time to initialize
357
328
  } else {
358
329
  Log.w(TAG, "Call invite not found for SID: " + callSid + " (may have been cancelled)");
359
330
  }
@@ -385,44 +356,6 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
385
356
  Log.d(TAG, "CapacitorTwilioVoice plugin destroyed");
386
357
  }
387
358
 
388
- @Override
389
- protected void handleOnResume() {
390
- super.handleOnResume();
391
- Log.d(
392
- TAG,
393
- "handleOnResume: hasPermission=" +
394
- hasMicrophonePermission() +
395
- ", awaitingSettings=" +
396
- awaitingSettingsResult +
397
- ", pendingCall=" +
398
- (pendingPermissionCall != null) +
399
- ", pendingAction=" +
400
- pendingPermissionAction
401
- );
402
- if (hasMicrophonePermission()) {
403
- if (pendingPermissionAction != PendingPermissionAction.NONE || pendingPermissionCall != null) {
404
- awaitingSettingsResult = false;
405
- Log.d(TAG, "handleOnResume: permission granted, resuming pending flow");
406
- handleMicrophonePermissionGranted();
407
- }
408
- } else if (awaitingSettingsResult && pendingPermissionCall != null) {
409
- awaitingSettingsResult = false;
410
- Log.d(TAG, "handleOnResume: permission still denied after returning from settings");
411
- JSObject ret = new JSObject();
412
- ret.put("granted", false);
413
- pendingPermissionCall.setKeepAlive(false);
414
- pendingPermissionCall.resolve(ret);
415
- pendingPermissionCall = null;
416
- } else if (awaitingSettingsResult && pendingPermissionAction == PendingPermissionAction.ACCEPT_CALL) {
417
- awaitingSettingsResult = false;
418
- Log.d(TAG, "handleOnResume: settings return without permission for accept flow");
419
- handlePermissionFailure();
420
- } else if (pendingPermissionCall != null) {
421
- Log.d(TAG, "handleOnResume: permission denied from dialog, invoking fallback handling");
422
- handleMicrophonePermissionDenied();
423
- }
424
- }
425
-
426
359
  public void setInjectedContext(Context injectedContext) {
427
360
  this.injectedContext = injectedContext;
428
361
  }
@@ -455,19 +388,33 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
455
388
  }
456
389
 
457
390
  private void ensureMicPermissionThenAccept(String callSid) {
458
- Log.d(TAG, "ensureMicPermissionThenAccept: callSid=" + callSid);
459
- if (hasMicrophonePermission()) {
460
- Log.d(TAG, "ensureMicPermissionThenAccept: permission granted, proceeding");
391
+ Context context = getSafeContext();
392
+ boolean granted =
393
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
394
+ if (granted) {
461
395
  proceedAcceptCall(callSid);
462
396
  return;
463
397
  }
464
398
 
399
+ // Remember callSid and request via Activity runtime permission API
465
400
  pendingCallSidForPermission = callSid;
466
- pendingPermissionAction = PendingPermissionAction.ACCEPT_CALL;
467
- permissionAttemptCount = 0;
468
- awaitingSettingsResult = false;
469
- Log.d(TAG, "ensureMicPermissionThenAccept: requesting permission before accepting call");
470
- requestMicrophonePermission();
401
+ Activity activity = getActivity();
402
+ if (activity != null) {
403
+ ActivityCompat.requestPermissions(
404
+ activity,
405
+ new String[] { Manifest.permission.RECORD_AUDIO },
406
+ REQUEST_CODE_RECORD_AUDIO_FOR_ACCEPT
407
+ );
408
+ } else if (mainActivityClass != null) {
409
+ // No current activity; bring app to foreground where permission can be requested
410
+ Intent launchIntent = new Intent(context, mainActivityClass);
411
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
412
+ launchIntent.putExtra("AUTO_ACCEPT_CALL", true);
413
+ launchIntent.putExtra(EXTRA_CALL_SID, callSid);
414
+ context.startActivity(launchIntent);
415
+ } else {
416
+ Log.w(TAG, "No activity available to request RECORD_AUDIO permission");
417
+ }
471
418
  }
472
419
 
473
420
  // Helper to actually start the service once permission is granted
@@ -488,12 +435,6 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
488
435
  Log.d(TAG, "Call acceptance started via service (permission granted)");
489
436
  } catch (Exception e) {
490
437
  Log.e(TAG, "Error accepting call via service", e);
491
- } finally {
492
- pendingCallSidForPermission = null;
493
- if (pendingPermissionAction == PendingPermissionAction.ACCEPT_CALL) {
494
- pendingPermissionAction = PendingPermissionAction.NONE;
495
- }
496
- permissionAttemptCount = 0;
497
438
  }
498
439
  }
499
440
 
@@ -742,32 +683,11 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
742
683
  return;
743
684
  }
744
685
 
745
- if (pendingOutgoingCall != null) {
746
- pendingOutgoingCall.setKeepAlive(false);
747
- pendingOutgoingCall.reject("Another call is awaiting microphone permission.");
748
- clearOutgoingPermissionState();
749
- }
750
-
751
686
  String to = call.getString("to");
752
687
  if (to == null) {
753
688
  to = ""; // Empty string for echo test
754
689
  }
755
690
 
756
- if (hasMicrophonePermission()) {
757
- startOutgoingCall(call, to);
758
- return;
759
- }
760
-
761
- pendingOutgoingCall = call;
762
- pendingOutgoingTo = to;
763
- pendingPermissionAction = PendingPermissionAction.OUTGOING_CALL;
764
- permissionAttemptCount = 0;
765
- call.setKeepAlive(true);
766
- requestMicrophonePermission();
767
- }
768
-
769
- private void startOutgoingCall(PluginCall call, String to) {
770
- Log.d(TAG, "startOutgoingCall: to=" + to);
771
691
  // Start call via the foreground service
772
692
  Intent serviceIntent = new Intent(getSafeContext(), VoiceCallService.class);
773
693
  serviceIntent.setAction(VoiceCallService.ACTION_START_CALL);
@@ -780,368 +700,13 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
780
700
  JSObject ret = new JSObject();
781
701
  ret.put("success", true);
782
702
  ret.put("callSid", "pending"); // Will be updated when service connects
783
- call.setKeepAlive(false);
784
703
  call.resolve(ret);
785
704
  } catch (Exception e) {
786
- call.setKeepAlive(false);
787
705
  Log.e(TAG, "Error starting call service", e);
788
706
  call.reject("Failed to start call: " + e.getMessage());
789
- } finally {
790
- clearOutgoingPermissionState();
791
- }
792
- }
793
-
794
- private boolean hasMicrophonePermission() {
795
- return ContextCompat.checkSelfPermission(getSafeContext(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
796
- }
797
-
798
- private SharedPreferences getPrefs() {
799
- return getSafeContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
800
- }
801
-
802
- private void requestMicrophonePermission() {
803
- Activity activity = getActivity();
804
- permissionRequestTimestamp = System.currentTimeMillis();
805
- permissionAttemptCount++;
806
-
807
- if (activity != null) {
808
- activity.runOnUiThread(() -> {
809
- Log.d(TAG, "requestMicrophonePermission: requesting RECORD_AUDIO (attempt " + permissionAttemptCount + ")");
810
- if (micPermissionLauncher != null) {
811
- micPermissionLauncher.launch(new String[] { Manifest.permission.RECORD_AUDIO });
812
- } else {
813
- ActivityCompat.requestPermissions(
814
- activity,
815
- new String[] { Manifest.permission.RECORD_AUDIO },
816
- REQUEST_CODE_RECORD_AUDIO_FOR_ACCEPT
817
- );
818
- }
819
- });
820
- return;
821
- }
822
-
823
- if (mainActivityClass != null) {
824
- Context context = getSafeContext();
825
- Intent launchIntent = new Intent(context, mainActivityClass);
826
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
827
- if (pendingPermissionAction == PendingPermissionAction.ACCEPT_CALL && pendingCallSidForPermission != null) {
828
- launchIntent.putExtra("AUTO_ACCEPT_CALL", true);
829
- launchIntent.putExtra(EXTRA_CALL_SID, pendingCallSidForPermission);
830
- }
831
- Log.d(TAG, "requestMicrophonePermission: launching activity to request permission");
832
- context.startActivity(launchIntent);
833
- } else {
834
- Log.w(TAG, "Unable to request microphone permission - no activity available");
835
- handlePermissionFailure();
836
- }
837
- }
838
-
839
- private void handleMicPermissionResult(Map<String, Boolean> permissions) {
840
- Boolean granted = permissions.get(Manifest.permission.RECORD_AUDIO);
841
- Log.d(TAG, "handleMicPermissionResult: granted=" + granted + ", pendingAction=" + pendingPermissionAction);
842
- if (granted != null && granted) {
843
- handleMicrophonePermissionGranted();
844
- } else {
845
- handleMicrophonePermissionDenied();
846
- }
847
- }
848
-
849
- private void handleMicrophonePermissionGranted() {
850
- Log.d(
851
- TAG,
852
- "handleMicrophonePermissionGranted: pendingAction=" +
853
- pendingPermissionAction +
854
- ", pendingCall=" +
855
- (pendingPermissionCall != null)
856
- );
857
- permissionAttemptCount = 0;
858
-
859
- if (pendingPermissionAction == PendingPermissionAction.OUTGOING_CALL && pendingOutgoingCall != null) {
860
- PluginCall call = pendingOutgoingCall;
861
- String to = pendingOutgoingTo != null ? pendingOutgoingTo : "";
862
- pendingOutgoingCall = null;
863
- pendingOutgoingTo = null;
864
- pendingPermissionAction = PendingPermissionAction.NONE;
865
- startOutgoingCall(call, to);
866
- return;
867
- }
868
-
869
- if (pendingPermissionAction == PendingPermissionAction.ACCEPT_CALL) {
870
- if (pendingCallSidForPermission != null) {
871
- String callSid = pendingCallSidForPermission;
872
- pendingCallSidForPermission = null;
873
- pendingPermissionAction = PendingPermissionAction.NONE;
874
- proceedAcceptCall(callSid);
875
- return;
876
- }
877
-
878
- pendingPermissionAction = PendingPermissionAction.NONE;
879
- awaitingSettingsResult = false;
880
- permissionAttemptCount = 0;
881
- return;
882
- }
883
-
884
- if (pendingPermissionCall != null) {
885
- JSObject ret = new JSObject();
886
- ret.put("granted", true);
887
- pendingPermissionCall.setKeepAlive(false);
888
- pendingPermissionCall.resolve(ret);
889
- pendingPermissionCall = null;
890
- awaitingSettingsResult = false;
891
- }
892
-
893
- pendingPermissionAction = PendingPermissionAction.NONE;
894
- }
895
-
896
- private void handleMicrophonePermissionDenied() {
897
- Log.d(TAG, "handleMicrophonePermissionDenied invoked");
898
- Activity activity = getActivity();
899
- if (activity == null) {
900
- Log.w(TAG, "handleMicrophonePermissionDenied: no activity");
901
- handlePermissionFailure();
902
- return;
903
- }
904
-
905
- boolean canRequestAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO);
906
- Log.d(
907
- TAG,
908
- "handleMicrophonePermissionDenied: canRequestAgain=" +
909
- canRequestAgain +
910
- ", attempt=" +
911
- permissionAttemptCount +
912
- ", pendingAction=" +
913
- pendingPermissionAction +
914
- ", standaloneCall=" +
915
- (pendingPermissionCall != null)
916
- );
917
-
918
- if (pendingPermissionAction == PendingPermissionAction.NONE && pendingPermissionCall != null) {
919
- if (canRequestAgain && permissionAttemptCount <= 1) {
920
- showStandalonePermissionRationaleDialog(activity, pendingPermissionCall);
921
- } else {
922
- showStandalonePermissionSettingsDialog(activity, pendingPermissionCall);
923
- }
924
- } else if (canRequestAgain && permissionAttemptCount <= 1) {
925
- showPermissionRationaleDialog(activity);
926
- } else {
927
- showPermissionSettingsDialog(activity);
928
707
  }
929
708
  }
930
709
 
931
- private void showPermissionRationaleDialog(Activity activity) {
932
- Log.d(TAG, "showPermissionRationaleDialog");
933
- new AlertDialog.Builder(activity)
934
- .setTitle("Microphone required")
935
- .setMessage("Microphone access is required to place and receive calls.")
936
- .setPositiveButton("Retry", (dialog, which) -> {
937
- dialog.dismiss();
938
- requestMicrophonePermission();
939
- })
940
- .setNegativeButton("Cancel", (dialog, which) -> {
941
- dialog.dismiss();
942
- handlePermissionFailure();
943
- })
944
- .setCancelable(false)
945
- .show();
946
- }
947
-
948
- private void showStandalonePermissionRationaleDialog(Activity activity, PluginCall call) {
949
- Log.d(TAG, "showStandalonePermissionRationaleDialog");
950
- new AlertDialog.Builder(activity)
951
- .setTitle("Microphone required")
952
- .setMessage("Microphone access is required to place and receive calls.")
953
- .setPositiveButton("Retry", (dialog, which) -> {
954
- dialog.dismiss();
955
- requestMicrophonePermission();
956
- })
957
- .setNegativeButton("Cancel", (dialog, which) -> {
958
- dialog.dismiss();
959
- JSObject ret = new JSObject();
960
- ret.put("granted", false);
961
- call.setKeepAlive(false);
962
- call.resolve(ret);
963
- pendingPermissionCall = null;
964
- awaitingSettingsResult = false;
965
- permissionAttemptCount = 0;
966
- })
967
- .setCancelable(false)
968
- .show();
969
- }
970
-
971
- private void showPermissionSettingsDialog(Activity activity) {
972
- Log.d(TAG, "showPermissionSettingsDialog");
973
- new AlertDialog.Builder(activity)
974
- .setTitle("Enable microphone")
975
- .setMessage("You can enable the microphone in Settings to use calling features.")
976
- .setPositiveButton("Open Settings", (dialog, which) -> {
977
- dialog.dismiss();
978
- ensureUnlockedThenOpenSettings();
979
- })
980
- .setNegativeButton("Cancel", (dialog, which) -> {
981
- dialog.dismiss();
982
- handlePermissionFailure();
983
- })
984
- .setCancelable(false)
985
- .show();
986
- }
987
-
988
- private void showStandalonePermissionSettingsDialog(Activity activity, PluginCall call) {
989
- Log.d(TAG, "showStandalonePermissionSettingsDialog");
990
- new AlertDialog.Builder(activity)
991
- .setTitle("Enable microphone")
992
- .setMessage("Microphone access is required. Open Settings to enable the permission.")
993
- .setPositiveButton("Open Settings", (dialog, which) -> {
994
- dialog.dismiss();
995
- pendingPermissionCall = call;
996
- call.setKeepAlive(true);
997
- awaitingSettingsResult = true;
998
- ensureUnlockedThenOpenSettings();
999
- })
1000
- .setNegativeButton("Cancel", (dialog, which) -> {
1001
- dialog.dismiss();
1002
- JSObject ret = new JSObject();
1003
- ret.put("granted", false);
1004
- call.setKeepAlive(false);
1005
- call.resolve(ret);
1006
- pendingPermissionCall = null;
1007
- awaitingSettingsResult = false;
1008
- permissionAttemptCount = 0;
1009
- })
1010
- .setCancelable(false)
1011
- .show();
1012
- }
1013
-
1014
- private void openAppSettings() {
1015
- Context context = getSafeContext();
1016
- if (pendingPermissionCall != null || pendingPermissionAction != PendingPermissionAction.NONE) {
1017
- awaitingSettingsResult = true;
1018
- }
1019
- Log.d(
1020
- TAG,
1021
- "openAppSettings: awaitingSettingsResult=" +
1022
- awaitingSettingsResult +
1023
- ", pendingAction=" +
1024
- pendingPermissionAction +
1025
- ", pendingCall=" +
1026
- (pendingPermissionCall != null)
1027
- );
1028
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
1029
- intent.setData(Uri.fromParts("package", context.getPackageName(), null));
1030
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1031
- context.startActivity(intent);
1032
- Toast.makeText(context, "Enable microphone permission and return to the app", Toast.LENGTH_LONG).show();
1033
- }
1034
-
1035
- private boolean isDeviceLocked() {
1036
- KeyguardManager km = (KeyguardManager) getSafeContext().getSystemService(Context.KEYGUARD_SERVICE);
1037
- return km != null && km.isKeyguardLocked();
1038
- }
1039
-
1040
- private void ensureUnlockedThenOpenSettings() {
1041
- Activity activity = getActivity();
1042
- if (activity == null) {
1043
- Log.w(TAG, "ensureUnlockedThenOpenSettings: no activity available");
1044
- openAppSettings();
1045
- return;
1046
- }
1047
-
1048
- KeyguardManager km = (KeyguardManager) getSafeContext().getSystemService(Context.KEYGUARD_SERVICE);
1049
- if (km == null || !km.isKeyguardLocked()) {
1050
- openAppSettings();
1051
- return;
1052
- }
1053
-
1054
- km.requestDismissKeyguard(
1055
- activity,
1056
- new KeyguardManager.KeyguardDismissCallback() {
1057
- @Override
1058
- public void onDismissSucceeded() {
1059
- Log.d(TAG, "Keyguard dismissed, opening settings");
1060
- openAppSettings();
1061
- }
1062
-
1063
- @Override
1064
- public void onDismissCancelled() {
1065
- Log.d(TAG, "Keyguard dismissal cancelled");
1066
- }
1067
-
1068
- @Override
1069
- public void onDismissError() {
1070
- Log.w(TAG, "Keyguard dismissal error");
1071
- }
1072
- }
1073
- );
1074
- }
1075
-
1076
- private void moveAppToBackgroundIfLocked() {
1077
- Activity activity = getActivity();
1078
- if (activity != null && isDeviceLocked()) {
1079
- Log.d(TAG, "moveAppToBackgroundIfLocked: moving task to back");
1080
- activity.moveTaskToBack(true);
1081
- }
1082
- }
1083
-
1084
- private void handlePermissionFailure() {
1085
- Log.d(
1086
- TAG,
1087
- "handlePermissionFailure: pendingAction=" + pendingPermissionAction + ", pendingCall=" + (pendingPermissionCall != null)
1088
- );
1089
- if (pendingPermissionAction == PendingPermissionAction.OUTGOING_CALL) {
1090
- if (pendingOutgoingCall != null) {
1091
- pendingOutgoingCall.setKeepAlive(false);
1092
- pendingOutgoingCall.reject("Microphone permission is required to place a call.");
1093
- }
1094
- clearOutgoingPermissionState();
1095
- } else if (pendingPermissionAction == PendingPermissionAction.ACCEPT_CALL) {
1096
- if (pendingCallSidForPermission != null) {
1097
- CallInvite invite = activeCallInvites.get(pendingCallSidForPermission);
1098
- if (invite != null) {
1099
- dismissIncomingCallNotification();
1100
- activeCallInvites.remove(pendingCallSidForPermission);
1101
- try {
1102
- invite.reject(getSafeContext());
1103
- } catch (Exception ex) {
1104
- Log.w(TAG, "handlePermissionFailure: failed to reject invite", ex);
1105
- }
1106
- }
1107
- JSObject data = new JSObject();
1108
- data.put("callSid", pendingCallSidForPermission);
1109
- data.put("reason", "microphone_permission_denied");
1110
- if (invite != null) {
1111
- if (invite.getFrom() != null) {
1112
- data.put("from", invite.getFrom().replace("client:", ""));
1113
- }
1114
- if (invite.getTo() != null) {
1115
- data.put("to", invite.getTo());
1116
- }
1117
- }
1118
- notifyListeners("callDisconnected", data);
1119
- }
1120
- pendingCallSidForPermission = null;
1121
- pendingPermissionAction = PendingPermissionAction.NONE;
1122
- awaitingSettingsResult = false;
1123
- pendingPermissionCall = null;
1124
- moveAppToBackgroundIfLocked();
1125
- } else if (pendingPermissionCall != null) {
1126
- JSObject ret = new JSObject();
1127
- ret.put("granted", false);
1128
- pendingPermissionCall.setKeepAlive(false);
1129
- pendingPermissionCall.resolve(ret);
1130
- pendingPermissionCall = null;
1131
- awaitingSettingsResult = false;
1132
- }
1133
- permissionAttemptCount = 0;
1134
- }
1135
-
1136
- private void clearOutgoingPermissionState() {
1137
- pendingOutgoingCall = null;
1138
- pendingOutgoingTo = null;
1139
- if (pendingPermissionAction == PendingPermissionAction.OUTGOING_CALL) {
1140
- pendingPermissionAction = PendingPermissionAction.NONE;
1141
- }
1142
- permissionAttemptCount = 0;
1143
- }
1144
-
1145
710
  // Call parameter creation is now handled by VoiceCallService
1146
711
 
1147
712
  @PluginMethod
@@ -1189,7 +754,6 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
1189
754
  JSObject ret = new JSObject();
1190
755
  ret.put("success", true);
1191
756
  call.resolve(ret);
1192
- moveAppToBackgroundIfLocked();
1193
757
  }
1194
758
 
1195
759
  @PluginMethod
@@ -1283,82 +847,25 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
1283
847
 
1284
848
  @PluginMethod
1285
849
  public void requestMicrophonePermission(PluginCall call) {
1286
- Log.d(TAG, "requestMicrophonePermission invoked");
1287
- if (hasMicrophonePermission()) {
1288
- Log.d(TAG, "requestMicrophonePermission: already granted");
1289
- JSObject ret = new JSObject();
1290
- ret.put("granted", true);
1291
- call.resolve(ret);
1292
- return;
1293
- }
1294
-
1295
- Activity activity = getActivity();
1296
- if (activity == null) {
1297
- Log.w(TAG, "requestMicrophonePermission: no activity available");
1298
- call.reject("Unable to request permission without an active activity");
1299
- return;
1300
- }
1301
-
1302
- SharedPreferences prefs = getPrefs();
1303
- boolean requestedBefore = prefs.getBoolean(PREF_MIC_PERMISSION_REQUESTED, false);
1304
- boolean shouldShow = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO);
1305
- Log.d(
1306
- TAG,
1307
- "requestMicrophonePermission: requestedBefore=" +
1308
- requestedBefore +
1309
- ", shouldShow=" +
1310
- shouldShow +
1311
- ", pendingAction=" +
1312
- pendingPermissionAction
1313
- );
1314
-
1315
- if (pendingPermissionAction == PendingPermissionAction.NONE && !shouldShow && requestedBefore) {
1316
- showStandalonePermissionSettingsDialog(activity, call);
1317
- return;
1318
- }
1319
-
1320
- prefs.edit().putBoolean(PREF_MIC_PERMISSION_REQUESTED, true).apply();
1321
-
1322
- if (pendingPermissionAction == PendingPermissionAction.NONE) {
1323
- pendingPermissionCall = call;
1324
- call.setKeepAlive(true);
1325
- }
1326
- awaitingSettingsResult = false;
1327
- permissionAttemptCount++;
1328
- permissionRequestTimestamp = System.currentTimeMillis();
1329
- Log.d(TAG, "requestMicrophonePermission: invoking ActivityCompat.requestPermissions (attempt " + permissionAttemptCount + ")");
1330
-
1331
- ActivityCompat.requestPermissions(
1332
- activity,
1333
- new String[] { Manifest.permission.RECORD_AUDIO },
1334
- REQUEST_CODE_RECORD_AUDIO_FOR_ACCEPT
1335
- );
850
+ requestPermissions(call);
1336
851
  }
1337
852
 
1338
853
  @Override
1339
854
  protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
1340
855
  super.handleRequestPermissionsResult(requestCode, permissions, grantResults);
1341
856
 
1342
- if (requestCode != REQUEST_CODE_RECORD_AUDIO_FOR_ACCEPT) {
1343
- return;
1344
- }
1345
-
1346
- boolean granted = hasMicrophonePermission();
1347
- Log.d(
1348
- TAG,
1349
- "handleRequestPermissionsResult: granted=" +
1350
- granted +
1351
- ", attempt=" +
1352
- permissionAttemptCount +
1353
- ", pendingAction=" +
1354
- pendingPermissionAction +
1355
- ", standaloneCall=" +
1356
- (pendingPermissionCall != null)
1357
- );
1358
- if (granted) {
1359
- handleMicrophonePermissionGranted();
1360
- } else {
1361
- handleMicrophonePermissionDenied();
857
+ // If we were waiting to accept a call after mic permission
858
+ if (pendingCallSidForPermission != null) {
859
+ boolean granted =
860
+ ActivityCompat.checkSelfPermission(getSafeContext(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
861
+ String callSid = pendingCallSidForPermission;
862
+ pendingCallSidForPermission = null;
863
+ if (granted) {
864
+ Log.d(TAG, "RECORD_AUDIO granted from permission flow; proceeding to accept call");
865
+ proceedAcceptCall(callSid);
866
+ } else {
867
+ Log.w(TAG, "RECORD_AUDIO denied; cannot accept call");
868
+ }
1362
869
  }
1363
870
  }
1364
871
 
@@ -1578,8 +1085,13 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
1578
1085
  }
1579
1086
  };*/
1580
1087
 
1581
- private void showIncomingCallNotification(CallInvite callInvite, String callSid, String callerName) {
1088
+ private void showIncomingCallNotification(CallInvite callInvite, String callSid) {
1582
1089
  try {
1090
+ String callerName = callInvite.getFrom();
1091
+ if (callerName != null && callerName.startsWith("client:")) {
1092
+ callerName = callerName.substring(7); // Remove "client:" prefix
1093
+ }
1094
+
1583
1095
  // Create intent for accepting the call
1584
1096
  PendingIntent acceptPendingIntent;
1585
1097
  if (this.bridge == null) {
@@ -1696,22 +1208,16 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
1696
1208
  String callSid = UUID.randomUUID().toString(); // Generate a unique ID
1697
1209
  activeCallInvites.put(callSid, callInvite);
1698
1210
 
1699
- Map<String, String> params = callInvite.getCustomParameters();
1700
- String callerName = params.containsKey("CapacitorTwilioCallerName")
1701
- ? params.get("CapacitorTwilioCallerName")
1702
- : callInvite.getFrom();
1703
-
1704
1211
  // Create and show notification
1705
- showIncomingCallNotification(callInvite, callSid, callerName);
1212
+ showIncomingCallNotification(callInvite, callSid);
1706
1213
 
1707
1214
  // Start ringtone and vibration
1708
1215
  startRingtone();
1709
1216
 
1710
1217
  JSObject data = new JSObject();
1711
1218
  data.put("callSid", callSid);
1712
- data.put("from", callerName);
1219
+ data.put("from", callInvite.getFrom());
1713
1220
  data.put("to", callInvite.getTo());
1714
- data.put("customParams", new JSONObject(params));
1715
1221
  notifyListeners("callInviteReceived", data);
1716
1222
  }
1717
1223
 
@@ -1772,9 +1278,19 @@ public class CapacitorTwilioVoicePlugin extends Plugin {
1772
1278
  notifyListeners("callDisconnected", data);
1773
1279
 
1774
1280
  Log.d(TAG, "Call rejected from notification");
1775
- moveAppToBackgroundIfLocked();
1776
1281
  } else {
1777
1282
  Log.e(TAG, "Call invite not found for SID: " + callSid);
1778
1283
  }
1779
1284
  }
1285
+
1286
+ @PluginMethod
1287
+ public void getPluginVersion(final PluginCall call) {
1288
+ try {
1289
+ final JSObject ret = new JSObject();
1290
+ ret.put("version", this.PLUGIN_VERSION);
1291
+ call.resolve(ret);
1292
+ } catch (final Exception e) {
1293
+ call.reject("Could not get plugin version", e);
1294
+ }
1295
+ }
1780
1296
  }