@qusaieilouti99/call-manager 0.1.185 → 0.1.186

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.
@@ -38,6 +38,7 @@ import java.util.concurrent.CopyOnWriteArrayList
38
38
  import java.util.concurrent.atomic.AtomicBoolean
39
39
  import android.app.KeyguardManager
40
40
  import java.util.UUID
41
+ import android.provider.Settings // Import for Settings.canDrawOverlays and ACTION_MANAGE_OVERLAY_PERMISSION
41
42
 
42
43
  /**
43
44
  * Core call‐management engine. Manages self-managed telecom calls,
@@ -1043,6 +1044,16 @@ object CallEngine {
1043
1044
  }
1044
1045
 
1045
1046
  try {
1047
+ // For SYSTEM_ALERT_WINDOW usage, the permission must be granted.
1048
+ // We are checking it in requestOverlayPermission.
1049
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
1050
+ Log.w(TAG, "Cannot show CallActivity overlay without SYSTEM_ALERT_WINDOW permission. Launching permission request.")
1051
+ // Instead of starting the activity which will fail, launch the permission request.
1052
+ requestOverlayPermission()
1053
+ showStandardNotification(context, callId, callerName, callType, callerPicUrl)
1054
+ return
1055
+ }
1056
+
1046
1057
  val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
1047
1058
  val wakeLock = powerManager.newWakeLock(
1048
1059
  PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
@@ -1362,4 +1373,35 @@ object CallEngine {
1362
1373
  availableCallEndpoints = emptyList()
1363
1374
  wasManuallySetAudioRoute = false
1364
1375
  }
1376
+
1377
+ // --- New Function for SYSTEM_ALERT_WINDOW permission ---
1378
+ fun requestOverlayPermission(): Boolean {
1379
+ val context = requireContext()
1380
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1381
+ if (!Settings.canDrawOverlays(context)) {
1382
+ Log.d(TAG, "SYSTEM_ALERT_WINDOW permission not granted. Requesting it.")
1383
+ try {
1384
+ val intent = Intent(
1385
+ Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
1386
+ Uri.parse("package:" + context.packageName)
1387
+ ).apply {
1388
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // Required when starting from non-Activity context
1389
+ }
1390
+ context.startActivity(intent)
1391
+ Log.d(TAG, "Launched SYSTEM_ALERT_WINDOW permission settings.")
1392
+ false // Not granted yet, user needs to act
1393
+ } catch (e: Exception) {
1394
+ Log.e(TAG, "Failed to launch SYSTEM_ALERT_WINDOW permission settings: ${e.message}", e)
1395
+ false // Failed to launch, so not granted
1396
+ }
1397
+ } else {
1398
+ Log.d(TAG, "SYSTEM_ALERT_WINDOW permission already granted.")
1399
+ true
1400
+ }
1401
+ } else {
1402
+ // Permissions granted at install time for older Android versions
1403
+ Log.d(TAG, "SYSTEM_ALERT_WINDOW permission automatically granted on API < 23.")
1404
+ true
1405
+ }
1406
+ }
1365
1407
  }
@@ -123,4 +123,11 @@ class CallManager : HybridCallManagerSpec() {
123
123
  metadata
124
124
  )
125
125
  }
126
+
127
+ // --- New Hybrid Method for SYSTEM_ALERT_WINDOW permission ---
128
+ override fun requestOverlayPermissionAndroid(): Boolean {
129
+ Log.d(TAG, "requestOverlayPermissionAndroid requested")
130
+ ensureInitialized()
131
+ return CallEngine.requestOverlayPermission()
132
+ }
126
133
  }
@@ -25,6 +25,9 @@ class CallEngine {
25
25
  // Track video calls that need speaker activation
26
26
  private var videoCallsNeedingSpeaker: Set<String> = []
27
27
 
28
+ // NEW: Track calls being answered via the startCall method
29
+ private var answeringIncomingViaStartCall: Set<String> = [] // NEW
30
+
28
31
  private init() {
29
32
  logger.info("CallEngine singleton created")
30
33
  }
@@ -200,6 +203,30 @@ class CallEngine {
200
203
  metadata: String? = nil)
201
204
  {
202
205
  logger.info("startCall (join ongoing): \(callId), type=\(callType)")
206
+
207
+ // --- MODIFICATION START ---
208
+ // Check if the callId corresponds to an existing incoming call
209
+ if let existingCallInfo = activeCalls[callId], existingCallInfo.state == .incoming {
210
+ logger.info("startCall: Detected attempt to answer existing incoming call \(callId). Redirecting to answer flow.")
211
+
212
+ // Update metadata for the existing incoming call if provided
213
+ if let m = metadata {
214
+ callMetadata[callId] = m
215
+ logger.info("metadata updated for incoming call \(callId)")
216
+ }
217
+
218
+ // Mark this call as one being answered via startCall
219
+ answeringIncomingViaStartCall.insert(callId)
220
+
221
+ // Trigger the CallKit answer action. This will lead to `callKitManager(_:didAnswerCall:)` being called.
222
+ callKitManager.answerCall(callId: callId)
223
+
224
+ // Return early as we've redirected to the answer flow.
225
+ return
226
+ }
227
+ // --- MODIFICATION END ---
228
+
229
+
203
230
  if let m = metadata {
204
231
  callMetadata[callId] = m
205
232
  logger.info("metadata cached for \(callId)")
@@ -447,7 +474,16 @@ class CallEngine {
447
474
 
448
475
  extension CallEngine: CallKitManagerDelegate {
449
476
  func callKitManager(_ manager: CallKitManager, didAnswerCall callId: String) {
450
- coreCallAnswered(callId: callId, isLocalAnswer: true)
477
+ // MODIFICATION START
478
+ if answeringIncomingViaStartCall.contains(callId) {
479
+ logger.info("📞 CallKit answered: \(callId). This was initiated by startCall, treating as non-local answer.")
480
+ answeringIncomingViaStartCall.remove(callId)
481
+ coreCallAnswered(callId: callId, isLocalAnswer: false) // isLocalAnswer: false for OUTGOING_CALL_ANSWERED
482
+ } else {
483
+ logger.info("📞 CallKit answered: \(callId). Standard local answer.")
484
+ coreCallAnswered(callId: callId, isLocalAnswer: true) // isLocalAnswer: true for CALL_ANSWERED
485
+ }
486
+ // MODIFICATION END
451
487
  }
452
488
 
453
489
  func callKitManager(_ manager: CallKitManager, didEndCall callId: String) {
@@ -153,4 +153,9 @@ public class CallManager: HybridCallManagerSpec {
153
153
  logger.info("🎯 hasActiveCall ▶ js → native")
154
154
  return CallEngine.shared.hasActiveCalls()
155
155
  }
156
+
157
+ public func requestOverlayPermissionAndroid() throws -> Bool {
158
+ logger.info("🎯 requestOverlayPermissionAndroid ▶ js → native")
159
+ return true
160
+ }
156
161
  }
@@ -1 +1 @@
1
- {"version":3,"names":["NitroModules","CallManagerHybridObject","createHybridObject"],"sourceRoot":"../../src","sources":["CallManager.nitro.ts"],"mappings":";;AAAA;AACA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAG5E;;AAKA;;AAyDA,OAAO,MAAMC,uBAAuB,GAClCD,YAAY,CAACE,kBAAkB,CAAc,aAAa,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["NitroModules","CallManagerHybridObject","createHybridObject"],"sourceRoot":"../../src","sources":["CallManager.nitro.ts"],"mappings":";;AAAA;AACA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAG5E;;AAKA;;AA2DA,OAAO,MAAMC,uBAAuB,GAClCD,YAAY,CAACE,kBAAkB,CAAc,aAAa,CAAC","ignoreList":[]}
@@ -27,6 +27,7 @@ export interface CallManager extends HybridObject<{
27
27
  addListener(listener: (event: CallEventType, payload: string) => void): () => void;
28
28
  registerVoIPTokenListener(listener: (payload: string) => void): () => void;
29
29
  hasActiveCall(): boolean;
30
+ requestOverlayPermissionAndroid(): boolean;
30
31
  }
31
32
  export declare const CallManagerHybridObject: CallManager;
32
33
  //# sourceMappingURL=CallManager.nitro.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CallManager.nitro.d.ts","sourceRoot":"","sources":["../../../src/CallManager.nitro.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAEzD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,IAAI,IAAI,CAAC;IACxB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAGnC,eAAe,IAAI,eAAe,CAAC;IACnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1C,WAAW,IAAI,IAAI,CAAC;IACpB,iBAAiB,CACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CACP,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvE,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IAGR,WAAW,CAET,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACxD,MAAM,IAAI,CAAC;IAEd,yBAAyB,CAEvB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,MAAM,IAAI,CAAC;IAEd,aAAa,IAAI,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,uBAAuB,aACyB,CAAC"}
1
+ {"version":3,"file":"CallManager.nitro.d.ts","sourceRoot":"","sources":["../../../src/CallManager.nitro.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAEzD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,IAAI,IAAI,CAAC;IACxB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAGnC,eAAe,IAAI,eAAe,CAAC;IACnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1C,WAAW,IAAI,IAAI,CAAC;IACpB,iBAAiB,CACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CACP,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvE,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IAGR,WAAW,CAET,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACxD,MAAM,IAAI,CAAC;IAEd,yBAAyB,CAEvB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,MAAM,IAAI,CAAC;IAEd,aAAa,IAAI,OAAO,CAAC;IAEzB,+BAA+B,IAAI,OAAO,CAAC;CAC5C;AAED,eAAO,MAAM,uBAAuB,aACyB,CAAC"}
@@ -142,5 +142,10 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
142
142
  auto __result = method(_javaPart);
143
143
  return static_cast<bool>(__result);
144
144
  }
145
+ bool JHybridCallManagerSpec::requestOverlayPermissionAndroid() {
146
+ static const auto method = javaClassStatic()->getMethod<jboolean()>("requestOverlayPermissionAndroid");
147
+ auto __result = method(_javaPart);
148
+ return static_cast<bool>(__result);
149
+ }
145
150
 
146
151
  } // namespace margelo::nitro::qusaieilouti99_callmanager
@@ -68,6 +68,7 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
68
68
  std::function<void()> addListener(const std::function<void(CallEventType /* event */, const std::string& /* payload */)>& listener) override;
69
69
  std::function<void()> registerVoIPTokenListener(const std::function<void(const std::string& /* payload */)>& listener) override;
70
70
  bool hasActiveCall() override;
71
+ bool requestOverlayPermissionAndroid() override;
71
72
 
72
73
  private:
73
74
  friend HybridBase;
@@ -113,6 +113,10 @@ abstract class HybridCallManagerSpec: HybridObject() {
113
113
  @DoNotStrip
114
114
  @Keep
115
115
  abstract fun hasActiveCall(): Boolean
116
+
117
+ @DoNotStrip
118
+ @Keep
119
+ abstract fun requestOverlayPermissionAndroid(): Boolean
116
120
 
117
121
  private external fun initHybrid(): HybridData
118
122
 
@@ -172,6 +172,14 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
172
172
  auto __value = std::move(__result.value());
173
173
  return __value;
174
174
  }
175
+ inline bool requestOverlayPermissionAndroid() override {
176
+ auto __result = _swiftPart.requestOverlayPermissionAndroid();
177
+ if (__result.hasError()) [[unlikely]] {
178
+ std::rethrow_exception(__result.error());
179
+ }
180
+ auto __value = std::move(__result.value());
181
+ return __value;
182
+ }
175
183
 
176
184
  private:
177
185
  CallManager::HybridCallManagerSpec_cxx _swiftPart;
@@ -30,6 +30,7 @@ public protocol HybridCallManagerSpec_protocol: HybridObject {
30
30
  func addListener(listener: @escaping (_ event: CallEventType, _ payload: String) -> Void) throws -> () -> Void
31
31
  func registerVoIPTokenListener(listener: @escaping (_ payload: String) -> Void) throws -> () -> Void
32
32
  func hasActiveCall() throws -> Bool
33
+ func requestOverlayPermissionAndroid() throws -> Bool
33
34
  }
34
35
 
35
36
  /// See ``HybridCallManagerSpec``
@@ -322,4 +322,16 @@ open class HybridCallManagerSpec_cxx {
322
322
  return bridge.create_Result_bool_(__exceptionPtr)
323
323
  }
324
324
  }
325
+
326
+ @inline(__always)
327
+ public final func requestOverlayPermissionAndroid() -> bridge.Result_bool_ {
328
+ do {
329
+ let __result = try self.__implementation.requestOverlayPermissionAndroid()
330
+ let __resultCpp = __result
331
+ return bridge.create_Result_bool_(__resultCpp)
332
+ } catch (let __error) {
333
+ let __exceptionPtr = __error.toCpp()
334
+ return bridge.create_Result_bool_(__exceptionPtr)
335
+ }
336
+ }
325
337
  }
@@ -30,6 +30,7 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
30
30
  prototype.registerHybridMethod("addListener", &HybridCallManagerSpec::addListener);
31
31
  prototype.registerHybridMethod("registerVoIPTokenListener", &HybridCallManagerSpec::registerVoIPTokenListener);
32
32
  prototype.registerHybridMethod("hasActiveCall", &HybridCallManagerSpec::hasActiveCall);
33
+ prototype.registerHybridMethod("requestOverlayPermissionAndroid", &HybridCallManagerSpec::requestOverlayPermissionAndroid);
33
34
  });
34
35
  }
35
36
 
@@ -71,6 +71,7 @@ namespace margelo::nitro::qusaieilouti99_callmanager {
71
71
  virtual std::function<void()> addListener(const std::function<void(CallEventType /* event */, const std::string& /* payload */)>& listener) = 0;
72
72
  virtual std::function<void()> registerVoIPTokenListener(const std::function<void(const std::string& /* payload */)>& listener) = 0;
73
73
  virtual bool hasActiveCall() = 0;
74
+ virtual bool requestOverlayPermissionAndroid() = 0;
74
75
 
75
76
  protected:
76
77
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.185",
3
+ "version": "0.1.186",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -62,6 +62,8 @@ export interface CallManager
62
62
  ): () => void;
63
63
 
64
64
  hasActiveCall(): boolean; // if there is an active call, no matter ringing, incoming, outgoing, whatever
65
+
66
+ requestOverlayPermissionAndroid(): boolean; // only android
65
67
  }
66
68
 
67
69
  export const CallManagerHybridObject =