@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.
|
|
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
|
|
28
|
+
import java.net.HttpURLConnection
|
|
29
|
+
import java.net.URL
|
|
24
30
|
|
|
25
31
|
class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
347
|
+
companion object {
|
|
348
|
+
private const val TAG = "CallActivity"
|
|
349
|
+
}
|
|
574
350
|
}
|