@qusaieilouti99/call-manager 0.1.96 → 0.1.97

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.
@@ -1,12 +1,13 @@
1
- // CallActivity.kt
1
+ // File: CallActivity.kt
2
2
  package com.margelo.nitro.qusaieilouti99.callmanager
3
3
 
4
- import android.animation.ObjectAnimator
5
- import android.animation.ValueAnimator
6
4
  import android.app.Activity
7
5
  import android.app.KeyguardManager
8
6
  import android.content.Context
9
- import android.graphics.*
7
+ import android.graphics.Color
8
+ import android.graphics.RenderEffect
9
+ import android.graphics.Shader
10
+ import android.graphics.drawable.Drawable
10
11
  import android.graphics.drawable.GradientDrawable
11
12
  import android.os.Build
12
13
  import android.os.Bundle
@@ -14,561 +15,308 @@ import android.os.Handler
14
15
  import android.os.Looper
15
16
  import android.util.Log
16
17
  import android.util.TypedValue
18
+ import android.view.Gravity
17
19
  import android.view.View
18
20
  import android.view.ViewGroup
19
21
  import android.view.WindowManager
20
- import android.view.animation.LinearInterpolator
21
- import android.widget.*
22
+ import android.widget.FrameLayout
23
+ import android.widget.ImageView
24
+ import android.widget.LinearLayout
25
+ import android.widget.TextView
22
26
  import androidx.core.content.ContextCompat
23
- import androidx.core.view.ViewCompat
27
+ import com.bumptech.glide.Glide
24
28
 
25
29
  class CallActivity : Activity(), CallEngine.CallEndListener {
26
-
27
- private enum class FinishReason {
28
- ANSWER, DECLINE, TIMEOUT, MANUAL_DISMISS, EXTERNAL_END
29
- }
30
-
31
- private var finishReason: FinishReason? = null
32
- private var callId: String = ""
33
- private var callType: String = "Audio"
34
-
35
- // UI Components
36
- private lateinit var rootContainer: FrameLayout
37
- private lateinit var callerNameText: TextView
38
- private lateinit var profileImage: ImageView
39
- private lateinit var answerButton: FrameLayout
40
- private lateinit var declineButton: FrameLayout
41
-
42
- // Animation
43
- private var pulseAnimator: ValueAnimator? = null
44
-
45
- private val timeoutHandler = Handler(Looper.getMainLooper())
46
- private val timeoutRunnable = Runnable {
47
- Log.d(TAG, "CallActivity timeout triggered for callId: $callId")
48
- finishReason = FinishReason.TIMEOUT
49
- CallEngine.stopRingtone()
50
- CallEngine.cancelIncomingCallUI()
51
- CallEngine.endCall(callId)
52
- finishCallActivity()
53
- }
54
-
55
- override fun onCreate(savedInstanceState: Bundle?) {
56
- super.onCreate(savedInstanceState)
57
- Log.d(TAG, "CallActivity onCreate")
58
-
59
- // Enhanced lock screen bypass
60
- val isSamsungLockScreenBypass = intent.getBooleanExtra("SAMSUNG_LOCK_SCREEN_BYPASS", false)
61
- setupLockScreenBypass(isSamsungLockScreenBypass)
62
-
63
- // Create UI programmatically
64
- createProfessionalUI()
65
-
66
- // Read incoming call params
67
- callId = intent.getStringExtra("callId") ?: ""
68
- callType = intent.getStringExtra("callType") ?: "Audio"
69
- Log.d(TAG, "CallActivity received callId: $callId, callType: $callType")
70
-
71
- // Register for call-end callbacks
72
- CallEngine.registerCallEndListener(this)
73
-
74
- // Bind data
75
- val callerName = intent.getStringExtra("callerName") ?: "Unknown"
76
- val callerAvatar = intent.getStringExtra("callerAvatar")
77
- updateCallerInfo(callerName, callerAvatar)
78
-
79
- // Setup button listeners
80
- setupButtonListeners()
81
-
82
- // Start animations
83
- startPulseAnimation()
84
-
85
- // Start auto-timeout
86
- timeoutHandler.postDelayed(timeoutRunnable, 60_000)
87
- Log.d(TAG, "CallActivity setup complete")
88
- }
89
-
90
- private fun createProfessionalUI() {
91
- // Root container with gradient background
92
- rootContainer = FrameLayout(this).apply {
93
- layoutParams = ViewGroup.LayoutParams(
94
- ViewGroup.LayoutParams.MATCH_PARENT,
95
- ViewGroup.LayoutParams.MATCH_PARENT
96
- )
97
- background = createGradientBackground()
98
- }
99
-
100
- // Main content container
101
- val contentContainer = LinearLayout(this).apply {
102
- layoutParams = FrameLayout.LayoutParams(
103
- FrameLayout.LayoutParams.MATCH_PARENT,
104
- FrameLayout.LayoutParams.MATCH_PARENT
105
- ).apply {
106
- setPadding(dp(32), dp(80), dp(32), dp(48))
107
- }
108
- orientation = LinearLayout.VERTICAL
109
- gravity = android.view.Gravity.CENTER_HORIZONTAL
110
- }
111
-
112
- // Add components to content container
113
- contentContainer.addView(createCallTypeIndicator())
114
- contentContainer.addView(createSpacerView(40))
115
- contentContainer.addView(createProfileSection())
116
- contentContainer.addView(createSpacerView(32))
117
- contentContainer.addView(createCallerInfoSection())
118
- contentContainer.addView(createFlexibleSpacerView())
119
- contentContainer.addView(createQuickActionsSection())
120
- contentContainer.addView(createSpacerView(32))
121
- contentContainer.addView(createCallActionsSection())
122
-
123
- rootContainer.addView(contentContainer)
124
- setContentView(rootContainer)
125
- }
126
-
127
- private fun createGradientBackground(): GradientDrawable {
128
- return GradientDrawable().apply {
129
- colors = intArrayOf(
130
- Color.parseColor("#FF0D1B2A"),
131
- Color.parseColor("#FF1B263B"),
132
- Color.parseColor("#FF415A77")
133
- )
134
- orientation = GradientDrawable.Orientation.TL_BR
135
- gradientType = GradientDrawable.LINEAR_GRADIENT
136
- }
137
- }
138
-
139
- private fun createCallTypeIndicator(): LinearLayout {
140
- return LinearLayout(this).apply {
141
- layoutParams = LinearLayout.LayoutParams(
142
- LinearLayout.LayoutParams.WRAP_CONTENT,
143
- LinearLayout.LayoutParams.WRAP_CONTENT
144
- )
145
- orientation = LinearLayout.HORIZONTAL
146
- gravity = android.view.Gravity.CENTER_VERTICAL
147
-
148
- // Call icon
149
- addView(createCallIcon())
150
-
151
- // Call type text
152
- addView(TextView(this@CallActivity).apply {
153
- layoutParams = LinearLayout.LayoutParams(
154
- LinearLayout.LayoutParams.WRAP_CONTENT,
155
- LinearLayout.LayoutParams.WRAP_CONTENT
156
- ).apply { setMargins(dp(8), 0, 0, 0) }
157
- text = "Incoming call"
158
- setTextColor(Color.parseColor("#80FFFFFF"))
159
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
160
- typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
161
- })
162
- }
163
- }
164
-
165
- private fun createCallIcon(): View {
166
- return View(this).apply {
167
- layoutParams = LinearLayout.LayoutParams(dp(18), dp(18))
168
- background = GradientDrawable().apply {
169
- shape = GradientDrawable.OVAL
170
- setColor(Color.parseColor("#80FFFFFF"))
171
- }
172
- }
173
- }
174
-
175
- private fun createProfileSection(): FrameLayout {
176
- return FrameLayout(this).apply {
177
- layoutParams = LinearLayout.LayoutParams(dp(180), dp(180))
178
-
179
- // Outer pulse ring
180
- addView(createPulseRing(180, "#30FFFFFF"))
181
-
182
- // Middle pulse ring
183
- addView(createPulseRing(160, "#40FFFFFF"))
184
-
185
- // Profile container
186
- val profileContainer = FrameLayout(this@CallActivity).apply {
187
- layoutParams = FrameLayout.LayoutParams(dp(140), dp(140)).apply {
188
- gravity = android.view.Gravity.CENTER
189
- }
190
- }
191
-
192
- // Profile background
193
- profileContainer.addView(View(this@CallActivity).apply {
194
- layoutParams = FrameLayout.LayoutParams(dp(140), dp(140))
195
- background = GradientDrawable().apply {
196
- shape = GradientDrawable.OVAL
197
- setColor(Color.WHITE)
198
- setStroke(dp(3), Color.parseColor("#E0E0E0"))
199
- }
200
- })
201
-
202
- // Profile image
203
- profileImage = ImageView(this@CallActivity).apply {
204
- layoutParams = FrameLayout.LayoutParams(dp(136), dp(136)).apply {
205
- gravity = android.view.Gravity.CENTER
206
- }
207
- scaleType = ImageView.ScaleType.CENTER_CROP
208
- background = GradientDrawable().apply {
209
- shape = GradientDrawable.OVAL
210
- setColor(Color.parseColor("#F5F5F5"))
211
- }
212
- }
213
- profileContainer.addView(profileImage)
214
-
215
- addView(profileContainer)
216
- }
217
- }
218
-
219
- private fun createPulseRing(size: Int, color: String): View {
220
- return View(this).apply {
221
- layoutParams = FrameLayout.LayoutParams(dp(size), dp(size)).apply {
222
- gravity = android.view.Gravity.CENTER
223
- }
224
- background = GradientDrawable().apply {
225
- shape = GradientDrawable.OVAL
226
- setStroke(dp(2), Color.parseColor(color))
227
- setColor(Color.parseColor("#10FFFFFF"))
228
- }
229
- }
230
- }
231
-
232
- private fun createCallerInfoSection(): LinearLayout {
233
- return LinearLayout(this).apply {
234
- layoutParams = LinearLayout.LayoutParams(
235
- LinearLayout.LayoutParams.WRAP_CONTENT,
236
- LinearLayout.LayoutParams.WRAP_CONTENT
237
- )
238
- orientation = LinearLayout.VERTICAL
239
- gravity = android.view.Gravity.CENTER
240
-
241
- // Caller name
242
- callerNameText = TextView(this@CallActivity).apply {
243
- layoutParams = LinearLayout.LayoutParams(
244
- LinearLayout.LayoutParams.WRAP_CONTENT,
245
- LinearLayout.LayoutParams.WRAP_CONTENT
246
- ).apply { setMargins(0, 0, 0, dp(4)) }
247
- text = "John Doe" // Default, will be updated
248
- setTextColor(Color.WHITE)
249
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 32f)
250
- typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
251
- gravity = android.view.Gravity.CENTER
252
- }
253
- addView(callerNameText)
254
-
255
- // Caller number/label
256
- addView(TextView(this@CallActivity).apply {
257
- layoutParams = LinearLayout.LayoutParams(
258
- LinearLayout.LayoutParams.WRAP_CONTENT,
259
- LinearLayout.LayoutParams.WRAP_CONTENT
260
- )
261
- text = "Mobile"
262
- setTextColor(Color.parseColor("#B3FFFFFF"))
263
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
264
- gravity = android.view.Gravity.CENTER
265
- })
266
- }
30
+ private enum class FinishReason { ANSWER, DECLINE, TIMEOUT,
31
+ MANUAL_DISMISS, EXTERNAL_END }
32
+ private var finishReason: FinishReason? = null
33
+ private var callId: String = ""
34
+ private var callType: String = "Audio"
35
+ private val timeoutMs = 60_000L
36
+ private val handler = Handler(Looper.getMainLooper())
37
+ private val timeoutRunnable = Runnable {
38
+ finishReason = FinishReason.TIMEOUT
39
+ CallEngine.stopRingtone()
40
+ CallEngine.cancelIncomingCallUI()
41
+ CallEngine.endCall(callId)
42
+ finishCallActivity()
43
+ }
44
+
45
+ override fun onCreate(savedInstanceState: Bundle?) {
46
+ super.onCreate(savedInstanceState)
47
+ val isSamsungBypass = intent.getBooleanExtra(
48
+ "SAMSUNG_LOCK_SCREEN_BYPASS", false
49
+ )
50
+ setupLockScreenBypass(isSamsungBypass)
51
+
52
+ // Read params
53
+ callId = intent.getStringExtra("callId") ?: ""
54
+ callType = intent.getStringExtra("callType") ?: "Audio"
55
+ val callerName = intent.getStringExtra("callerName") ?: "Unknown"
56
+ val avatarUrl = intent.getStringExtra("callerAvatar")
57
+
58
+ CallEngine.registerCallEndListener(this)
59
+ buildUi(callerName, avatarUrl)
60
+ handler.postDelayed(timeoutRunnable, timeoutMs)
61
+ Log.d(TAG, "CallActivity setup complete for $callId")
62
+ }
63
+
64
+ private fun buildUi(name: String, avatarUrl: String?) {
65
+ val root = FrameLayout(this).apply {
66
+ layoutParams = ViewGroup.LayoutParams(
67
+ ViewGroup.LayoutParams.MATCH_PARENT,
68
+ ViewGroup.LayoutParams.MATCH_PARENT
69
+ )
267
70
  }
268
71
 
269
- private fun createQuickActionsSection(): LinearLayout {
270
- return LinearLayout(this).apply {
271
- layoutParams = LinearLayout.LayoutParams(
272
- LinearLayout.LayoutParams.MATCH_PARENT,
273
- LinearLayout.LayoutParams.WRAP_CONTENT
274
- )
275
- orientation = LinearLayout.HORIZONTAL
276
- gravity = android.view.Gravity.CENTER
277
-
278
- // Remind me action
279
- addView(createQuickAction("⏰", "Remind me").apply {
280
- layoutParams = (layoutParams as LinearLayout.LayoutParams).apply {
281
- setMargins(0, 0, dp(40), 0)
282
- }
283
- })
284
-
285
- // Message action
286
- addView(createQuickAction("💬", "Message"))
287
- }
72
+ // 1) Background image + blur
73
+ val bg = ImageView(this).apply {
74
+ layoutParams = FrameLayout.LayoutParams(
75
+ ViewGroup.LayoutParams.MATCH_PARENT,
76
+ ViewGroup.LayoutParams.MATCH_PARENT
77
+ )
78
+ scaleType = ImageView.ScaleType.CENTER_CROP
288
79
  }
289
-
290
- private fun createQuickAction(icon: String, label: String): LinearLayout {
291
- return LinearLayout(this).apply {
292
- layoutParams = LinearLayout.LayoutParams(
293
- LinearLayout.LayoutParams.WRAP_CONTENT,
294
- LinearLayout.LayoutParams.WRAP_CONTENT
295
- )
296
- orientation = LinearLayout.VERTICAL
297
- gravity = android.view.Gravity.CENTER
298
- setPadding(dp(12), dp(12), dp(12), dp(12))
299
- background = createQuickActionBackground()
300
- isClickable = true
301
-
302
- // Icon
303
- addView(TextView(this@CallActivity).apply {
304
- layoutParams = LinearLayout.LayoutParams(
305
- LinearLayout.LayoutParams.WRAP_CONTENT,
306
- LinearLayout.LayoutParams.WRAP_CONTENT
307
- ).apply { setMargins(0, 0, 0, dp(4)) }
308
- text = icon
309
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
310
- })
311
-
312
- // Label
313
- addView(TextView(this@CallActivity).apply {
314
- layoutParams = LinearLayout.LayoutParams(
315
- LinearLayout.LayoutParams.WRAP_CONTENT,
316
- LinearLayout.LayoutParams.WRAP_CONTENT
317
- )
318
- text = label
319
- setTextColor(Color.WHITE)
320
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
321
- })
322
- }
80
+ if (!avatarUrl.isNullOrEmpty()) {
81
+ Glide.with(this).load(avatarUrl).into(bg)
82
+ } else {
83
+ bg.setImageResource(android.R.drawable.sym_def_app_icon)
323
84
  }
324
-
325
- private fun createQuickActionBackground(): GradientDrawable {
326
- return GradientDrawable().apply {
327
- shape = GradientDrawable.RECTANGLE
328
- cornerRadius = dp(16).toFloat()
329
- setColor(Color.parseColor("#20FFFFFF"))
330
- setStroke(dp(1), Color.parseColor("#30FFFFFF"))
331
- }
85
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
86
+ bg.setRenderEffect(
87
+ RenderEffect.createBlurEffect(25f, 25f, Shader.TileMode.CLAMP)
88
+ )
332
89
  }
333
-
334
- private fun createCallActionsSection(): LinearLayout {
335
- return LinearLayout(this).apply {
336
- layoutParams = LinearLayout.LayoutParams(
337
- LinearLayout.LayoutParams.MATCH_PARENT,
338
- LinearLayout.LayoutParams.WRAP_CONTENT
339
- )
340
- orientation = LinearLayout.HORIZONTAL
341
- gravity = android.view.Gravity.CENTER
342
-
343
- // Decline button
344
- declineButton = createCallActionButton("✕", "#FFF44336", "#FFD32F2F").apply {
345
- layoutParams = LinearLayout.LayoutParams(dp(80), dp(80)).apply {
346
- setMargins(0, 0, dp(80), 0)
347
- }
348
- }
349
- addView(declineButton)
350
-
351
- // Answer button
352
- answerButton = createCallActionButton("✓", "#FF4CAF50", "#FF388E3C").apply {
353
- layoutParams = LinearLayout.LayoutParams(dp(80), dp(80))
354
- }
355
- addView(answerButton)
356
- }
90
+ root.addView(bg)
91
+
92
+ // 2) Dark scrim
93
+ root.addView(View(this).apply {
94
+ layoutParams = FrameLayout.LayoutParams(
95
+ ViewGroup.LayoutParams.MATCH_PARENT,
96
+ ViewGroup.LayoutParams.MATCH_PARENT
97
+ )
98
+ setBackgroundColor(Color.parseColor("#80000000"))
99
+ })
100
+
101
+ // 3) Top call-type label
102
+ val callTypeLabel = TextView(this).apply {
103
+ text = when (callType.lowercase()) {
104
+ "video" -> "Incoming video call"
105
+ else -> "Incoming audio call"
106
+ }
107
+ setTextColor(Color.WHITE)
108
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
109
+ gravity = Gravity.CENTER
110
+ layoutParams = FrameLayout.LayoutParams(
111
+ ViewGroup.LayoutParams.WRAP_CONTENT,
112
+ ViewGroup.LayoutParams.WRAP_CONTENT
113
+ ).apply {
114
+ gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
115
+ topMargin = dp(40)
116
+ }
357
117
  }
358
-
359
- private fun createCallActionButton(text: String, startColor: String, endColor: String): FrameLayout {
360
- return FrameLayout(this).apply {
361
- // Shadow
362
- addView(View(this@CallActivity).apply {
363
- layoutParams = FrameLayout.LayoutParams(dp(80), dp(80))
364
- background = GradientDrawable().apply {
365
- shape = GradientDrawable.OVAL
366
- colors = intArrayOf(
367
- Color.parseColor("#40000000"),
368
- Color.parseColor("#20000000"),
369
- Color.parseColor("#00000000")
370
- )
371
- gradientType = GradientDrawable.RADIAL_GRADIENT
372
- gradientRadius = dp(40).toFloat()
373
- }
374
- })
375
-
376
- // Button
377
- addView(TextView(this@CallActivity).apply {
378
- layoutParams = FrameLayout.LayoutParams(dp(72), dp(72)).apply {
379
- gravity = android.view.Gravity.CENTER
380
- }
381
- this.text = text
382
- setTextColor(Color.WHITE)
383
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 28f)
384
- typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
385
- gravity = android.view.Gravity.CENTER
386
- background = GradientDrawable().apply {
387
- shape = GradientDrawable.OVAL
388
- colors = intArrayOf(
389
- Color.parseColor(startColor),
390
- Color.parseColor(endColor)
391
- )
392
- orientation = GradientDrawable.Orientation.TL_BR
393
- setStroke(dp(2), Color.parseColor("#FF66BB6A"))
394
- }
395
- isClickable = true
396
- })
397
- }
118
+ root.addView(callTypeLabel)
119
+
120
+ // 4) Center content: profile + name
121
+ val centerCol = LinearLayout(this).apply {
122
+ orientation = LinearLayout.VERTICAL
123
+ gravity = Gravity.CENTER_HORIZONTAL
124
+ layoutParams = FrameLayout.LayoutParams(
125
+ ViewGroup.LayoutParams.MATCH_PARENT,
126
+ ViewGroup.LayoutParams.WRAP_CONTENT
127
+ ).apply {
128
+ gravity = Gravity.CENTER
129
+ topMargin = dp(20)
130
+ }
398
131
  }
399
-
400
- private fun createSpacerView(heightDp: Int): View {
401
- return View(this).apply {
402
- layoutParams = LinearLayout.LayoutParams(
403
- LinearLayout.LayoutParams.MATCH_PARENT,
404
- dp(heightDp)
405
- )
406
- }
132
+ // Profile circle
133
+ val profile = ImageView(this).apply {
134
+ val sz = dp(140)
135
+ layoutParams = LinearLayout.LayoutParams(sz, sz)
136
+ scaleType = ImageView.ScaleType.CENTER_CROP
137
+ background = GradientDrawable().apply {
138
+ shape = GradientDrawable.OVAL
139
+ setColor(Color.LTGRAY)
140
+ }
141
+ clipToOutline = true
407
142
  }
408
-
409
- private fun createFlexibleSpacerView(): View {
410
- return View(this).apply {
411
- layoutParams = LinearLayout.LayoutParams(
412
- LinearLayout.LayoutParams.MATCH_PARENT,
413
- 0,
414
- 1f
415
- )
416
- }
143
+ if (!avatarUrl.isNullOrEmpty()) {
144
+ Glide.with(this).load(avatarUrl).circleCrop().into(profile)
417
145
  }
418
-
419
- private fun updateCallerInfo(callerName: String, callerAvatar: String?) {
420
- callerNameText.text = callerName
421
-
422
- if (callerAvatar != null && callerAvatar.isNotEmpty()) {
423
- // Load profile picture (you can use Glide, Picasso, or similar)
424
- // For now, just show default
425
- profileImage.setImageResource(android.R.drawable.sym_def_app_icon)
426
- } else {
427
- // Create default avatar with initials
428
- profileImage.setImageDrawable(createDefaultAvatar(callerName))
429
- }
146
+ centerCol.addView(profile)
147
+
148
+ // Caller name
149
+ centerCol.addView(TextView(this).apply {
150
+ text = name
151
+ setTextColor(Color.WHITE)
152
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 28f)
153
+ typeface = android.graphics.Typeface.DEFAULT_BOLD
154
+ gravity = Gravity.CENTER
155
+ layoutParams = LinearLayout.LayoutParams(
156
+ ViewGroup.LayoutParams.WRAP_CONTENT,
157
+ ViewGroup.LayoutParams.WRAP_CONTENT
158
+ ).apply { topMargin = dp(24) }
159
+ })
160
+
161
+ root.addView(centerCol)
162
+
163
+ // 5) Bottom actions
164
+ val actions = LinearLayout(this).apply {
165
+ orientation = LinearLayout.HORIZONTAL
166
+ gravity = Gravity.CENTER
167
+ layoutParams = FrameLayout.LayoutParams(
168
+ ViewGroup.LayoutParams.MATCH_PARENT,
169
+ ViewGroup.LayoutParams.WRAP_CONTENT
170
+ ).apply {
171
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
172
+ bottomMargin = dp(48)
173
+ }
430
174
  }
431
175
 
432
- private fun createDefaultAvatar(name: String): GradientDrawable {
433
- return GradientDrawable().apply {
434
- shape = GradientDrawable.OVAL
435
- setColor(Color.parseColor("#FF4CAF50"))
176
+ val declineBtn = createCircleButton(
177
+ android.R.drawable.ic_menu_close_clear_cancel,
178
+ Color.parseColor("#F44336")
179
+ ).apply { setOnClickListener { onDecline() } }
180
+
181
+ val answerBtn = createCircleButton(
182
+ android.R.drawable.ic_menu_call,
183
+ Color.parseColor("#4CAF50")
184
+ ).apply { setOnClickListener { onAnswer() } }
185
+
186
+ actions.addView(declineBtn)
187
+ actions.addView(View(this).apply {
188
+ layoutParams = LinearLayout.LayoutParams(dp(60), 0)
189
+ })
190
+ actions.addView(answerBtn)
191
+ root.addView(actions)
192
+
193
+ setContentView(root)
194
+ }
195
+
196
+ private fun createCircleButton(iconRes: Int, bgColor: Int): FrameLayout {
197
+ val size = dp(70)
198
+ return FrameLayout(this).apply {
199
+ layoutParams = LinearLayout.LayoutParams(size, size)
200
+ isClickable = true; isFocusable = true
201
+ foreground = getRipple()
202
+ // circle bg
203
+ addView(View(context).apply {
204
+ layoutParams = FrameLayout.LayoutParams(
205
+ ViewGroup.LayoutParams.MATCH_PARENT,
206
+ ViewGroup.LayoutParams.MATCH_PARENT
207
+ )
208
+ background = GradientDrawable().apply {
209
+ shape = GradientDrawable.OVAL
210
+ setColor(bgColor)
436
211
  }
212
+ })
213
+ // icon
214
+ addView(ImageView(context).apply {
215
+ layoutParams = FrameLayout.LayoutParams(dp(36), dp(36))
216
+ .apply { gravity = Gravity.CENTER }
217
+ setImageResource(iconRes)
218
+ setColorFilter(Color.WHITE)
219
+ })
437
220
  }
438
-
439
- private fun setupButtonListeners() {
440
- answerButton.setOnClickListener {
441
- Log.d(TAG, "Answer clicked for callId: $callId")
442
- finishReason = FinishReason.ANSWER
443
- CallEngine.stopRingtone()
444
- CallEngine.cancelIncomingCallUI()
445
- CallEngine.answerCall(callId)
446
- finishCallActivity()
447
- }
448
-
449
- declineButton.setOnClickListener {
450
- Log.d(TAG, "Decline clicked for callId: $callId")
451
- finishReason = FinishReason.DECLINE
452
- CallEngine.stopRingtone()
453
- CallEngine.cancelIncomingCallUI()
454
- CallEngine.endCall(callId)
455
- finishCallActivity()
456
- }
221
+ }
222
+
223
+ private fun getRipple(): Drawable? {
224
+ val tv = TypedValue()
225
+ theme.resolveAttribute(
226
+ android.R.attr.selectableItemBackgroundBorderless, tv, true
227
+ )
228
+ return ContextCompat.getDrawable(this, tv.resourceId)
229
+ }
230
+
231
+ private fun onAnswer() {
232
+ finishReason = FinishReason.ANSWER
233
+ CallEngine.stopRingtone()
234
+ CallEngine.cancelIncomingCallUI()
235
+ CallEngine.answerCall(callId)
236
+ finishCallActivity()
237
+ }
238
+
239
+ private fun onDecline() {
240
+ finishReason = FinishReason.DECLINE
241
+ CallEngine.stopRingtone()
242
+ CallEngine.cancelIncomingCallUI()
243
+ CallEngine.endCall(callId)
244
+ finishCallActivity()
245
+ }
246
+
247
+ private fun dp(v: Int): Int = TypedValue.applyDimension(
248
+ TypedValue.COMPLEX_UNIT_DIP, v.toFloat(),
249
+ resources.displayMetrics
250
+ ).toInt()
251
+
252
+ private fun setupLockScreenBypass(isSamsung: Boolean) {
253
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
254
+ setShowWhenLocked(true); setTurnScreenOn(true)
255
+ } else {
256
+ window.addFlags(
257
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
258
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
259
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
260
+ )
457
261
  }
458
-
459
- private fun startPulseAnimation() {
460
- pulseAnimator = ValueAnimator.ofFloat(1f, 1.2f).apply {
461
- duration = 1500
462
- repeatCount = ValueAnimator.INFINITE
463
- repeatMode = ValueAnimator.REVERSE
464
- interpolator = LinearInterpolator()
465
-
466
- addUpdateListener { animation ->
467
- val scale = animation.animatedValue as Float
468
- // Apply to pulse rings (you can get references to them if needed)
469
- // For now, this is a placeholder for the animation logic
262
+ if (isSamsung) {
263
+ window.addFlags(
264
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
265
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
266
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
267
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
268
+ )
269
+ val km = getSystemService(Context.KEYGUARD_SERVICE)
270
+ as KeyguardManager
271
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
272
+ km.requestDismissKeyguard(this,
273
+ object : KeyguardManager.KeyguardDismissCallback() {
274
+ override fun onDismissSucceeded() {
275
+ Log.d(TAG, "Samsung keyguard dismissed")
470
276
  }
471
- }
472
- pulseAnimator?.start()
473
- }
474
-
475
- private fun setupLockScreenBypass(isSamsungSpecific: Boolean) {
476
- // Standard lock screen bypass
477
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
478
- setShowWhenLocked(true)
479
- setTurnScreenOn(true)
480
- } else {
481
- window.addFlags(
482
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
483
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
484
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
485
- )
486
- }
487
-
488
- // Samsung specific fixes
489
- if (isSamsungSpecific) {
490
- Log.d(TAG, "Applying Samsung-specific lock screen bypass")
491
- window.addFlags(
492
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
493
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
494
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
495
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
496
- )
497
-
498
- val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
499
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
500
- keyguardManager.requestDismissKeyguard(this, object : KeyguardManager.KeyguardDismissCallback() {
501
- override fun onDismissSucceeded() {
502
- Log.d(TAG, "Samsung keyguard dismissed successfully")
503
- }
504
- override fun onDismissError() {
505
- Log.w(TAG, "Samsung keyguard dismissal failed")
506
- }
507
- })
277
+ override fun onDismissError() {
278
+ Log.w(TAG, "Samsung keyguard dismiss error")
508
279
  }
509
- }
510
- }
511
-
512
- // Helper function to convert dp to pixels
513
- private fun dp(value: Int): Int {
514
- return TypedValue.applyDimension(
515
- TypedValue.COMPLEX_UNIT_DIP,
516
- value.toFloat(),
517
- resources.displayMetrics
518
- ).toInt()
280
+ })
281
+ }
519
282
  }
520
-
521
- override fun onDestroy() {
522
- super.onDestroy()
523
- Log.d(TAG, "CallActivity onDestroy for callId: $callId. Reason: $finishReason")
524
-
525
- // Stop animations
526
- pulseAnimator?.cancel()
527
-
528
- // Unregister listener
529
- CallEngine.unregisterCallEndListener(this)
530
-
531
- // Cancel timeout
532
- timeoutHandler.removeCallbacks(timeoutRunnable)
533
-
534
- // If user never answered, clean up ringtone/UI
535
- if (finishReason != FinishReason.ANSWER) {
536
- CallEngine.stopRingtone()
537
- CallEngine.cancelIncomingCallUI()
538
- }
539
- }
540
-
541
- override fun onBackPressed() {
542
- Log.d(TAG, "onBackPressed for callId: $callId → treat as decline")
543
- finishReason = FinishReason.MANUAL_DISMISS
544
- CallEngine.stopRingtone()
545
- CallEngine.cancelIncomingCallUI()
546
- CallEngine.endCall(callId)
547
- finishCallActivity()
283
+ }
284
+
285
+ override fun onBackPressed() {
286
+ finishReason = FinishReason.MANUAL_DISMISS
287
+ CallEngine.stopRingtone()
288
+ CallEngine.cancelIncomingCallUI()
289
+ CallEngine.endCall(callId)
290
+ finishCallActivity()
291
+ }
292
+
293
+ override fun onCallEnded(endedCallId: String) {
294
+ if (endedCallId == callId && !isFinishing) {
295
+ finishReason = FinishReason.EXTERNAL_END
296
+ runOnUiThread { finishCallActivity() }
548
297
  }
549
-
550
- override fun onCallEnded(endedCallId: String) {
551
- if (endedCallId == callId && !isFinishing) {
552
- Log.d(TAG, "CallActivity onCallEnded callback for callId: $callId")
553
- finishReason = FinishReason.EXTERNAL_END
554
- runOnUiThread { finishCallActivity() }
555
- }
298
+ }
299
+
300
+ override fun onDestroy() {
301
+ super.onDestroy()
302
+ CallEngine.unregisterCallEndListener(this)
303
+ handler.removeCallbacks(timeoutRunnable)
304
+ if (finishReason != FinishReason.ANSWER) {
305
+ CallEngine.stopRingtone()
306
+ CallEngine.cancelIncomingCallUI()
556
307
  }
557
-
558
- private fun finishCallActivity() {
559
- if (isFinishing) {
560
- Log.d(TAG, "Already finishing, skip.")
561
- return
562
- }
563
- Log.d(TAG, "Finishing CallActivity for callId: $callId")
564
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
565
- finishAndRemoveTask()
566
- } else {
567
- finish()
568
- }
308
+ }
309
+
310
+ private fun finishCallActivity() {
311
+ if (isFinishing) return
312
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
313
+ finishAndRemoveTask()
314
+ } else {
315
+ finish()
569
316
  }
317
+ }
570
318
 
571
- companion object {
572
- private const val TAG = "CallActivity"
573
- }
319
+ companion object {
320
+ private const val TAG = "CallActivity"
321
+ }
574
322
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.96",
3
+ "version": "0.1.97",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",