@qusaieilouti99/call-manager 0.1.98 → 0.1.100
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.
|
@@ -4,12 +4,14 @@ package com.margelo.nitro.qusaieilouti99.callmanager
|
|
|
4
4
|
import android.app.Activity
|
|
5
5
|
import android.app.KeyguardManager
|
|
6
6
|
import android.content.Context
|
|
7
|
+
import android.graphics.Bitmap
|
|
7
8
|
import android.graphics.BitmapFactory
|
|
8
9
|
import android.graphics.Color
|
|
9
10
|
import android.graphics.RenderEffect
|
|
10
11
|
import android.graphics.Shader
|
|
11
|
-
import android.graphics.
|
|
12
|
+
import android.graphics.Typeface
|
|
12
13
|
import android.graphics.drawable.GradientDrawable
|
|
14
|
+
import android.graphics.drawable.RippleDrawable
|
|
13
15
|
import android.os.Build
|
|
14
16
|
import android.os.Bundle
|
|
15
17
|
import android.os.Handler
|
|
@@ -24,19 +26,20 @@ import android.widget.FrameLayout
|
|
|
24
26
|
import android.widget.ImageView
|
|
25
27
|
import android.widget.LinearLayout
|
|
26
28
|
import android.widget.TextView
|
|
27
|
-
import androidx.core.content.ContextCompat
|
|
28
29
|
import java.net.HttpURLConnection
|
|
29
30
|
import java.net.URL
|
|
30
31
|
|
|
31
32
|
class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
32
33
|
|
|
33
|
-
private enum class FinishReason {
|
|
34
|
-
|
|
34
|
+
private enum class FinishReason {
|
|
35
|
+
ANSWER, DECLINE, TIMEOUT, MANUAL_DISMISS, EXTERNAL_END
|
|
36
|
+
}
|
|
35
37
|
private var finishReason: FinishReason? = null
|
|
36
|
-
private var callId
|
|
37
|
-
private var callType
|
|
38
|
+
private var callId = ""
|
|
39
|
+
private var callType = "Audio"
|
|
40
|
+
|
|
38
41
|
private val timeoutMs = 60_000L
|
|
39
|
-
private val
|
|
42
|
+
private val uiHandler = Handler(Looper.getMainLooper())
|
|
40
43
|
private val timeoutRunnable = Runnable {
|
|
41
44
|
finishReason = FinishReason.TIMEOUT
|
|
42
45
|
CallEngine.stopRingtone()
|
|
@@ -52,7 +55,6 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
52
55
|
)
|
|
53
56
|
setupLockScreenBypass(isSamsungBypass)
|
|
54
57
|
|
|
55
|
-
// Read params
|
|
56
58
|
callId = intent.getStringExtra("callId") ?: ""
|
|
57
59
|
callType = intent.getStringExtra("callType") ?: "Audio"
|
|
58
60
|
val callerName = intent.getStringExtra("callerName") ?: "Unknown"
|
|
@@ -60,7 +62,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
60
62
|
|
|
61
63
|
CallEngine.registerCallEndListener(this)
|
|
62
64
|
buildUi(callerName, avatarUrl)
|
|
63
|
-
|
|
65
|
+
uiHandler.postDelayed(timeoutRunnable, timeoutMs)
|
|
64
66
|
Log.d(TAG, "CallActivity setup complete for callId=$callId")
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -72,18 +74,17 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
72
74
|
)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
// 1)
|
|
77
|
+
// 1) Full-screen background + blur
|
|
76
78
|
val bg = ImageView(this).apply {
|
|
77
79
|
layoutParams = FrameLayout.LayoutParams(
|
|
78
80
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
79
81
|
ViewGroup.LayoutParams.MATCH_PARENT
|
|
80
82
|
)
|
|
81
83
|
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
82
|
-
// apply RenderEffect blur on API31+
|
|
83
84
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
84
85
|
setRenderEffect(
|
|
85
86
|
RenderEffect.createBlurEffect(
|
|
86
|
-
|
|
87
|
+
50f, 50f, Shader.TileMode.CLAMP
|
|
87
88
|
)
|
|
88
89
|
)
|
|
89
90
|
}
|
|
@@ -100,12 +101,12 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
100
101
|
setBackgroundColor(Color.parseColor("#80000000"))
|
|
101
102
|
})
|
|
102
103
|
|
|
103
|
-
// 3)
|
|
104
|
-
val
|
|
105
|
-
text =
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
// 3) “Incoming ... call” with extra top padding (48dp)
|
|
105
|
+
val typeLabel = TextView(this).apply {
|
|
106
|
+
text = if (callType.equals("video", true))
|
|
107
|
+
"Incoming video call"
|
|
108
|
+
else
|
|
109
|
+
"Incoming audio call"
|
|
109
110
|
setTextColor(Color.WHITE)
|
|
110
111
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
|
111
112
|
gravity = Gravity.CENTER
|
|
@@ -114,55 +115,38 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
114
115
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
115
116
|
).apply {
|
|
116
117
|
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
117
|
-
topMargin = dp(
|
|
118
|
+
topMargin = dp(48)
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
|
-
root.addView(
|
|
121
|
+
root.addView(typeLabel)
|
|
121
122
|
|
|
122
|
-
// 4)
|
|
123
|
-
val
|
|
123
|
+
// 4) Avatar + name in top half
|
|
124
|
+
val avatarSection = LinearLayout(this).apply {
|
|
124
125
|
orientation = LinearLayout.VERTICAL
|
|
125
126
|
gravity = Gravity.CENTER_HORIZONTAL
|
|
126
127
|
layoutParams = FrameLayout.LayoutParams(
|
|
127
128
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
128
129
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
129
130
|
).apply {
|
|
130
|
-
gravity = Gravity.
|
|
131
|
-
topMargin = dp(
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
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)
|
|
131
|
+
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
132
|
+
topMargin = dp(100)
|
|
143
133
|
}
|
|
144
|
-
// enable clipping to our oval background
|
|
145
|
-
clipToOutline = true
|
|
146
134
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Caller name
|
|
151
|
-
centerCol.addView(TextView(this).apply {
|
|
135
|
+
avatarSection.addView(createAvatarView(name, avatarUrl))
|
|
136
|
+
avatarSection.addView(TextView(this).apply {
|
|
152
137
|
text = name
|
|
153
138
|
setTextColor(Color.WHITE)
|
|
154
139
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 28f)
|
|
155
|
-
typeface =
|
|
140
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
156
141
|
gravity = Gravity.CENTER
|
|
157
142
|
layoutParams = LinearLayout.LayoutParams(
|
|
158
143
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
159
144
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
160
|
-
).apply { topMargin = dp(
|
|
145
|
+
).apply { topMargin = dp(16) }
|
|
161
146
|
})
|
|
147
|
+
root.addView(avatarSection)
|
|
162
148
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// 5) Bottom actions
|
|
149
|
+
// 5) Bottom buttons with extra bottom padding (64dp)
|
|
166
150
|
val actions = LinearLayout(this).apply {
|
|
167
151
|
orientation = LinearLayout.HORIZONTAL
|
|
168
152
|
gravity = Gravity.CENTER
|
|
@@ -171,53 +155,129 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
171
155
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
172
156
|
).apply {
|
|
173
157
|
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
|
174
|
-
bottomMargin = dp(
|
|
158
|
+
bottomMargin = dp(64)
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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)
|
|
161
|
+
actions.addView(
|
|
162
|
+
createCircleButton(
|
|
163
|
+
android.R.drawable.ic_menu_close_clear_cancel,
|
|
164
|
+
Color.parseColor("#F44336")
|
|
165
|
+
).apply { setOnClickListener { onDecline() } }
|
|
166
|
+
)
|
|
189
167
|
actions.addView(View(this).apply {
|
|
190
168
|
layoutParams = LinearLayout.LayoutParams(dp(60), 0)
|
|
191
169
|
})
|
|
192
|
-
actions.addView(
|
|
170
|
+
actions.addView(
|
|
171
|
+
createCircleButton(
|
|
172
|
+
android.R.drawable.ic_menu_call,
|
|
173
|
+
Color.parseColor("#4CAF50")
|
|
174
|
+
).apply { setOnClickListener { onAnswer() } }
|
|
175
|
+
)
|
|
193
176
|
root.addView(actions)
|
|
194
177
|
|
|
195
178
|
setContentView(root)
|
|
196
179
|
}
|
|
197
180
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
181
|
+
private fun createAvatarView(name: String, url: String?): FrameLayout {
|
|
182
|
+
val size = dp(140)
|
|
183
|
+
// Gradient purple background
|
|
184
|
+
val gradientBg = GradientDrawable(
|
|
185
|
+
GradientDrawable.Orientation.TL_BR,
|
|
186
|
+
intArrayOf(
|
|
187
|
+
Color.parseColor("#8E24AA"),
|
|
188
|
+
Color.parseColor("#CE93D8")
|
|
189
|
+
)
|
|
190
|
+
).apply { shape = GradientDrawable.OVAL }
|
|
191
|
+
|
|
192
|
+
val container = FrameLayout(this).apply {
|
|
193
|
+
layoutParams = LinearLayout.LayoutParams(size, size)
|
|
194
|
+
background = gradientBg
|
|
195
|
+
clipToOutline = true
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ImageView (only visible when URL != null)
|
|
199
|
+
val iv = ImageView(this).apply {
|
|
200
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
201
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
202
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
203
|
+
)
|
|
204
|
+
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
205
|
+
}
|
|
206
|
+
container.addView(iv)
|
|
207
|
+
|
|
208
|
+
// Initials TextView
|
|
209
|
+
val initials = TextView(this).apply {
|
|
210
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
211
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
212
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
213
|
+
)
|
|
214
|
+
gravity = Gravity.CENTER
|
|
215
|
+
setTextColor(Color.WHITE)
|
|
216
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 32f)
|
|
217
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
218
|
+
text = getInitials(name)
|
|
219
|
+
}
|
|
220
|
+
container.addView(initials)
|
|
221
|
+
|
|
222
|
+
if (url.isNullOrEmpty()) {
|
|
223
|
+
iv.visibility = View.GONE
|
|
224
|
+
initials.visibility = View.VISIBLE
|
|
225
|
+
} else {
|
|
226
|
+
iv.visibility = View.VISIBLE
|
|
227
|
+
initials.visibility = View.GONE
|
|
228
|
+
loadAvatar(iv, url)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return container
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private fun getInitials(fullName: String): String {
|
|
235
|
+
val parts = fullName.trim().split("\\s+".toRegex())
|
|
236
|
+
return when (parts.size) {
|
|
237
|
+
0 -> ""
|
|
238
|
+
1 -> parts[0].substring(0, 1).uppercase()
|
|
239
|
+
else -> (parts[0][0].toString() + parts[1][0].toString()).uppercase()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun loadAvatar(iv: ImageView, url: String) {
|
|
201
244
|
Thread {
|
|
202
245
|
try {
|
|
203
246
|
val conn = URL(url).openConnection() as HttpURLConnection
|
|
204
247
|
conn.doInput = true; conn.connect()
|
|
205
248
|
val bmp = BitmapFactory.decodeStream(conn.inputStream)
|
|
206
249
|
runOnUiThread { iv.setImageBitmap(bmp) }
|
|
207
|
-
} catch (_: Exception) {
|
|
250
|
+
} catch (_: Exception) { }
|
|
208
251
|
}.start()
|
|
209
252
|
}
|
|
210
253
|
|
|
211
|
-
// Same loader for background
|
|
212
254
|
private fun loadAndBlurBackground(iv: ImageView, url: String?) {
|
|
213
|
-
if (url.isNullOrEmpty()) return
|
|
214
255
|
Thread {
|
|
215
|
-
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
256
|
+
val bmp: Bitmap? = try {
|
|
257
|
+
if (!url.isNullOrEmpty()) {
|
|
258
|
+
val c = URL(url).openConnection() as HttpURLConnection
|
|
259
|
+
c.doInput = true; c.connect()
|
|
260
|
+
BitmapFactory.decodeStream(c.inputStream)
|
|
261
|
+
} else {
|
|
262
|
+
BitmapFactory.decodeResource(
|
|
263
|
+
resources, R.drawable.default_call_bg
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
} catch (_: Exception) { null }
|
|
267
|
+
bmp ?: return@Thread
|
|
268
|
+
|
|
269
|
+
val finalBmp = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
270
|
+
// heavier blur via 16× down/up-scale
|
|
271
|
+
val factor = 16
|
|
272
|
+
val w = bmp.width / factor
|
|
273
|
+
val h = bmp.height / factor
|
|
274
|
+
val small = Bitmap.createScaledBitmap(bmp, w, h, true)
|
|
275
|
+
Bitmap.createScaledBitmap(small, bmp.width, bmp.height, true)
|
|
276
|
+
} else {
|
|
277
|
+
bmp
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
runOnUiThread { iv.setImageBitmap(finalBmp) }
|
|
221
281
|
}.start()
|
|
222
282
|
}
|
|
223
283
|
|
|
@@ -225,9 +285,11 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
225
285
|
val size = dp(70)
|
|
226
286
|
return FrameLayout(this).apply {
|
|
227
287
|
layoutParams = LinearLayout.LayoutParams(size, size)
|
|
228
|
-
isClickable = true
|
|
229
|
-
|
|
230
|
-
|
|
288
|
+
isClickable = true
|
|
289
|
+
isFocusable = true
|
|
290
|
+
foreground = makeCircleRipple()
|
|
291
|
+
|
|
292
|
+
// circular background
|
|
231
293
|
addView(View(context).apply {
|
|
232
294
|
layoutParams = FrameLayout.LayoutParams(
|
|
233
295
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -238,6 +300,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
238
300
|
setColor(bgColor)
|
|
239
301
|
}
|
|
240
302
|
})
|
|
303
|
+
|
|
241
304
|
// icon
|
|
242
305
|
addView(ImageView(context).apply {
|
|
243
306
|
layoutParams = FrameLayout.LayoutParams(dp(36), dp(36))
|
|
@@ -248,12 +311,15 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
248
311
|
}
|
|
249
312
|
}
|
|
250
313
|
|
|
251
|
-
private fun
|
|
252
|
-
val
|
|
253
|
-
|
|
254
|
-
|
|
314
|
+
private fun makeCircleRipple(): RippleDrawable {
|
|
315
|
+
val mask = GradientDrawable().apply {
|
|
316
|
+
shape = GradientDrawable.OVAL
|
|
317
|
+
setColor(Color.WHITE)
|
|
318
|
+
}
|
|
319
|
+
val color = android.content.res.ColorStateList.valueOf(
|
|
320
|
+
Color.parseColor("#33FFFFFF")
|
|
255
321
|
)
|
|
256
|
-
return
|
|
322
|
+
return RippleDrawable(color, null, mask)
|
|
257
323
|
}
|
|
258
324
|
|
|
259
325
|
private fun onAnswer() {
|
|
@@ -272,14 +338,10 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
272
338
|
finishCallActivity()
|
|
273
339
|
}
|
|
274
340
|
|
|
275
|
-
private fun dp(v: Int): Int = TypedValue.applyDimension(
|
|
276
|
-
TypedValue.COMPLEX_UNIT_DIP, v.toFloat(),
|
|
277
|
-
resources.displayMetrics
|
|
278
|
-
).toInt()
|
|
279
|
-
|
|
280
341
|
private fun setupLockScreenBypass(isSamsung: Boolean) {
|
|
281
342
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
282
|
-
setShowWhenLocked(true)
|
|
343
|
+
setShowWhenLocked(true)
|
|
344
|
+
setTurnScreenOn(true)
|
|
283
345
|
} else {
|
|
284
346
|
window.addFlags(
|
|
285
347
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
@@ -294,19 +356,16 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
294
356
|
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
|
|
295
357
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
|
296
358
|
)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
300
|
-
km.requestDismissKeyguard(this,
|
|
359
|
+
(getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager)
|
|
360
|
+
.requestDismissKeyguard(this,
|
|
301
361
|
object : KeyguardManager.KeyguardDismissCallback() {
|
|
302
362
|
override fun onDismissSucceeded() {
|
|
303
363
|
Log.d(TAG, "Samsung keyguard dismissed")
|
|
304
364
|
}
|
|
305
365
|
override fun onDismissError() {
|
|
306
|
-
Log.w(TAG, "
|
|
366
|
+
Log.w(TAG, "Keyguard dismiss error")
|
|
307
367
|
}
|
|
308
368
|
})
|
|
309
|
-
}
|
|
310
369
|
}
|
|
311
370
|
}
|
|
312
371
|
|
|
@@ -328,7 +387,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
328
387
|
override fun onDestroy() {
|
|
329
388
|
super.onDestroy()
|
|
330
389
|
CallEngine.unregisterCallEndListener(this)
|
|
331
|
-
|
|
390
|
+
uiHandler.removeCallbacks(timeoutRunnable)
|
|
332
391
|
if (finishReason != FinishReason.ANSWER) {
|
|
333
392
|
CallEngine.stopRingtone()
|
|
334
393
|
CallEngine.cancelIncomingCallUI()
|
|
@@ -344,6 +403,12 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
344
403
|
}
|
|
345
404
|
}
|
|
346
405
|
|
|
406
|
+
private fun dp(v: Int): Int = TypedValue.applyDimension(
|
|
407
|
+
TypedValue.COMPLEX_UNIT_DIP,
|
|
408
|
+
v.toFloat(),
|
|
409
|
+
resources.displayMetrics
|
|
410
|
+
).toInt()
|
|
411
|
+
|
|
347
412
|
companion object {
|
|
348
413
|
private const val TAG = "CallActivity"
|
|
349
414
|
}
|
|
Binary file
|