I'm trying to implement geofencing as mentioned here: https://developer.android.com/training/location/geofencing
I get my BroadcastReceiver's onReceive called, but then a weird thing happens, geofencingEvent.geofenceTransition is always -1, instead of Geofence.GEOFENCE_TRANSITION_ENTER.
In any given time i have only 1 geofence. Tested on several devices and emulators. On real devices used Lockito app to simulate movement, on emulators used it's own control panel to change location.
CODE:
RECEIVER:
class GeofenceBroadcastReceiver : BroadcastReceiver(),KoinComponent {
val navInManager:NavInManager = get()
override fun onReceive(context: Context, intent: Intent) {
Timber.d("***** GeofenceBroadcastReceiver onReceive")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes
.getStatusCodeString(geofencingEvent.errorCode)
Timber.e("***** error $errorMessage")
return
}
val geofenceTransition = geofencingEvent.geofenceTransition
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
Timber.i("***** ENTER triggered id ${triggeringGeofences[0].requestId}")
} else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
Timber.i("***** EXIT triggered id ${triggeringGeofences[0].requestId}")
} else {
Timber.e("***** invalid_type $geofenceTransition")
}
}
}
CREATING THE GEOFENCE:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(App.instance, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(App.instance, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
private fun getGeofencingRequest(geofence: Geofence): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofence(geofence)
}.build()
}
fun setGeofenceForPoint(stopPoint: StopPoint){
val sessionId = "DELIVERY"
Timber.d("***** before Geofence $sessionId")
val geofence =
Geofence.Builder()
.setRequestId(sessionId)
.setCircularRegion(
stopPoint.location.latitude,
stopPoint.location.longitude,
1000F
)
.setExpirationDuration(1800000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
if (ActivityCompat.checkSelfPermission(
GetPackageCourierApp.instance,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
geofencingClient?.addGeofences(getGeofencingRequest(geofence), geofencePendingIntent)?.run {
addOnSuccessListener {
Timber.d("***** Geofence(s) added")
}
addOnFailureListener {
Timber.e("***** Failed to add geofence(s)")
}
}
}
}
Related
Scenario: The below mentioned code snippet shows a BroadcastReciver whose work is to start a service whenever the added geonfence is entered. A JobIntentService is then initiated which does some operation with the passed on string, and then triggers notification with the data.
What needs to be done: I want that, among many other added geofences, when a specific geofence is triggered, it should be removed to prevent from retriggering.
Question: How to implement this in above mentioned scenario?
Below is the screenshot for reference
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == ACTION_GEOFENCE_EVENT) {
val geoFencingEvent = GeofencingEvent.fromIntent(intent)
if (geoFencingEvent.hasError()) {
Timber.i("Error while activating Geofencing: ${geoFencingEvent.errorCode}")
return
} else {
val geoFenceTransition = geoFencingEvent.geofenceTransition
if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
val geofenceId = geoFencingEvent.triggeringGeofences[0].requestId
val jobIntent = Intent(context, GeofenceTransitionsJobIntentService::class.java)
.putExtra("REMINDER_ID", geofenceId)
GeofenceTransitionsJobIntentService.enqueueWork(context, jobIntent)
}
}
}
}
}
I am playing around with Wear OS on a Fossil Falster 3 (it is running API 28). Also please know that I am just starting with Kotlin, so the code below is not good to say the least.
I want to create a Geofence using broadcast receiver.
In my MainActivity.kt I have the following:
override fun onStart() {
super.onStart()
requestForegroundAndBackgroundLocationPermissions()
val locations = arrayOf(Location("tgh", 58.798233, 11.123959))
val geofences = locations.map {
Geofence.Builder()
.setRequestId(it.name)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setCircularRegion(it.latitude, it.longitude, 100F)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_DWELL)
.setLoiteringDelay(10)
.build()
}
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofences)
}.build()
val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceReceiver::class.java)
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
val geofencingClient = LocationServices.getGeofencingClient(this)
geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent).run {
addOnFailureListener {
// display error
exception -> Log.d(TAG, "addOnFailureListener Exception: $exception")
}
addOnSuccessListener {
// move on
Log.d(TAG, "addOnSuccessListener, move on")
}
}
}
The GeofenceReceiver is in the same file:
class GeofenceReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
Log.d(TAG, "geofencingEvent.hasError()")
} else {
geofencingEvent.triggeringGeofences.forEach {
val geofence = it.requestId
Log.d(TAG, "location at: " + geofence)
}
}
}
}
The manifest has the following snippet:
<receiver android:name=".GeofenceReceiver"
android:enabled="true"
android:exported="true"/>
That is pretty much it. When I run the code, addOnFailureListener is triggered and the error message is printed and it is resulting in a 1000 exception.
Exception 1000 according to google documentation means GEOFENCE_NOT_AVAILABLE. I have enabled location on both the watch and the phone to its highest level on phone (Improve Location Accuracy is ON). On the watch I have set to use location from both watch and phone (highest level). Still I keep getting the 1000 error code.
I'm making a note app that trigger notificaitions when you enter a geofence if I make more than 1 note with the same location when I enter it only on geofence triggers and only shows 1 notification.
Here is the code:
Broadcast Receiver
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//if (intent.action == ACTION_GEOFENCE_EVENT) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = errorMessage(context, geofencingEvent.errorCode)
Log.e(TAG, errorMessage)
return
}
if (geofencingEvent.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
Log.v(TAG, "context.getString(R.string.geofence_entered)")
val fenceId = when {
geofencingEvent.triggeringGeofences.isNotEmpty() ->
geofencingEvent.triggeringGeofences
else -> {
Log.e(TAG, "No Geofence Trigger Found! Abort mission!")
return
}
}
/*val foundIndex = GeofencingConstants.LANDMARK_DATA.indexOfFirst {
it.id == fenceId
}*/
// Unknown Geofences aren't helpful to us
/*if ( -1 == foundIndex ) {
Log.e(TAG, "Unknown Geofence: Abort Mission")
return
}*/
for (geofence in fenceId){
sendGeofenceEnteredNotification(
context, geofence.requestId, intent.getStringExtra("note")!!
)
}
}
}
// }
}
-------------- Methods for adding geofencing ---------------------------
private fun createPendingIntent(context: Context, note: Note) : PendingIntent{
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
intent.action = "MainActivity.treasureHunt.action.ACTION_GEOFENCE_EVENT"
intent.putExtra("note", note.note)
return PendingIntent.getBroadcast(context, note.date.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun addGeofenceForNote(note: Note, context: Context) {
val geofencingClient = LocationServices.getGeofencingClient(context)
val geofence = Geofence.Builder()
.setRequestId(note.id)
.setCircularRegion(note.latitude, note.longitude, GeofencingConstants.GEOFENCE_RADIUS_IN_METERS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
// Build the geofence request
val geofencingRequest = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
geofencingClient.addGeofences(geofencingRequest, createPendingIntent(context, note))?.run {
addOnSuccessListener {
Log.e("Add Geofence", geofence.requestId)
}
addOnFailureListener {
if ((it.message != null)) {
Log.w("TAG", it.message!!)
}
}
}
}
I made 3 notes with the same location and the 2nd one was the one that trigger the broadcastreceiver.
P.S. even if the geofence are in totally different locations I still can only get 1 notification. As far as I can see all pending intents and notification get different IDs.
Ty in advance
the problem wasa the phone for some reason couldnt create all the notifications.
the code that fixed it was the .apply{} on the notificationManager
fun NotificationManager.handleNotification(context: Context, geofences: List<Geofence>){
val list = ArrayList<Notification>()
val set = mutableSetOf<Int>()
val notificationIdBase = (Date().time / 1000L % Int.MAX_VALUE).toInt()
apply {
for (i in geofences.indices){
val notificationId = notificationIdBase + i
val notification = createGroupNotification(context, notificationId, geofences[i].requestId)
list.add(notification)
notify(notificationId, notification)
set.add(notificationId)
Log.d("TAG", notificationId.toString())
}
Log.d("TAG", geofences.size.toString() + " " + list.size + " " + set.size)
}
}
I try to make appear a push alert to the user when he reach a defined zone.
So I coded my app from : https://developer.android.com/training/location/geofencing
It is working perfectly if my app is running with a service following the location of the user.
It also work if I start google map for example, that will track my location too. Pushes will appear.
But if I close my app the push won't appear, so the geofencing is not detected if no app is tracking my location.
Is it normal ?
How to make it work always ?
What is the point of geofencing if you need a foreground service following your location ?
public void createGeofenceAlerts(LatLng latLng, int radius) {
final Geofence enter = buildGeofence(ID_ENTER, latLng, radius, Geofence.GEOFENCE_TRANSITION_ENTER);
final Geofence exit = buildGeofence(ID_EXIT, latLng, radius, Geofence.GEOFENCE_TRANSITION_EXIT);
final Geofence dwell = buildGeofence(ID_DWELL, latLng, radius, Geofence.GEOFENCE_TRANSITION_DWELL);
GeofencingRequest request = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build();
fencingClient.addGeofences(request, getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Timber.i("succes");
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Timber.e(e,"failure");
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show();
}
});
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
PendingIntent pending = PendingIntent.getService(
mContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return pending;
}
private Geofence buildGeofence(String id, LatLng center, int radius, int transitionType) {
Geofence.Builder builder = new Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.getLatitude(),
center.getLongitude(),
radius)
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE);
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY);
}
return builder.build();
}
I have been working with GeoFence for such a long time, I had the same question and I got the answer by myself after trying different solutions, So basically, GeoFence only get triggers if any app in the phone is fetching the location for some x duration. If you test the GeoFence sample app provided by google then you can see that the app works only when you open the Google maps application, its because Google Maps is the only app in the device that requests locations passively.
For Prove you can clone GeoFence sample and the LocationUpdateForGroundService sample from this below link
https://github.com/googlesamples/android-play-location
Run both of them GeoFence and LocationUpdateForGroundService at the same time, You will notice by changing the lat and long from the emulator that now you dont need to open Google maps any more because now there is another app which is requesting location.
So do create a foreground service in the GeoFence application and use Fuse Location Client to request location updates for some x duration.
I think I found a solution, tested on Android 9. I used the Google documentation https://developer.android.com/training/location/geofencing but I replaced the service by a broadcast receiver.
My GeofenceManager :
private val braodcastPendingIntent: PendingIntent
get() {
val intent = Intent(mContext, GeofenceTransitionsBroadcastReceiver::class.java)
val pending = PendingIntent.getBroadcast(
mContext.applicationContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
return pending
}
fun createGeofenceAlerts(latLng: LatLng, radiusMeter: Int, isBroadcast: Boolean) {
val enter = buildGeofence(ID_ENTER, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_ENTER)
val exit = buildGeofence(ID_EXIT, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_EXIT)
val dwell = buildGeofence(ID_DWELL, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_DWELL)
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build()
val pending = if (isBroadcast) {
braodcastPendingIntent
} else {
servicePendingIntent
}
fencingClient.addGeofences(request, pending).addOnSuccessListener {
Timber.i("succes")
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show()
}.addOnFailureListener { e ->
Timber.e(e, "failure")
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show()
}
}
private fun buildGeofence(id: String, center: LatLng, radius: Int, transitionType: Int): Geofence {
val builder = Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.latitude,
center.longitude,
radius.toFloat())
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE)
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY)
}
return builder.build()
}
My BroadcastReceiver, obviously you need to declare it in the manfifest :
class GeofenceTransitionsBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.i("received")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
Timber.e("Geofence error")
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
|| geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Get the transition details as a String.
val geofenceTransitionDetails = GeofenceManager.getGeofenceTransitionDetails(
geofenceTransition,
triggeringGeofences, true
)
// Send notification and log the transition details.
GeofenceManager.sendNotification(context, geofenceTransition, geofenceTransitionDetails)
Timber.i(geofenceTransitionDetails)
} else {
// Log the error.
Timber.e("Unknown geo event : %d", geofenceTransition)
}
}
The important part is to know that on Android 8 and 9 the geofencing has a latency of 2 minutes.
I discovered that my geoFenceBroadCastReceiver started to work correctly in the emulator when I had google maps open. I simply could not figure out what the problem was, and as it turns out, I was missing a piece of the puzzle obviously. The behavior is also described in this question (aptly titled): Geofence Broadcast Receiver not triggered but when I open the google map, it works, as well as filed numerous times as an issue in the Android Location Samples project Issue 239, Issue 247 Issue 266.
I don't see the actual answer to this problem posted as an answer here, so for posterity I will provide some suggestions. Issue 264 seems to point to the solution, i.e. using a JobIntentService
BUT
There's a code comment in the GeoFence Sample LocationUpdateIntentService
Note: Apps running on "O" devices (regardless of targetSdkVersion) may
receive updates less frequently than the interval specified in the
{#link com.google.android.gms.location.LocationRequest}
when the app is no longer in the foreground.
that seems to confirm what #Vinayak points out in his answer here
O.S will not allow app to get location update from background. Implement geofence location code on foreground service. It will run as expected
The geofence broadcast receiver isn't allowed to get timely location updates (apparently even in an IntentService). You'll have to run location client in a foreground service that's requesting fine location access to get more accurate/timely geoFence triggering. So seemingly the right answer is to run the geoFence in a foreGroundService.
If you go the foreGroundService route, you'll also need to create a Notification Channel, as otherwise, you'll run into problems like this one.
See Also:
https://developer.android.com/guide/components/foreground-services
https://developer.android.com/training/location/change-location-settings#location-request
https://developer.android.com/training/location/request-updates
https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20com.google.android.gms.location.LocationCallback)
O.S will not allow app to get location update from background. Implement geofence location code on foreground service. It will run as expected
I apply #Mudassir Zulfiqar solution for foreground service implementation. Geofence will not working properly on background unless another app does not use your location or you may trigger location by your own.
Here is my foreground service implementation:
class GeofenceForegroundService: Service() {
private lateinit var geofenceHelper: GeofenceHelper
private lateinit var geofencingClient: GeofencingClient
private lateinit var pendingIntent: PendingIntent
private lateinit var locationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private val geofenceId = "Some_Geofence_Id"
private val geofenceRadius = 200.0
private val TAG = "GeofenceForegroundServi"
override fun onBind(p0: Intent?): IBinder? {
return null
}
#SuppressLint("MissingPermission")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
geofenceHelper = GeofenceHelper(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel("Geofence", "Geofence Loc", NotificationManager.IMPORTANCE_NONE)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
val notification: Notification = NotificationCompat.Builder(this, "Geofence")
.setContentTitle("Geofence Active")
.setContentText("Location Updating...")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(geofenceHelper.pendingIntent)
.build()
val latLng = intent?.getParcelableExtra<LatLng>("LATLNG")
geofencingClient = LocationServices.getGeofencingClient(this)
locationClient = LocationServices.getFusedLocationProviderClient(this)
val geofence = geofenceHelper.getGeofence(geofenceId, latLng!!, geofenceRadius.toFloat())
val geofencingRequest = geofenceHelper.getGeofencingRequest(geofence)
pendingIntent = geofenceHelper.pendingIntent
val locationRequest = LocationRequest.create().apply {
interval = 10000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
Log.d(TAG, "${locationResult.lastLocation.latitude} ${locationResult.lastLocation.longitude}")
}
}
Looper.myLooper()?.let {
locationClient.requestLocationUpdates(locationRequest, locationCallback,
it
)
}
geofencingClient.addGeofences(geofencingRequest, pendingIntent).run {
addOnSuccessListener {
Log.d(TAG, "onSuccess: Geofence Added...")
}
addOnFailureListener {
Log.d(TAG, "onFailure ${geofenceHelper.getErrorString(it)}")
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
} else {
startForeground(1, notification)
}
return START_STICKY
}
override fun onDestroy() {
Log.i(TAG, "onDestroy: RUN")
geofencingClient.removeGeofences(pendingIntent)
locationClient.removeLocationUpdates(locationCallback)
super.onDestroy()
}
override fun onTaskRemoved(rootIntent: Intent?) {
Log.i(TAG, "onTaskRemoved: RUN")
super.onTaskRemoved(rootIntent)
stopSelf()
}
}
If you have any questions about above code. Please share on your comments.
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!