@qusaieilouti99/call-manager 0.1.96 → 0.1.98

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,14 @@
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.BitmapFactory
8
+ import android.graphics.Color
9
+ import android.graphics.RenderEffect
10
+ import android.graphics.Shader
11
+ import android.graphics.drawable.Drawable
10
12
  import android.graphics.drawable.GradientDrawable
11
13
  import android.os.Build
12
14
  import android.os.Bundle
@@ -14,561 +16,335 @@ import android.os.Handler
14
16
  import android.os.Looper
15
17
  import android.util.Log
16
18
  import android.util.TypedValue
19
+ import android.view.Gravity
17
20
  import android.view.View
18
21
  import android.view.ViewGroup
19
22
  import android.view.WindowManager
20
- import android.view.animation.LinearInterpolator
21
- import android.widget.*
23
+ import android.widget.FrameLayout
24
+ import android.widget.ImageView
25
+ import android.widget.LinearLayout
26
+ import android.widget.TextView
22
27
  import androidx.core.content.ContextCompat
23
- import androidx.core.view.ViewCompat
28
+ import java.net.HttpURLConnection
29
+ import java.net.URL
24
30
 
25
31
  class CallActivity : Activity(), CallEngine.CallEndListener {
26
32
 
27
- private enum class FinishReason {
28
- ANSWER, DECLINE, TIMEOUT, MANUAL_DISMISS, EXTERNAL_END
33
+ private enum class FinishReason { ANSWER, DECLINE, TIMEOUT,
34
+ MANUAL_DISMISS, EXTERNAL_END }
35
+ private var finishReason: FinishReason? = null
36
+ private var callId: String = ""
37
+ private var callType: String = "Audio"
38
+ private val timeoutMs = 60_000L
39
+ private val handler = Handler(Looper.getMainLooper())
40
+ private val timeoutRunnable = Runnable {
41
+ finishReason = FinishReason.TIMEOUT
42
+ CallEngine.stopRingtone()
43
+ CallEngine.cancelIncomingCallUI()
44
+ CallEngine.endCall(callId)
45
+ finishCallActivity()
46
+ }
47
+
48
+ override fun onCreate(savedInstanceState: Bundle?) {
49
+ super.onCreate(savedInstanceState)
50
+ val isSamsungBypass = intent.getBooleanExtra(
51
+ "SAMSUNG_LOCK_SCREEN_BYPASS", false
52
+ )
53
+ setupLockScreenBypass(isSamsungBypass)
54
+
55
+ // Read params
56
+ callId = intent.getStringExtra("callId") ?: ""
57
+ callType = intent.getStringExtra("callType") ?: "Audio"
58
+ val callerName = intent.getStringExtra("callerName") ?: "Unknown"
59
+ val avatarUrl = intent.getStringExtra("callerAvatar")
60
+
61
+ CallEngine.registerCallEndListener(this)
62
+ buildUi(callerName, avatarUrl)
63
+ handler.postDelayed(timeoutRunnable, timeoutMs)
64
+ Log.d(TAG, "CallActivity setup complete for callId=$callId")
65
+ }
66
+
67
+ private fun buildUi(name: String, avatarUrl: String?) {
68
+ val root = FrameLayout(this).apply {
69
+ layoutParams = ViewGroup.LayoutParams(
70
+ ViewGroup.LayoutParams.MATCH_PARENT,
71
+ ViewGroup.LayoutParams.MATCH_PARENT
72
+ )
29
73
  }
30
74
 
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()
75
+ // 1) Background ImageView + optional blur
76
+ val bg = ImageView(this).apply {
77
+ layoutParams = FrameLayout.LayoutParams(
78
+ ViewGroup.LayoutParams.MATCH_PARENT,
79
+ ViewGroup.LayoutParams.MATCH_PARENT
80
+ )
81
+ scaleType = ImageView.ScaleType.CENTER_CROP
82
+ // apply RenderEffect blur on API31+
83
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
84
+ setRenderEffect(
85
+ RenderEffect.createBlurEffect(
86
+ 25f, 25f, Shader.TileMode.CLAMP
87
+ )
88
+ )
89
+ }
53
90
  }
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
- }
267
- }
268
-
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
- }
91
+ root.addView(bg)
92
+ loadAndBlurBackground(bg, avatarUrl)
93
+
94
+ // 2) Dark scrim
95
+ root.addView(View(this).apply {
96
+ layoutParams = FrameLayout.LayoutParams(
97
+ ViewGroup.LayoutParams.MATCH_PARENT,
98
+ ViewGroup.LayoutParams.MATCH_PARENT
99
+ )
100
+ setBackgroundColor(Color.parseColor("#80000000"))
101
+ })
102
+
103
+ // 3) Top call-type label
104
+ val callTypeLabel = TextView(this).apply {
105
+ text = when (callType.lowercase()) {
106
+ "video" -> "Incoming video call"
107
+ else -> "Incoming audio call"
108
+ }
109
+ setTextColor(Color.WHITE)
110
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
111
+ gravity = Gravity.CENTER
112
+ layoutParams = FrameLayout.LayoutParams(
113
+ ViewGroup.LayoutParams.WRAP_CONTENT,
114
+ ViewGroup.LayoutParams.WRAP_CONTENT
115
+ ).apply {
116
+ gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
117
+ topMargin = dp(40)
118
+ }
288
119
  }
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
- }
323
- }
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
- }
332
- }
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
- }
357
- }
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
- }
398
- }
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
- }
120
+ root.addView(callTypeLabel)
121
+
122
+ // 4) Center content: profile + name
123
+ val centerCol = LinearLayout(this).apply {
124
+ orientation = LinearLayout.VERTICAL
125
+ gravity = Gravity.CENTER_HORIZONTAL
126
+ layoutParams = FrameLayout.LayoutParams(
127
+ ViewGroup.LayoutParams.MATCH_PARENT,
128
+ ViewGroup.LayoutParams.WRAP_CONTENT
129
+ ).apply {
130
+ gravity = Gravity.CENTER
131
+ topMargin = dp(20)
132
+ }
407
133
  }
408
134
 
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
- }
135
+ // Profile circle
136
+ val profile = ImageView(this).apply {
137
+ val sz = dp(140)
138
+ layoutParams = LinearLayout.LayoutParams(sz, sz)
139
+ scaleType = ImageView.ScaleType.CENTER_CROP
140
+ background = GradientDrawable().apply {
141
+ shape = GradientDrawable.OVAL
142
+ setColor(Color.LTGRAY)
143
+ }
144
+ // enable clipping to our oval background
145
+ clipToOutline = true
417
146
  }
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
- }
147
+ centerCol.addView(profile)
148
+ loadAvatar(profile, avatarUrl)
149
+
150
+ // Caller name
151
+ centerCol.addView(TextView(this).apply {
152
+ text = name
153
+ setTextColor(Color.WHITE)
154
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 28f)
155
+ typeface = android.graphics.Typeface.DEFAULT_BOLD
156
+ gravity = Gravity.CENTER
157
+ layoutParams = LinearLayout.LayoutParams(
158
+ ViewGroup.LayoutParams.WRAP_CONTENT,
159
+ ViewGroup.LayoutParams.WRAP_CONTENT
160
+ ).apply { topMargin = dp(24) }
161
+ })
162
+
163
+ root.addView(centerCol)
164
+
165
+ // 5) Bottom actions
166
+ val actions = LinearLayout(this).apply {
167
+ orientation = LinearLayout.HORIZONTAL
168
+ gravity = Gravity.CENTER
169
+ layoutParams = FrameLayout.LayoutParams(
170
+ ViewGroup.LayoutParams.MATCH_PARENT,
171
+ ViewGroup.LayoutParams.WRAP_CONTENT
172
+ ).apply {
173
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
174
+ bottomMargin = dp(48)
175
+ }
430
176
  }
431
177
 
432
- private fun createDefaultAvatar(name: String): GradientDrawable {
433
- return GradientDrawable().apply {
434
- shape = GradientDrawable.OVAL
435
- setColor(Color.parseColor("#FF4CAF50"))
178
+ val declineBtn = createCircleButton(
179
+ android.R.drawable.ic_menu_close_clear_cancel,
180
+ Color.parseColor("#F44336")
181
+ ).apply { setOnClickListener { onDecline() } }
182
+
183
+ val answerBtn = createCircleButton(
184
+ android.R.drawable.ic_menu_call,
185
+ Color.parseColor("#4CAF50")
186
+ ).apply { setOnClickListener { onAnswer() } }
187
+
188
+ actions.addView(declineBtn)
189
+ actions.addView(View(this).apply {
190
+ layoutParams = LinearLayout.LayoutParams(dp(60), 0)
191
+ })
192
+ actions.addView(answerBtn)
193
+ root.addView(actions)
194
+
195
+ setContentView(root)
196
+ }
197
+
198
+ // Plain HTTP loader + setImageBitmap
199
+ private fun loadAvatar(iv: ImageView, url: String?) {
200
+ if (url.isNullOrEmpty()) return
201
+ Thread {
202
+ try {
203
+ val conn = URL(url).openConnection() as HttpURLConnection
204
+ conn.doInput = true; conn.connect()
205
+ val bmp = BitmapFactory.decodeStream(conn.inputStream)
206
+ runOnUiThread { iv.setImageBitmap(bmp) }
207
+ } catch (_: Exception) { /* ignore */ }
208
+ }.start()
209
+ }
210
+
211
+ // Same loader for background
212
+ private fun loadAndBlurBackground(iv: ImageView, url: String?) {
213
+ if (url.isNullOrEmpty()) return
214
+ Thread {
215
+ try {
216
+ val conn = URL(url).openConnection() as HttpURLConnection
217
+ conn.doInput = true; conn.connect()
218
+ val bmp = BitmapFactory.decodeStream(conn.inputStream)
219
+ runOnUiThread { iv.setImageBitmap(bmp) }
220
+ } catch (_: Exception) { /* ignore */ }
221
+ }.start()
222
+ }
223
+
224
+ private fun createCircleButton(iconRes: Int, bgColor: Int): FrameLayout {
225
+ val size = dp(70)
226
+ return FrameLayout(this).apply {
227
+ layoutParams = LinearLayout.LayoutParams(size, size)
228
+ isClickable = true; isFocusable = true
229
+ foreground = getRipple()
230
+ // colored circle
231
+ addView(View(context).apply {
232
+ layoutParams = FrameLayout.LayoutParams(
233
+ ViewGroup.LayoutParams.MATCH_PARENT,
234
+ ViewGroup.LayoutParams.MATCH_PARENT
235
+ )
236
+ background = GradientDrawable().apply {
237
+ shape = GradientDrawable.OVAL
238
+ setColor(bgColor)
436
239
  }
240
+ })
241
+ // icon
242
+ addView(ImageView(context).apply {
243
+ layoutParams = FrameLayout.LayoutParams(dp(36), dp(36))
244
+ .apply { gravity = Gravity.CENTER }
245
+ setImageResource(iconRes)
246
+ setColorFilter(Color.WHITE)
247
+ })
437
248
  }
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
- }
249
+ }
250
+
251
+ private fun getRipple(): Drawable? {
252
+ val tv = TypedValue()
253
+ theme.resolveAttribute(
254
+ android.R.attr.selectableItemBackgroundBorderless, tv, true
255
+ )
256
+ return ContextCompat.getDrawable(this, tv.resourceId)
257
+ }
258
+
259
+ private fun onAnswer() {
260
+ finishReason = FinishReason.ANSWER
261
+ CallEngine.stopRingtone()
262
+ CallEngine.cancelIncomingCallUI()
263
+ CallEngine.answerCall(callId)
264
+ finishCallActivity()
265
+ }
266
+
267
+ private fun onDecline() {
268
+ finishReason = FinishReason.DECLINE
269
+ CallEngine.stopRingtone()
270
+ CallEngine.cancelIncomingCallUI()
271
+ CallEngine.endCall(callId)
272
+ finishCallActivity()
273
+ }
274
+
275
+ private fun dp(v: Int): Int = TypedValue.applyDimension(
276
+ TypedValue.COMPLEX_UNIT_DIP, v.toFloat(),
277
+ resources.displayMetrics
278
+ ).toInt()
279
+
280
+ private fun setupLockScreenBypass(isSamsung: Boolean) {
281
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
282
+ setShowWhenLocked(true); setTurnScreenOn(true)
283
+ } else {
284
+ window.addFlags(
285
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
286
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
287
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
288
+ )
457
289
  }
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
290
+ if (isSamsung) {
291
+ window.addFlags(
292
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
293
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
294
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
295
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
296
+ )
297
+ val km = getSystemService(Context.KEYGUARD_SERVICE)
298
+ as KeyguardManager
299
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
300
+ km.requestDismissKeyguard(this,
301
+ object : KeyguardManager.KeyguardDismissCallback() {
302
+ override fun onDismissSucceeded() {
303
+ Log.d(TAG, "Samsung keyguard dismissed")
470
304
  }
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
- })
305
+ override fun onDismissError() {
306
+ Log.w(TAG, "Samsung keyguard dismiss error")
508
307
  }
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()
519
- }
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
- }
308
+ })
309
+ }
539
310
  }
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()
311
+ }
312
+
313
+ override fun onBackPressed() {
314
+ finishReason = FinishReason.MANUAL_DISMISS
315
+ CallEngine.stopRingtone()
316
+ CallEngine.cancelIncomingCallUI()
317
+ CallEngine.endCall(callId)
318
+ finishCallActivity()
319
+ }
320
+
321
+ override fun onCallEnded(endedCallId: String) {
322
+ if (endedCallId == callId && !isFinishing) {
323
+ finishReason = FinishReason.EXTERNAL_END
324
+ runOnUiThread { finishCallActivity() }
548
325
  }
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
- }
326
+ }
327
+
328
+ override fun onDestroy() {
329
+ super.onDestroy()
330
+ CallEngine.unregisterCallEndListener(this)
331
+ handler.removeCallbacks(timeoutRunnable)
332
+ if (finishReason != FinishReason.ANSWER) {
333
+ CallEngine.stopRingtone()
334
+ CallEngine.cancelIncomingCallUI()
556
335
  }
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
- }
336
+ }
337
+
338
+ private fun finishCallActivity() {
339
+ if (isFinishing) return
340
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
341
+ finishAndRemoveTask()
342
+ } else {
343
+ finish()
569
344
  }
345
+ }
570
346
 
571
- companion object {
572
- private const val TAG = "CallActivity"
573
- }
347
+ companion object {
348
+ private const val TAG = "CallActivity"
349
+ }
574
350
  }
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.98",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",