Android ConnectionService incoming calls - android

I'm trying to implement iOS callkit behavior on Android. I'm receiving a push notification from firebase and I want to show "incoming call" screen to the user. To do it I use ConnectionService from android.telecom package and other classes.
Here is my call manager class:
class CallManager(context: Context) {
val telecomManager: TelecomManager
var phoneAccountHandle:PhoneAccountHandle
var context:Context
val number = "3924823202"
init {
telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
this.context = context
val componentName = ComponentName(this.context, CallConnectionService::class.java)
phoneAccountHandle = PhoneAccountHandle(componentName, "Admin")
val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Admin").setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED).build()
telecomManager.registerPhoneAccount(phoneAccount)
val intent = Intent()
intent.component = ComponentName("com.android.server.telecom", "com.android.server.telecom.settings.EnableAccountPreferenceActivity")
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
}
#TargetApi(Build.VERSION_CODES.M)
fun startOutgoingCall() {
val extras = Bundle()
extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true)
val manager = context.getSystemService(TELECOM_SERVICE) as TelecomManager
val phoneAccountHandle = PhoneAccountHandle(ComponentName(context.packageName, CallConnectionService::class.java!!.getName()), "estosConnectionServiceId")
val test = Bundle()
test.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
test.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL)
test.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras)
try {
manager.placeCall(Uri.parse("tel:$number"), test)
} catch (e:SecurityException){
e.printStackTrace()
}
}
#TargetApi(Build.VERSION_CODES.M)
fun startIncomingCall(){
if (this.context.checkSelfPermission(Manifest.permission.MANAGE_OWN_CALLS) == PackageManager.PERMISSION_GRANTED) {
val extras = Bundle()
val uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null)
extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri)
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true)
val isCallPermitted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
telecomManager.isIncomingCallPermitted(phoneAccountHandle)
} else {
true
}
Log.i("CallManager", "is incoming call permited = $isCallPermitted")
telecomManager.addNewIncomingCall(phoneAccountHandle, extras)
}
}
}
And my custom ConnectionService implementation:
class CallConnectionService : ConnectionService() {
override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
Log.i("CallConnectionService", "onCreateOutgoingConnection")
val conn = CallConnection(applicationContext)
conn.setAddress(request!!.address, PRESENTATION_ALLOWED)
conn.setInitializing()
conn.videoProvider = MyVideoProvider()
conn.setActive()
return conn
}
override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?) {
super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
Log.i("CallConnectionService", "create outgoing call failed")
}
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
Log.i("CallConnectionService", "onCreateIncomingConnection")
val conn = CallConnection(applicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
conn.connectionProperties = Connection.PROPERTY_SELF_MANAGED
}
conn.setCallerDisplayName("test call", TelecomManager.PRESENTATION_ALLOWED)
conn.setAddress(request!!.address, PRESENTATION_ALLOWED)
conn.setInitializing()
conn.videoProvider = MyVideoProvider()
conn.setActive()
return conn
}
override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?) {
super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
Log.i("CallConnectionService", "create outgoing call failed ")
}
}
And my Connection implementation is like that:
class CallConnection(ctx:Context) : Connection() {
var ctx:Context = ctx
val TAG = "CallConnection"
override fun onShowIncomingCallUi() {
// super.onShowIncomingCallUi()
Log.i(TAG, "onShowIncomingCallUi")
val intent = Intent(Intent.ACTION_MAIN, null)
intent.flags = Intent.FLAG_ACTIVITY_NO_USER_ACTION or Intent.FLAG_ACTIVITY_NEW_TASK
intent.setClass(ctx, IncomingCallActivity::class.java!!)
val pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0)
val builder = Notification.Builder(ctx)
builder.setOngoing(true)
builder.setPriority(Notification.PRIORITY_HIGH)
// Set notification content intent to take user to fullscreen UI if user taps on the
// notification body.
builder.setContentIntent(pendingIntent)
// Set full screen intent to trigger display of the fullscreen UI when the notification
// manager deems it appropriate.
builder.setFullScreenIntent(pendingIntent, true)
// Setup notification content.
builder.setSmallIcon(R.mipmap.ic_launcher)
builder.setContentTitle("Your notification title")
builder.setContentText("Your notification content.")
// Use builder.addAction(..) to add buttons to answer or reject the call.
val notificationManager = ctx.getSystemService(
NotificationManager::class.java)
notificationManager.notify("Call Notification", 37, builder.build())
}
override fun onCallAudioStateChanged(state: CallAudioState?) {
Log.i(TAG, "onCallAudioStateChanged")
}
override fun onAnswer() {
Log.i(TAG, "onAnswer")
}
override fun onDisconnect() {
Log.i(TAG, "onDisconnect")
}
override fun onHold() {
Log.i(TAG, "onHold")
}
override fun onUnhold() {
Log.i(TAG, "onUnhold")
}
override fun onReject() {
Log.i(TAG, "onReject")
}
}
According to the document to show user incoming calcustomon UI - I should do some actions in onShowIncomingCallUi() method. But it just does not called by the system.
How can I fix it?

I was able to get it to work using a test app and Android Pie running on a Pixel 2 XL.
From my testing the important parts are to ensure:
That Connection.PROPERTY_SELF_MANAGED is set on the connection. Requires a minimum of API 26.
You have to register your phone account.
You have to set PhoneAccount.CAPABILITY_SELF_MANAGED in your capabilities when registering the phone account. That is the only capability that I set. Setting other capabilities caused it to throw an exception.
Finally, you need to ensure that you have this permission set in AndroidManifest.xml. android.permission.MANAGE_OWN_CALLS
So, I would check your manifest to ensure you have the permissions and also ensure the capabilities are set correctly. It looks like everything else was set correctly in your code above.
Hope that helps!

Related

lateinit property healthServicesManager has not been initialized

I'm trying to make an application for wearable in which I get various metrics from health services such as the heart rate for different sports. However I get the following error:: lateinit property healthServicesManager has not been initialized.
I have declared the lateinit as:
#Inject
lateinit var healthServicesManager: HealthServicesManagerTaekwondo
The complete code of the class:
#AndroidEntryPoint
class ExerciseServiceTaekwondo : LifecycleService() {
#Inject
lateinit var healthServicesManager: HealthServicesManagerTaekwondo
private val localBinder = LocalBinder()
private var isBound = false
private var isStarted = false
private var isForeground = false
private val _exerciseState = MutableStateFlow(ExerciseState.USER_ENDED)
val exerciseState: StateFlow<ExerciseState> = _exerciseState
private val _exerciseMetrics = MutableStateFlow(emptyMap<DataType, List<DataPoint>>())
val exerciseMetrics: StateFlow<Map<DataType, List<DataPoint>>> = _exerciseMetrics
private val _aggregateMetrics = MutableStateFlow(emptyMap<DataType, AggregateDataPoint>())
val aggregateMetrics: StateFlow<Map<DataType, AggregateDataPoint>> = _aggregateMetrics
private val _exerciseLaps = MutableStateFlow(0)
val exerciseLaps: StateFlow<Int> = _exerciseLaps
private val _exerciseDurationUpdate = MutableStateFlow(ActiveDurationUpdate())
val exerciseDurationUpdate: StateFlow<ActiveDurationUpdate> = _exerciseDurationUpdate
private val _locationAvailabilityState = MutableStateFlow(LocationAvailability.UNKNOWN)
val locationAvailabilityState: StateFlow<LocationAvailability> = _locationAvailabilityState
fun prepareExercise() {
lifecycleScope.launch {
healthServicesManager.prepareExercise()
}
}
fun startExercise() {
lifecycleScope.launch {
healthServicesManager.startExercise()
}
postOngoingActivityNotification()
}
fun pauseExercise() {
lifecycleScope.launch {
healthServicesManager.pauseExercise()
}
}
fun resumeExercise() {
lifecycleScope.launch {
healthServicesManager.resumeExercise()
}
}
/**
* End exercise in this service's coroutine context.
*/
fun endExercise() {
lifecycleScope.launch {
healthServicesManager.endExercise()
}
removeOngoingActivityNotification()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Log.d(TAG, "onStartCommand")
if (!isStarted) {
isStarted = true
if (!isBound) {
// We may have been restarted by the system. Manage our lifetime accordingly.
stopSelfIfNotRunning()
}
// Start collecting exercise information. We might stop shortly (see above), in which
// case launchWhenStarted takes care of canceling this coroutine.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
healthServicesManager.exerciseUpdateFlow.collect {
when (it) {
is ExerciseMessage.ExerciseUpdateMessage ->
processExerciseUpdate(it.exerciseUpdate)
is ExerciseMessage.LapSummaryMessage ->
_exerciseLaps.value = it.lapSummary.lapCount
is ExerciseMessage.LocationAvailabilityMessage ->
_locationAvailabilityState.value = it.locationAvailability
}
}
}
}
}
}
// If our process is stopped, we might have an active exercise. We want the system to
// recreate our service so that we can present the ongoing notification in that case.
return Service.START_STICKY
}
private fun stopSelfIfNotRunning() {
lifecycleScope.launch {
// We may have been restarted by the system. Check for an ongoing exercise.
if (!healthServicesManager.isExerciseInProgress()) {
// Need to cancel [prepareExercise()] to prevent battery drain.
if (_exerciseState.value == ExerciseState.PREPARING) {
lifecycleScope.launch {
healthServicesManager.endExercise()
}
}
// We have nothing to do, so we can stop.
stopSelf()
}
}
}
private fun processExerciseUpdate(exerciseUpdate: ExerciseUpdate) {
val oldState = _exerciseState.value
if (!oldState.isEnded && exerciseUpdate.state.isEnded) {
// Our exercise ended. Gracefully handle this termination be doing the following:
// TODO Save partial workout state, show workout summary, and let the user know why the exercise was ended.
// Dismiss any ongoing activity notification.
removeOngoingActivityNotification()
// Custom flow for the possible states captured by the isEnded boolean
when (exerciseUpdate.state) {
ExerciseState.TERMINATED -> {
// TODO Send the user a notification (another app ended their workout)
Log.i(
TAG,
"Your exercise was terminated because another app started tracking an exercise"
)
}
ExerciseState.AUTO_ENDED -> {
// TODO Send the user a notification
Log.i(
TAG,
"Your exercise was auto ended because there were no registered listeners"
)
}
ExerciseState.AUTO_ENDED_PERMISSION_LOST -> {
// TODO Send the user a notification
Log.w(
TAG,
"Your exercise was auto ended because it lost the required permissions"
)
}
else -> {
}
}
} else if (oldState.isEnded && exerciseUpdate.state == ExerciseState.ACTIVE) {
// Reset laps.
_exerciseLaps.value = 0
}
_exerciseState.value = exerciseUpdate.state
_exerciseMetrics.value = exerciseUpdate.latestMetrics
_aggregateMetrics.value = exerciseUpdate.latestAggregateMetrics
_exerciseDurationUpdate.value =
ActiveDurationUpdate(exerciseUpdate.activeDuration, Instant.now())
}
override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
handleBind()
return localBinder
}
override fun onRebind(intent: Intent?) {
super.onRebind(intent)
handleBind()
}
private fun handleBind() {
if (!isBound) {
isBound = true
// Start ourself. This will begin collecting exercise state if we aren't already.
startService(Intent(this, this::class.java))
}
}
override fun onUnbind(intent: Intent?): Boolean {
isBound = false
lifecycleScope.launch {
// Client can unbind because it went through a configuration change, in which case it
// will be recreated and bind again shortly. Wait a few seconds, and if still not bound,
// manage our lifetime accordingly.
delay(UNBIND_DELAY_MILLIS)
if (!isBound) {
stopSelfIfNotRunning()
}
}
// Allow clients to re-bind. We will be informed of this in onRebind().
return true
}
private fun removeOngoingActivityNotification() {
if (isForeground) {
Log.d(TAG, "Removing ongoing activity notification")
isForeground = false
stopForeground(true)
}
}
private fun postOngoingActivityNotification() {
if (!isForeground) {
isForeground = true
Log.d(TAG, "Posting ongoing activity notification")
createNotificationChannel()
startForeground(NOTIFICATION_ID, buildNotification())
}
}
private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL,
NOTIFICATION_CHANNEL_DISPLAY,
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(notificationChannel)
}
private fun buildNotification(): Notification {
// Make an intent that will take the user straight to the exercise UI.
val pendingIntent = NavDeepLinkBuilder(this)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.exerciseFragment)
.createPendingIntent()
// Build the notification.
val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
.setContentTitle(NOTIFICATION_TITLE)
.setContentText(NOTIFICATION_TEXT)
.setSmallIcon(R.drawable.ic_run)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_WORKOUT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// Ongoing Activity allows an ongoing Notification to appear on additional surfaces in the
// Wear OS user interface, so that users can stay more engaged with long running tasks.
val lastUpdate = exerciseDurationUpdate.value
val duration = lastUpdate.duration + Duration.between(lastUpdate.timestamp, Instant.now())
val startMillis = SystemClock.elapsedRealtime() - duration.toMillis()
val ongoingActivityStatus = Status.Builder()
.addTemplate(ONGOING_STATUS_TEMPLATE)
.addPart("duration", Status.StopwatchPart(startMillis))
.build()
val ongoingActivity =
OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
.setAnimatedIcon(R.drawable.ic_run)
.setStaticIcon(R.drawable.ic_run)
.setTouchIntent(pendingIntent)
.setStatus(ongoingActivityStatus)
.build()
ongoingActivity.apply(applicationContext)
return notificationBuilder.build()
}
/** Local clients will use this to access the service. */
inner class LocalBinder : Binder() {
fun getService() = this#ExerciseServiceTaekwondo
}
companion object {
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_CHANNEL = "com.example.exercise.ONGOING_EXERCISE"
private const val NOTIFICATION_CHANNEL_DISPLAY = "Ongoing Exercise"
private const val NOTIFICATION_TITLE = "Exercise Sample"
private const val NOTIFICATION_TEXT = "Ongoing Exercise"
private const val ONGOING_STATUS_TEMPLATE = "Ongoing Exercise #duration#"
private const val UNBIND_DELAY_MILLIS = 3_000L
fun bindService(context: Context, serviceConnection: ServiceConnection) {
val serviceIntent = Intent(context, ExerciseServiceTaekwondo::class.java)
context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
}
fun unbindService(context: Context, serviceConnection: ServiceConnection) {
context.unbindService(serviceConnection)
}
}
}

How to prevent PendingIntent from creating a new activity in Android

I'm making an APK file download screen and i show progress which is the percent of the file downloaded in the screen and also i have a notification to use a Foreground service because the file download must be downloaded from a service. During downloading, users can leave the app and can go back to my app through the notification. But the problem is, to achieve this logic, i use PendingIntent and when user click my app's notification, it recreates the app instead of going back to the previous screen earlier. I don't know why. please check my codes below.
File Download Fragment
class FileDownloadFragment(private val uri: String) : DialogFragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startDownloadService()
}
private fun startDownloadService() {
val downloadService = AppUpdateAPKDownloadService()
val startIntent = Intent(context, downloadService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(startIntent)
} else {
context.startService(startIntent)
}
}
...
}
Foreground Service
class AppUpdateAPKDownloadService: LifecycleService() {
...
/** Dispatcher */
private val ioDispatcher = Dispatchers.IO
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
if (intent != null) {
createNotificationChannel()
val notification = getNotification()
startForeground(NOTIFICATION_ID, notification)
lifecycleScope.launch{
downloadAPK(intent.getStringExtra(UPDATE_APK_URI).toString())
}
}
return START_NOT_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
).apply {
enableVibration(true)
enableLights(true)
}
val manager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
private fun getNotification(): Notification {
val intent = Intent(applicationContext, SplashActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
val pendingIntent = TaskStackBuilder.create(applicationContext).run {
addNextIntentWithParentStack(intent)
getPendingIntent(APP_UPDATE_PENDING_INTENT_REQUEST_CODE, PendingIntent.FLAG_IMMUTABLE)
}
// val pendingIntent = PendingIntent.getActivity(
// applicationContext,
// APP_UPDATE_PENDING_INTENT_REQUEST_CODE,
// intent,
// PendingIntent.FLAG_IMMUTABLE
// )
val notification = NotificationCompat.Builder(this, CHANNEL_ID).apply {
setContentTitle("DownloadApp")
setContentText("Downloading...")
setSmallIcon(R.drawable.icon)
setLargeIcon(BitmapFactory.decodeResource(this#AppUpdateAPKDownloadService.applicationContext.resources, R.mipmap.icon))
priority = NotificationCompat.PRIORITY_HIGH
setContentIntent(pendingIntent)
setOngoing(true)
setAutoCancel(true)
}.build()
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.notify(NOTIFICATION_ID, notification)
return notification
}
private suspend fun downloadAPK(uri: String) = withContext(ioDispatcher) {
kotlin.runCatching {
URL(uri)
}.onSuccess { url ->
val connection: URLConnection = url.openConnection()
connection.connect()
val fileFullSize = connection.contentLength
val directory = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
directory?.let {
if (!directory.exists()) directory.mkdirs()
}
val inputDataStream = DataInputStream(BufferedInputStream(url.openStream(), fileFullSize))
val file = File(directory, "my_app.apk")
val outputDataStream = DataOutputStream(BufferedOutputStream(FileOutputStream(file), fileFullSize))
processDownload(
inputStream = inputDataStream,
outputStream = outputDataStream,
fileFullSize = fileFullSize
)
val bundle = Bundle().apply {
putInt(UPDATE_APK_RECEIVER_MODE, UPDATE_APK_DOWNLOAD_COMPLETE)
}
uiUpdateReceiver?.send(Activity.RESULT_OK, bundle)
}.onFailure {
}
}
private fun processDownload(
inputStream: DataInputStream,
outputStream: DataOutputStream,
fileFullSize: Int
) {
val data = ByteArray(fileFullSize)
var downloadSize: Long = 0
var count: Int
while (inputStream.read(data).also { count = it } != -1) {
downloadSize += count
val percent = (downloadSize.toFloat() / fileFullSize) * 100
Log.d("TEST", "writing...$percent% in Service")
val bundle = Bundle().apply {
putInt(UPDATE_APK_RECEIVER_MODE, UPDATE_APK_UI_UPDATE)
putFloat(UPDATE_API_UI_PERCENT, percent)
}
uiUpdateReceiver?.send(Activity.RESULT_OK, bundle)
outputStream.write(data, 0, count)
}
outputStream.flush()
outputStream.close()
inputStream.close()
}
...
}
I also add android:launchMode="singleTop" to SplashActivity in AndroidManifest.xml
but it's still not working...
What mistake did I make?
TaskStackBuilder will always recreate the activities. That's the way it is designed. You don't want to use it if you want to return to an existing task.
Instead of your code to create the PendingIntent, use this:
val intent = PackageManager.getLaunchIntentForPackage("your.package.name")
val pendingIntent = PendingIntent.getActivity(applicationContext,
APP_UPDATE_PENDING_INTENT_REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE)
This will just cause your existing task to be brought to the foreground in whatever state it was in last. If your app is not running, this will launch your app as it would if you tapped the app icon on the HOME screen.
Okay, I solved this problem. it's 3 steps.
use PendingIntent.getActivity() instead of TaskStackBuilder.create(applicationContext).
use PendingIntent.FLAG_IMMUTABLE if you target Android M(API23) or above, or PendingIntent.FLAG_UPDATE_CURRENT if you target Android Lollipop(API21) or below.
add these flags Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP to the Intent.

How to implement a self-managed ConnectionService using Kotlin

I am an Android beginner, trying to setup Android code for my Cordova app.
My code works fine. No error whatsoever. All Logs showing but the Incoming call just doesn't show.
I have connected the ConnectionService to FirebaseMessaging service to fire incoming call on receiving a data push notification.
I receive the notification and all of the code runs successfully. Still no incoming call UI. Tried on Android 9 and 10 devices.
MyConnectionService.kt
#RequiresApi(Build.VERSION_CODES.M)
class MyConnectionService : ConnectionService() {
override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
Log.i("CallConnectionService", "onCreateOutgoingConnection")
val conn = VoipConnection(applicationContext)
conn.setAddress(request!!.address, PRESENTATION_ALLOWED)
conn.setInitializing()
//conn.videoProvider = MyVideoProvider()
conn.setActive()
return conn
}
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
Log.i("CallConnectionService", "onCreateIncomingConnection")
val conn = VoipConnection(applicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
conn.connectionProperties = Connection.PROPERTY_SELF_MANAGED
}
conn.setCallerDisplayName("test call", TelecomManager.PRESENTATION_ALLOWED)
//conn.setAddress(request!!.address, PRESENTATION_ALLOWED)
conn.setRinging()
conn.setInitializing()
conn.setActive()
return conn
}
override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?) {
super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
Log.i("CallConnectionService", "create outgoing call failed ")
}
override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?) {
super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
Log.i("CallConnectionService", "create outgoing call failed")
}
}
VoipConnection.kt
#RequiresApi(Build.VERSION_CODES.M)
class VoipConnection(ctx:Context) : Connection() {
var ctx:Context = ctx
val TAG = "CallConnection"
override fun onShowIncomingCallUi() {
Log.i(TAG, "onShowIncomingCallUi")
}
FirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
#RequiresApi(Build.VERSION_CODES.M)
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d(TAG, "From: " + remoteMessage.from)
try {
val callManager = CallManager(this)
val telecomManager = this.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
val componentName = ComponentName(this, MyConnectionService::class.java)
val phoneAccountHandle = PhoneAccountHandle(componentName,"Admin")
val callInfo = Bundle()
callInfo.putString("from", "tester")
telecomManager.addNewIncomingCall(phoneAccountHandle, callInfo)
}
catch (e: Exception) {
Log.e("error", e.toString())
}
}
override fun onNewToken(s: String) {
Log.d(TAG, "Refreshed token: $s")
}
}
CallManger.kt
#RequiresApi(Build.VERSION_CODES.M)
class CallManager(context: Context) {
val telecomManager: TelecomManager
var phoneAccountHandle: PhoneAccountHandle
var context: Context
val number = "3924823202"
init {
telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
this.context = context
val componentName = ComponentName(this.context, MyConnectionService::class.java)
phoneAccountHandle = PhoneAccountHandle(componentName, "Admin")
val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Admin")
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED).build()
telecomManager.registerPhoneAccount(phoneAccount)
val intent = Intent()
intent.component = ComponentName(
"com.android.server.telecom",
"com.android.server.telecom.settings.EnableAccountPreferenceActivity"
)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
Log.d("mainactivity", "init TelecomManager all well");
}
}
}
I know that this is late, but I am also working on it right now and I am also stumbling over a lot of issues.
You could try adding
callInfo.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandler) before calling addNewIncomingCall. I am not 100% sure if that will fix it, but I guess it's worth a try.
I guess you should call "onShowIncomingCallUi" manually in your "onCreateIncomingConnection" implementation like that:
conn.onShowIncomingCallUi()

"<App> is having trouble with Google Play Services. Please try again" while usiing Google NearBy Messages API

While using google NearBy Messages API I am getting error " is having trouble with Google Play Services. Please try again"
Please guide me for this issue.
Below is import for Google Messaging API
implementation 'com.google.android.gms:play-services-nearby:17.0.0'
Here is how I am subscribing using code
val options = SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.build()
Nearby.getMessagesClient(
this, MessagesOptions.Builder()
.setPermissions(NearbyPermissions.BLE)
.build())
Nearby.getMessagesClient(this).subscribe(getPendingIntent(), options)
I resolved it.
Nearby suggest using activity, on activty, the function will work better (https://developers.google.com/android/reference/com/google/android/gms/nearby/messages/MessagesClient#subscribe(android.app.PendingIntent,%20com.google.android.gms.nearby.messages.SubscribeOptions))
All of the Messages APIs should be used from a foreground Activity,
with the exception of the variants of subscribe that take a
PendingIntent parameter. Your Activity should publish(Message) or
subscribe(MessageListener) either in onStart() or in response to a
user action in a visible Activity, and you should always symmetrically
unpublish(Message) or unsubscribe(MessageListener) in onStop().
When subcribe, if using activity, it will ask to grant permission to bluetooth, location, microphone, if using service it will not ask
So if you use the service, you must combine using the activity.
When you subscribe in mainActivity, if another activity appears on top (then MainActivty will be onStop), a notification will appear.
Therefore, when subcribe, you must click OK to allow the another activity to be displayed
This is sample:
MainActivity.tk
private val mMessageListener: MessageListener = object : MessageListener() {
override fun onFound(message: Message) {
Log.d(TAG, "onFound message:"+ String(message.content))
}
override fun onLost(message: Message) {
Log.d(TAG, "Lost sight of message: " + String(message.content))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sharedPref: SharedPreferences = getSharedPreferences("MyPref", Context.MODE_PRIVATE)
val isFirstTime = sharedPref.getBoolean("FIRST_TIME", true)
if(isFirstTime) {
Nearby.getMessagesClient(this).subscribe(mMessageListener).addOnCompleteListener(this, OnCompleteListener {
requestPermissionFirstTime()
}).addOnCanceledListener(this, OnCanceledListener {
requestPermissionFirstTime()
})
} else {
requestPermissionCapture()
checkPermissionAccessibility()
startService(Intent(this, NearbyMessageService::class.java))
}
}
private fun requestPermissionFirstTime() {
val sharedPref: SharedPreferences = getSharedPreferences(Utils.IAMHERE_PREF, Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putBoolean("FIRST_TIME", false)
editor.apply()
Nearby.getMessagesClient(this).unsubscribe(mMessageListener)
requestPermissionCapture()
checkPermissionAccessibility()
}
NearbyMessageService.tk
class NearbyMessageService: IntentService("NearbyMessageService") {
private val mMessageListener: MessageListener = object : MessageListener() {
override fun onFound(message: Message) {
Log.d(TAG, "onFound message:"+ String(message.content))
}
override fun onLost(message: Message) {
Log.d(TAG, "Lost sight of message: " + String(message.content))
}
}
override fun onCreate() {
super.onCreate()
startForeground()
Nearby.getMessagesClient(this).subscribe(mMessageListener)
}
private fun startForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "002"
val channelName = "Nearby Service Channel"
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
val notification: Notification = Notification.Builder(applicationContext, channelId)
.setOngoing(true)
.setCategory(Notification.CATEGORY_SERVICE)
.setContentTitle(getString(R.string.app_name))
.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(Utils.NOTICATION_ID_NEARBY, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
} else {
startForeground(Utils.NOTICATION_ID_NEARBY, notification)
}
} else {
startForeground(Utils.NOTICATION_ID_NEARBY, Notification())
}
}
}

Nearby Messages using an IntentService

Initially I setup a BroadcastReceiver to receive intents from the Nearby Messages API.
class BeaconMessageReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Nearby.getMessagesClient(context).handleIntent(intent, object : MessageListener() {
override fun onFound(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Found iBeacon=$id")
sendNotification(context, "Found iBeacon=$id")
}
override fun onLost(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Lost iBeacon=$id")
sendNotification(context, "Lost iBeacon=$id")
}
})
}
private fun sendNotification(context: Context, text: String) {
Timber.d("Send notification.")
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(context, Notifications.CHANNEL_GENERAL)
.setContentTitle("Beacons")
.setContentText(text)
.setSmallIcon(R.drawable.ic_notification_white)
.build()
manager.notify(NotificationIdGenerator.nextID(), notification)
}
}
Then registered this receiver in my MainActivity after location permissions have been granted.
class MainActivity : AppCompatActivity() {
// ...
private fun onLocationPermissionsGranted() {
val filter = MessageFilter.Builder()
.includeIBeaconIds(UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FEED"), null, null)
.build()
val options = SubscribeOptions.Builder().setStrategy(Strategy.BLE_ONLY).setFilter(filter).build()
Nearby.getMessagesClient(context).subscribe(getPendingIntent(), options)
}
private fun getPendingIntent(): PendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(context, BeaconMessageReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
}
This worked well while the app was open, but does not work when the app is closed. So I found this example, that demonstrates how to setup an IntentService to receive messages while the app is in the background.
The example does use the Nearby.Messages class, which was deprecated in favor of the MessagesClient. So I replaced the deprecated code with the MessagesClient implementation.
class MainActivity : AppCompatActivity() {
// ...
private fun onLocationPermissionsGranted() {
val filter = MessageFilter.Builder()
.includeIBeaconIds(UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FEED"), null, null)
.build()
val options = SubscribeOptions.Builder().setStrategy(Strategy.BLE_ONLY).setFilter(filter).build()
Nearby.getMessagesClient(context).subscribe(getPendingIntent(), options)
.addOnSuccessListener {
Timber.i("Subscribed successfully.")
startService(Intent(this, BeaconMessageIntentService::class.java))
}.addOnFailureListener {
Timber.e(exception, "Subscription failed.")
}
}
private fun getPendingIntent(): PendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(context, BeaconMessageIntentService::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
}
And this is the IntentService (which is almost identical to my BroadcastReceiver).
class BeaconMessageIntentService : IntentService("BeaconMessageIntentService") {
override fun onHandleIntent(intent: Intent?) {
intent?.let {
Nearby.getMessagesClient(this)
.handleIntent(it, object : MessageListener() {
override fun onFound(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Found iBeacon=$id")
sendNotification("Found iBeacon=$id")
}
override fun onLost(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Lost iBeacon=$id")
sendNotification("Lost iBeacon=$id")
}
})
}
}
private fun sendNotification(text: String) {
Timber.d("Send notification.")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(this, Notifications.CHANNEL_GENERAL)
.setContentTitle("Beacons")
.setContentText(text)
.setSmallIcon(R.drawable.ic_notification_white)
.build()
manager.notify(NotificationIdGenerator.nextID(), notification)
}
}
onHandleIntent is called, and the Intent is not null; yet for some reason onFound() and onLost() are never called. Why would this be the case?
It's not really a solution but what I found is this (credit to this answer):
I've tried a few configurations including a BroadcastReceiver and adding a JobIntentService to run the code in the background, but every time I got this the onExpired callback which you can set to the SubscribeOptions:
options.setCallback(new SubscribeCallback() {
#Override
public void onExpired() {
super.onExpired();
Toast.makeText(context.get(), "No longer Subscribing!", Toast.LENGTH_SHORT).show();
}
}
When the subscribe occurred in the background it was delayed, but it was still called.
Notes:
1. When I've tested with Strategy.BLE_ONLY I did not get the onFound callback.
2. From Google's documentation:
Background subscriptions consumes less power than foreground
subscriptions, but have higher latency and lower reliability
When testing I found this "lower reliability" to be an understatement: onFound was rarely called and I never got the onLost.
I know this is a late reply, but I had the same problem and found out by debugging that it is an issue related to this error: "Attempting to perform a high-power operation from a non-Activity Context". This can be solved when calling Nearby.getMessagesClient(this) by passing in an activity context instead of this.
In my case I added a class extending Application which helps in returning this context (the below is in java but should be translatable to kotlin easily)
public class MyApplication extends Application {
private Activity currentActivity = null;
public Activity getCurrentActivity(){
return currentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity){
this.currentActivity = mCurrentActivity;
}
}
And in my base activity, from which all activities extend, I set the current activity by calling ((MyApplication) this.getApplicationContext()).setCurrentActivity(this); in the constructor.
My service can then call getMessagesClient with the correct context like below:
final Activity context = ((MyApplication)getApplicationContext()).getCurrentActivity();
Nearby.getMessagesClient(context).[...]
Do not forget to register your Application class in the AndroidManifest:
<application
android:name="com.example.xxx.MyApplication"`

Categories

Resources