@qusaieilouti99/call-manager 0.1.99 → 0.1.101
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,3 +1,4 @@
|
|
|
1
|
+
// File: CallActivity.kt
|
|
1
2
|
package com.margelo.nitro.qusaieilouti99.callmanager
|
|
2
3
|
|
|
3
4
|
import android.app.Activity
|
|
@@ -49,31 +50,23 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
49
50
|
|
|
50
51
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
51
52
|
super.onCreate(savedInstanceState)
|
|
52
|
-
// Samsung lock‐screen bypass flag
|
|
53
53
|
val isSamsungBypass = intent.getBooleanExtra(
|
|
54
54
|
"SAMSUNG_LOCK_SCREEN_BYPASS", false
|
|
55
55
|
)
|
|
56
56
|
setupLockScreenBypass(isSamsungBypass)
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
callType = intent.getStringExtra("callType") ?: "Audio"
|
|
58
|
+
callId = intent.getStringExtra("callId") ?: ""
|
|
59
|
+
callType = intent.getStringExtra("callType") ?: "Audio"
|
|
61
60
|
val callerName = intent.getStringExtra("callerName") ?: "Unknown"
|
|
62
61
|
val avatarUrl = intent.getStringExtra("callerAvatar")
|
|
63
62
|
|
|
64
|
-
// Register for external hang‐up
|
|
65
63
|
CallEngine.registerCallEndListener(this)
|
|
66
|
-
|
|
67
|
-
// Build all‐code UI
|
|
68
64
|
buildUi(callerName, avatarUrl)
|
|
69
|
-
|
|
70
|
-
// Auto‐timeout
|
|
71
65
|
uiHandler.postDelayed(timeoutRunnable, timeoutMs)
|
|
72
66
|
Log.d(TAG, "CallActivity setup complete for callId=$callId")
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
private fun buildUi(name: String, avatarUrl: String?) {
|
|
76
|
-
// Root container
|
|
77
70
|
val root = FrameLayout(this).apply {
|
|
78
71
|
layoutParams = ViewGroup.LayoutParams(
|
|
79
72
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -81,20 +74,17 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
81
74
|
)
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
//
|
|
85
|
-
// 1) Background ImageView + blur
|
|
86
|
-
//
|
|
77
|
+
// 1) Full-screen background + blur
|
|
87
78
|
val bg = ImageView(this).apply {
|
|
88
79
|
layoutParams = FrameLayout.LayoutParams(
|
|
89
80
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
90
81
|
ViewGroup.LayoutParams.MATCH_PARENT
|
|
91
82
|
)
|
|
92
83
|
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
93
|
-
// On API 31+ apply live RenderEffect blur
|
|
94
84
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
95
85
|
setRenderEffect(
|
|
96
86
|
RenderEffect.createBlurEffect(
|
|
97
|
-
|
|
87
|
+
50f, 50f, Shader.TileMode.CLAMP
|
|
98
88
|
)
|
|
99
89
|
)
|
|
100
90
|
}
|
|
@@ -102,9 +92,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
102
92
|
root.addView(bg)
|
|
103
93
|
loadAndBlurBackground(bg, avatarUrl)
|
|
104
94
|
|
|
105
|
-
//
|
|
106
|
-
// 2) Semi-transparent scrim
|
|
107
|
-
//
|
|
95
|
+
// 2) Dark scrim
|
|
108
96
|
root.addView(View(this).apply {
|
|
109
97
|
layoutParams = FrameLayout.LayoutParams(
|
|
110
98
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -113,9 +101,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
113
101
|
setBackgroundColor(Color.parseColor("#80000000"))
|
|
114
102
|
})
|
|
115
103
|
|
|
116
|
-
//
|
|
117
|
-
// 3) Top “Incoming … call” label
|
|
118
|
-
//
|
|
104
|
+
// 3) “Incoming ... call” with extra top padding (48dp)
|
|
119
105
|
val typeLabel = TextView(this).apply {
|
|
120
106
|
text = if (callType.equals("video", true))
|
|
121
107
|
"Incoming video call"
|
|
@@ -129,14 +115,12 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
129
115
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
130
116
|
).apply {
|
|
131
117
|
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
132
|
-
topMargin = dp(
|
|
118
|
+
topMargin = dp(48)
|
|
133
119
|
}
|
|
134
120
|
}
|
|
135
121
|
root.addView(typeLabel)
|
|
136
122
|
|
|
137
|
-
//
|
|
138
|
-
// 4) Avatar + Name (in top half)
|
|
139
|
-
//
|
|
123
|
+
// 4) Avatar + name in top half
|
|
140
124
|
val avatarSection = LinearLayout(this).apply {
|
|
141
125
|
orientation = LinearLayout.VERTICAL
|
|
142
126
|
gravity = Gravity.CENTER_HORIZONTAL
|
|
@@ -162,9 +146,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
162
146
|
})
|
|
163
147
|
root.addView(avatarSection)
|
|
164
148
|
|
|
165
|
-
//
|
|
166
|
-
// 5) Bottom Answer/Decline Buttons
|
|
167
|
-
//
|
|
149
|
+
// 5) Bottom buttons with extra bottom padding (64dp)
|
|
168
150
|
val actions = LinearLayout(this).apply {
|
|
169
151
|
orientation = LinearLayout.HORIZONTAL
|
|
170
152
|
gravity = Gravity.CENTER
|
|
@@ -173,7 +155,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
173
155
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
174
156
|
).apply {
|
|
175
157
|
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
|
176
|
-
bottomMargin = dp(
|
|
158
|
+
bottomMargin = dp(64)
|
|
177
159
|
}
|
|
178
160
|
}
|
|
179
161
|
actions.addView(
|
|
@@ -183,7 +165,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
183
165
|
).apply { setOnClickListener { onDecline() } }
|
|
184
166
|
)
|
|
185
167
|
actions.addView(View(this).apply {
|
|
186
|
-
layoutParams = LinearLayout.LayoutParams(dp(
|
|
168
|
+
layoutParams = LinearLayout.LayoutParams(dp(100), 0)
|
|
187
169
|
})
|
|
188
170
|
actions.addView(
|
|
189
171
|
createCircleButton(
|
|
@@ -196,22 +178,24 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
196
178
|
setContentView(root)
|
|
197
179
|
}
|
|
198
180
|
|
|
199
|
-
|
|
200
|
-
private fun createAvatarView(
|
|
201
|
-
name: String,
|
|
202
|
-
url: String?
|
|
203
|
-
): FrameLayout {
|
|
181
|
+
private fun createAvatarView(name: String, url: String?): FrameLayout {
|
|
204
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
|
+
|
|
205
192
|
val container = FrameLayout(this).apply {
|
|
206
193
|
layoutParams = LinearLayout.LayoutParams(size, size)
|
|
207
|
-
background =
|
|
208
|
-
shape = GradientDrawable.OVAL
|
|
209
|
-
setColor(Color.parseColor("#DDFFFFFF"))
|
|
210
|
-
}
|
|
194
|
+
background = gradientBg
|
|
211
195
|
clipToOutline = true
|
|
212
196
|
}
|
|
213
197
|
|
|
214
|
-
// ImageView
|
|
198
|
+
// ImageView (only visible when URL != null)
|
|
215
199
|
val iv = ImageView(this).apply {
|
|
216
200
|
layoutParams = FrameLayout.LayoutParams(
|
|
217
201
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -221,7 +205,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
221
205
|
}
|
|
222
206
|
container.addView(iv)
|
|
223
207
|
|
|
224
|
-
// Initials
|
|
208
|
+
// Initials TextView
|
|
225
209
|
val initials = TextView(this).apply {
|
|
226
210
|
layoutParams = FrameLayout.LayoutParams(
|
|
227
211
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -236,14 +220,14 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
236
220
|
container.addView(initials)
|
|
237
221
|
|
|
238
222
|
if (url.isNullOrEmpty()) {
|
|
239
|
-
|
|
240
|
-
iv.setImageResource(R.drawable.ic_default_avatar)
|
|
223
|
+
iv.visibility = View.GONE
|
|
241
224
|
initials.visibility = View.VISIBLE
|
|
242
225
|
} else {
|
|
243
|
-
|
|
226
|
+
iv.visibility = View.VISIBLE
|
|
244
227
|
initials.visibility = View.GONE
|
|
245
228
|
loadAvatar(iv, url)
|
|
246
229
|
}
|
|
230
|
+
|
|
247
231
|
return container
|
|
248
232
|
}
|
|
249
233
|
|
|
@@ -251,13 +235,11 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
251
235
|
val parts = fullName.trim().split("\\s+".toRegex())
|
|
252
236
|
return when (parts.size) {
|
|
253
237
|
0 -> ""
|
|
254
|
-
1 -> parts[0].substring(0,1).uppercase()
|
|
255
|
-
else -> (parts[0][0].toString() + parts[1][0].toString())
|
|
256
|
-
.uppercase()
|
|
238
|
+
1 -> parts[0].substring(0, 1).uppercase()
|
|
239
|
+
else -> (parts[0][0].toString() + parts[1][0].toString()).uppercase()
|
|
257
240
|
}
|
|
258
241
|
}
|
|
259
242
|
|
|
260
|
-
/** Downloads an image + sets it on the ImageView. */
|
|
261
243
|
private fun loadAvatar(iv: ImageView, url: String) {
|
|
262
244
|
Thread {
|
|
263
245
|
try {
|
|
@@ -265,11 +247,10 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
265
247
|
conn.doInput = true; conn.connect()
|
|
266
248
|
val bmp = BitmapFactory.decodeStream(conn.inputStream)
|
|
267
249
|
runOnUiThread { iv.setImageBitmap(bmp) }
|
|
268
|
-
} catch (_: Exception) {
|
|
250
|
+
} catch (_: Exception) { }
|
|
269
251
|
}.start()
|
|
270
252
|
}
|
|
271
253
|
|
|
272
|
-
/** Downloads bg image (remote or fallback), applies blur if needed. */
|
|
273
254
|
private fun loadAndBlurBackground(iv: ImageView, url: String?) {
|
|
274
255
|
Thread {
|
|
275
256
|
val bmp: Bitmap? = try {
|
|
@@ -278,19 +259,16 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
278
259
|
c.doInput = true; c.connect()
|
|
279
260
|
BitmapFactory.decodeStream(c.inputStream)
|
|
280
261
|
} else {
|
|
281
|
-
// your fallback background
|
|
282
262
|
BitmapFactory.decodeResource(
|
|
283
263
|
resources, R.drawable.default_call_bg
|
|
284
264
|
)
|
|
285
265
|
}
|
|
286
|
-
} catch (_: Exception) {
|
|
287
|
-
null
|
|
288
|
-
}
|
|
266
|
+
} catch (_: Exception) { null }
|
|
289
267
|
bmp ?: return@Thread
|
|
290
268
|
|
|
291
269
|
val finalBmp = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
292
|
-
//
|
|
293
|
-
val factor =
|
|
270
|
+
// heavier blur via 16× down/up-scale
|
|
271
|
+
val factor = 16
|
|
294
272
|
val w = bmp.width / factor
|
|
295
273
|
val h = bmp.height / factor
|
|
296
274
|
val small = Bitmap.createScaledBitmap(bmp, w, h, true)
|
|
@@ -303,18 +281,15 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
303
281
|
}.start()
|
|
304
282
|
}
|
|
305
283
|
|
|
306
|
-
|
|
307
|
-
private fun createCircleButton(
|
|
308
|
-
iconRes: Int,
|
|
309
|
-
bgColor: Int
|
|
310
|
-
): FrameLayout {
|
|
284
|
+
private fun createCircleButton(iconRes: Int, bgColor: Int): FrameLayout {
|
|
311
285
|
val size = dp(70)
|
|
312
286
|
return FrameLayout(this).apply {
|
|
313
287
|
layoutParams = LinearLayout.LayoutParams(size, size)
|
|
314
288
|
isClickable = true
|
|
315
289
|
isFocusable = true
|
|
316
290
|
foreground = makeCircleRipple()
|
|
317
|
-
|
|
291
|
+
|
|
292
|
+
// circular background
|
|
318
293
|
addView(View(context).apply {
|
|
319
294
|
layoutParams = FrameLayout.LayoutParams(
|
|
320
295
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -325,6 +300,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
325
300
|
setColor(bgColor)
|
|
326
301
|
}
|
|
327
302
|
})
|
|
303
|
+
|
|
328
304
|
// icon
|
|
329
305
|
addView(ImageView(context).apply {
|
|
330
306
|
layoutParams = FrameLayout.LayoutParams(dp(36), dp(36))
|
|
@@ -335,18 +311,15 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
335
311
|
}
|
|
336
312
|
}
|
|
337
313
|
|
|
338
|
-
/** Circular RippleDrawable. */
|
|
339
314
|
private fun makeCircleRipple(): RippleDrawable {
|
|
340
315
|
val mask = GradientDrawable().apply {
|
|
341
316
|
shape = GradientDrawable.OVAL
|
|
342
317
|
setColor(Color.WHITE)
|
|
343
318
|
}
|
|
344
|
-
val
|
|
345
|
-
|
|
346
|
-
android.content.res.ColorStateList.valueOf(clr),
|
|
347
|
-
null,
|
|
348
|
-
mask
|
|
319
|
+
val color = android.content.res.ColorStateList.valueOf(
|
|
320
|
+
Color.parseColor("#33FFFFFF")
|
|
349
321
|
)
|
|
322
|
+
return RippleDrawable(color, null, mask)
|
|
350
323
|
}
|
|
351
324
|
|
|
352
325
|
private fun onAnswer() {
|
|
@@ -356,6 +329,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
356
329
|
CallEngine.answerCall(callId)
|
|
357
330
|
finishCallActivity()
|
|
358
331
|
}
|
|
332
|
+
|
|
359
333
|
private fun onDecline() {
|
|
360
334
|
finishReason = FinishReason.DECLINE
|
|
361
335
|
CallEngine.stopRingtone()
|
|
@@ -391,8 +365,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
391
365
|
override fun onDismissError() {
|
|
392
366
|
Log.w(TAG, "Keyguard dismiss error")
|
|
393
367
|
}
|
|
394
|
-
}
|
|
395
|
-
)
|
|
368
|
+
})
|
|
396
369
|
}
|
|
397
370
|
}
|
|
398
371
|
|
|
@@ -431,7 +404,8 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
|
|
|
431
404
|
}
|
|
432
405
|
|
|
433
406
|
private fun dp(v: Int): Int = TypedValue.applyDimension(
|
|
434
|
-
TypedValue.COMPLEX_UNIT_DIP,
|
|
407
|
+
TypedValue.COMPLEX_UNIT_DIP,
|
|
408
|
+
v.toFloat(),
|
|
435
409
|
resources.displayMetrics
|
|
436
410
|
).toInt()
|
|
437
411
|
|
|
@@ -38,6 +38,11 @@ import java.util.concurrent.ConcurrentHashMap
|
|
|
38
38
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
39
39
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
40
40
|
import android.app.KeyguardManager
|
|
41
|
+
import android.content.ContentResolver
|
|
42
|
+
import android.database.ContentObserver
|
|
43
|
+
import android.os.Vibrator
|
|
44
|
+
import android.os.VibrationEffect
|
|
45
|
+
import android.provider.Settings
|
|
41
46
|
/**
|
|
42
47
|
* Core call‐management engine. Manages self-managed telecom calls,
|
|
43
48
|
* audio routing, UI notifications, etc.
|
|
@@ -102,6 +107,10 @@ object CallEngine {
|
|
|
102
107
|
private var eventHandler: ((CallEventType, String) -> Unit)? = null
|
|
103
108
|
private val cachedEvents = mutableListOf<Pair<CallEventType, String>>()
|
|
104
109
|
|
|
110
|
+
private var ringtone: android.media.Ringtone? = null
|
|
111
|
+
private var vibrator: Vibrator? = null
|
|
112
|
+
private var volumeObserver: ContentObserver? = null
|
|
113
|
+
|
|
105
114
|
interface LockScreenBypassCallback {
|
|
106
115
|
fun onLockScreenBypassChanged(shouldBypass: Boolean)
|
|
107
116
|
}
|
|
@@ -255,7 +264,7 @@ object CallEngine {
|
|
|
255
264
|
currentCallId = callId
|
|
256
265
|
Log.d(TAG, "Call $callId added to activeCalls. State: INCOMING")
|
|
257
266
|
|
|
258
|
-
showIncomingCallUI(callId, displayName, callType)
|
|
267
|
+
showIncomingCallUI(callId, displayName, callType, pictureUrl)
|
|
259
268
|
registerPhoneAccount()
|
|
260
269
|
|
|
261
270
|
val telecomManager =
|
|
@@ -916,17 +925,17 @@ object CallEngine {
|
|
|
916
925
|
}
|
|
917
926
|
}
|
|
918
927
|
|
|
919
|
-
private fun showIncomingCallUI(callId: String, callerName: String, callType: String) {
|
|
928
|
+
private fun showIncomingCallUI(callId: String, callerName: String, callType: String, callerPicUrl: String?) {
|
|
920
929
|
val context = requireContext()
|
|
921
930
|
Log.d(TAG, "Showing incoming call UI for $callId")
|
|
922
931
|
|
|
923
932
|
// **SIMPLE LOGIC**: If locked -> overlay, if unlocked -> notification
|
|
924
933
|
if (isDeviceLocked(context)) {
|
|
925
934
|
Log.d(TAG, "Device is locked - using overlay approach")
|
|
926
|
-
showCallActivityOverlay(context, callId, callerName, callType)
|
|
935
|
+
showCallActivityOverlay(context, callId, callerName, callType, callerPicUrl)
|
|
927
936
|
} else {
|
|
928
937
|
Log.d(TAG, "Device is unlocked - using standard notification")
|
|
929
|
-
showStandardNotification(context, callId, callerName, callType)
|
|
938
|
+
showStandardNotification(context, callId, callerName, callType, callerPicUrl)
|
|
930
939
|
}
|
|
931
940
|
|
|
932
941
|
playRingtone()
|
|
@@ -946,7 +955,7 @@ object CallEngine {
|
|
|
946
955
|
/**
|
|
947
956
|
* Shows the call activity directly as overlay (when device is locked)
|
|
948
957
|
*/
|
|
949
|
-
private fun showCallActivityOverlay(context: Context, callId: String, callerName: String, callType: String) {
|
|
958
|
+
private fun showCallActivityOverlay(context: Context, callId: String, callerName: String, callType: String, callerPicUrl: String?) {
|
|
950
959
|
val overlayIntent = Intent(context, CallActivity::class.java).apply {
|
|
951
960
|
addFlags(
|
|
952
961
|
Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
@@ -957,6 +966,10 @@ object CallEngine {
|
|
|
957
966
|
putExtra("callId", callId)
|
|
958
967
|
putExtra("callerName", callerName)
|
|
959
968
|
putExtra("callType", callType)
|
|
969
|
+
if (callerPicUrl != null) {
|
|
970
|
+
putExtra("callAvatar", callerPicUrl)
|
|
971
|
+
}
|
|
972
|
+
|
|
960
973
|
putExtra("LOCK_SCREEN_MODE", true) // Flag to identify lock screen mode
|
|
961
974
|
}
|
|
962
975
|
|
|
@@ -978,14 +991,14 @@ object CallEngine {
|
|
|
978
991
|
} catch (e: Exception) {
|
|
979
992
|
Log.e(TAG, "Overlay failed, falling back to notification: ${e.message}")
|
|
980
993
|
// If overlay fails, fall back to notification
|
|
981
|
-
showStandardNotification(context, callId, callerName, callType)
|
|
994
|
+
showStandardNotification(context, callId, callerName, callType, callerPicUrl)
|
|
982
995
|
}
|
|
983
996
|
}
|
|
984
997
|
|
|
985
998
|
/**
|
|
986
999
|
* Shows standard notification with full-screen intent (when device is unlocked)
|
|
987
1000
|
*/
|
|
988
|
-
private fun showStandardNotification(context: Context, callId: String, callerName: String, callType: String) {
|
|
1001
|
+
private fun showStandardNotification(context: Context, callId: String, callerName: String, callType: String, callerPicUrl: String?) {
|
|
989
1002
|
createNotificationChannel()
|
|
990
1003
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
991
1004
|
|
|
@@ -994,6 +1007,9 @@ object CallEngine {
|
|
|
994
1007
|
putExtra("callId", callId)
|
|
995
1008
|
putExtra("callerName", callerName)
|
|
996
1009
|
putExtra("callType", callType)
|
|
1010
|
+
if (callerPicUrl != null) {
|
|
1011
|
+
putExtra("callAvatar", callerPicUrl)
|
|
1012
|
+
}
|
|
997
1013
|
// No lock screen flag - standard notification mode
|
|
998
1014
|
}
|
|
999
1015
|
|
|
@@ -1195,13 +1211,46 @@ object CallEngine {
|
|
|
1195
1211
|
// --- Media Management ---
|
|
1196
1212
|
private fun playRingtone() {
|
|
1197
1213
|
val context = requireContext()
|
|
1214
|
+
|
|
1215
|
+
// 1) start a repeating vibration
|
|
1216
|
+
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
|
1217
|
+
vibrator?.let { v ->
|
|
1218
|
+
val pattern = longArrayOf(0L, 500L, 500L) // wait, vibrate, pause
|
|
1219
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1220
|
+
v.vibrate(VibrationEffect.createWaveform(pattern, 0 /* repeat idx */))
|
|
1221
|
+
} else {
|
|
1222
|
+
@Suppress("DEPRECATION")
|
|
1223
|
+
v.vibrate(pattern, 0)
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// 2) play the ringtone
|
|
1198
1228
|
try {
|
|
1199
1229
|
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
1200
1230
|
ringtone = RingtoneManager.getRingtone(context, uri)
|
|
1201
1231
|
ringtone?.play()
|
|
1202
|
-
Log.d(TAG, "Ringtone started playing")
|
|
1203
1232
|
} catch (e: Exception) {
|
|
1204
|
-
Log.e(TAG, "Failed to play ringtone
|
|
1233
|
+
Log.e(TAG, "Failed to play ringtone", e)
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// 3) register a ContentObserver on the ring-volume setting
|
|
1237
|
+
if (volumeObserver == null) {
|
|
1238
|
+
val resolver: ContentResolver = context.contentResolver
|
|
1239
|
+
volumeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
|
|
1240
|
+
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
|
1241
|
+
super.onChange(selfChange, uri)
|
|
1242
|
+
// only react to ring-volume changes
|
|
1243
|
+
if (uri == Settings.System.getUriFor(Settings.System.VOLUME_RING)) {
|
|
1244
|
+
Log.d(TAG, "Ring volume changed, silencing ringtone")
|
|
1245
|
+
stopRingtone()
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
resolver.registerContentObserver(
|
|
1250
|
+
Settings.System.getUriFor(Settings.System.VOLUME_RING),
|
|
1251
|
+
false,
|
|
1252
|
+
volumeObserver!!
|
|
1253
|
+
)
|
|
1205
1254
|
}
|
|
1206
1255
|
}
|
|
1207
1256
|
|
|
@@ -1210,9 +1259,19 @@ object CallEngine {
|
|
|
1210
1259
|
ringtone?.stop()
|
|
1211
1260
|
Log.d(TAG, "Ringtone stopped")
|
|
1212
1261
|
} catch (e: Exception) {
|
|
1213
|
-
Log.e(TAG, "Error stopping ringtone
|
|
1262
|
+
Log.e(TAG, "Error stopping ringtone", e)
|
|
1214
1263
|
}
|
|
1215
1264
|
ringtone = null
|
|
1265
|
+
|
|
1266
|
+
// cancel vibration
|
|
1267
|
+
vibrator?.cancel()
|
|
1268
|
+
vibrator = null
|
|
1269
|
+
|
|
1270
|
+
// unregister observer
|
|
1271
|
+
volumeObserver?.let {
|
|
1272
|
+
requireContext().contentResolver.unregisterContentObserver(it)
|
|
1273
|
+
volumeObserver = null
|
|
1274
|
+
}
|
|
1216
1275
|
}
|
|
1217
1276
|
|
|
1218
1277
|
private fun startRingback() {
|
package/package.json
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
-
android:width="24dp"
|
|
3
|
-
android:height="24dp"
|
|
4
|
-
android:viewportWidth="24"
|
|
5
|
-
android:viewportHeight="24">
|
|
6
|
-
<path
|
|
7
|
-
android:fillColor="#BDBDBD"
|
|
8
|
-
android:pathData="
|
|
9
|
-
M12,12c2.21,0 4,-1.79 4,-4
|
|
10
|
-
s-1.79,-4 -4,-4
|
|
11
|
-
s-4,1.79 -4,4
|
|
12
|
-
s1.79,4 4,4z
|
|
13
|
-
M6,20
|
|
14
|
-
c0,-3.31 2.69,-6 6,-6
|
|
15
|
-
s6,2.69 6,6
|
|
16
|
-
v1
|
|
17
|
-
H6v-1z"/>
|
|
18
|
-
</vector>
|