@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.
|
|
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
|
|
27
|
+
import com.bumptech.glide.Glide
|
|
24
28
|
|
|
25
29
|
class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
319
|
+
companion object {
|
|
320
|
+
private const val TAG = "CallActivity"
|
|
321
|
+
}
|
|
574
322
|
}
|