I'm trying to use WorkManager instead of a Foreground Service. But I have a problem when I add createCancelPendingIntent() as an Intent action for the notification. When I click "Cancel" Button in the notification, the Worker stops but the notification does not get dismissed unless I start the Worker again, wait until it's done and send another notification with the same ID but with setOngoing(false).
This is my code:
class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
private val notificationManager = ContextCompat
.getSystemService(
applicationContext,
NotificationManager::class.java
) as NotificationManager
override suspend fun doWork(): Result {
fakeDownload()
notificationManager.sendNotification(
"Download complete!",
applicationContext)
return Result.success()
}
private suspend fun fakeDownload() {
for (progress in 0..100 step 10){
delay(1000)
setForeground(createForeground(progress))
}
}
private fun createForegroundInfo(progress: String): ForegroundInfo {
// Pending Intent to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle("Downloading")
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, "Cancel", intent)
.build()
return ForegroundInfo(0, notification)
}
}
Stopping the worker from the notification works by raising an exception. You need to catch the exception in the usual kotlin way, then manually clear the notification.
A few more details are here: https://stackoverflow.com/a/66900141
override suspend fun doWork(): Result {
return try {
set foreground
do work
Result.success() [or failure, as applicable]
} catch (e: Exception) {
Result.success() [or failure, as applicable]
} finally {
clean up...
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll() [or cancel(id) for more targetted operation]
Log.d(tag, "finally")
}
}
Related
I have setup the worker class, my intent was to fetch data from the web each 24 hours and i want to get a notification with fetched data. I used periodic work request. My app behavior is weird, notifications fire off only when i start the app. And when app is created, i receive multiple notifications with the data i want. I want only one notification each 24 hours.
Here is my worker class code
#RequiresApi(Build.VERSION_CODES.O)
class PeriodicWork(context: Context, workerParameters: WorkerParameters) :
CoroutineWorker(context, workerParameters) {
private val repository =
Repository(Database.invoke(applicationContext))
override suspend fun doWork(): Result {
try {
val notificationString = getNotificationResponse().body()!!.value
val notification =
NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_drawable)
.setContentTitle("Notification")
.setContentText(notificationString)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(notificationString)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
createNotificationChannel()
with(NotificationManagerCompat.from(applicationContext)) {
notify(1, notification)
}
} catch (e: Exception) {
}
return Result.success()
}
private fun createNotificationChannel() {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"New Notification",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Notification channel"
}
val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
private suspend fun getNotificationResponse(): Response<WantedData> {
val notificationResponse =
withContext(Dispatchers.IO) { repository.getRandomString() }
repository.saveString(notificationResponse.body()!!)
return notificationResponse
}
}
MainActivity part of code where i instantiate worker in a function which i call in onCreate lifecycle method
private fun setupPeriodicRequest() {
val periodicRequest =
PeriodicWorkRequestBuilder<PeriodicWork>(24, TimeUnit.HOURS)
.build()
WorkManager.getInstance()
.enqueueUniquePeriodicWork(
"Periodic Notification",
ExistingPeriodicWorkPolicy.REPLACE,
periodicRequest
)
}
If needed i can provide more info, thanks in advance!!!
The problem is when you instantiate worker in setupPeriodicRequest function. You're passing ExistingPeriodicWorkPolicy.REPLACE in your enqueueUniquePeriodicWork function.
Instead, you should use ExistingPeriodicWorkPolicy.KEEP that will only create WorkRequest if not exists.
So, this is how your setupPeriodicRequest function should look like:
private fun setupPeriodicRequest() {
val periodicRequest = PeriodicWorkRequestBuilder<PeriodicWork>(24, TimeUnit.HOURS)
.build()
WorkManager.getInstance()
.enqueueUniquePeriodicWork(
"Periodic Notification",
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest
)
}
From the code documentation about ExistingPeriodicWorkPolicy:
public enum ExistingPeriodicWorkPolicy {
/**
* If there is existing pending (uncompleted) work with the same unique
* name, cancel and delete it. Then, insert the newly-specified work.
*/
REPLACE,
/**
* If there is existing pending (uncompleted) work with the same unique
* name, do nothing.
* Otherwise, insert the newly-specified work.
*/
KEEP
}
I solved my problem with multiple network calls. The setup within the worker class was bad. When i put createNotificationChannel() function above network call the behavior i was experiencing disappeared. I would guess that app was making network calls as long as worker did not do its supposed job. In my case i had network call on first line and it would fire off network request until the notification was created.
override suspend fun doWork(): Result {
try {
createNotificationChannel()
val notificationString = getNotificationResponse().body()!!.value
val notification =
NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_drawable)
.setContentTitle("Notification")
.setContentText(notificationString)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(notificationString)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
with(NotificationManagerCompat.from(applicationContext)) {
notify(1, notification)
}
I've been researching awhile about how to keep active a constantly running audio playback in the background (online radio). For last I made a foreground service for it and its works for the most phones, but not on Samsung Android P and above... (as this article show in the "Foreground service limitations" section: https://proandroiddev.com/android-foreground-service-restrictions-d3baa93b2f70)
I heard that there is a advanced tool for audio playback called ExoPlayer. Could this lib help me out?
I'v been tried these solutions:
ping google.com every 2 sec
request battery optimization ignoring
set wake lock for mediplayer with: setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) (still in use)
Starting the service:
viewModel.isMusicControlServiceNeedToStart.observe(this, Observer {
if (it) {
val intent = Intent(this, MusicControlForegroundServiceImpl::class.java).apply { action = ACTION_SHOW_MUSIC_CONTROL }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(intent) else startService(intent)
} else {
stopService(Intent(this, MusicControlForegroundServiceImpl::class.java))
}
})
The service itself:
class MusicControlForegroundServiceImpl : Service(), KoinComponent {
private val notificationManager: NotificationManager by inject()
private val radioManager: RadioManager by inject()
private val context: Context by inject()
private val preferences: Preferences by inject()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action == ACTION_SHOW_MUSIC_CONTROL) {
val lastSelectedRadio = preferences.getJSON(Globals.LAST_SELECTED_RADIO_KEY, Radio::class.java)
?: return START_NOT_STICKY
val notification = notificationManager.createMediaControlNotificationIfNeeded(context, lastSelectedRadio)
startForeground(1, notification)
}
return START_NOT_STICKY
}
override fun onTaskRemoved(rootIntent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(rootIntent) else startService(rootIntent)
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
if (!notificationManager.musicControlServiceRestart) radioManager.release()
synchronized(MUSIC_CONTROL_SERVICE_LOCK) { notificationManager.musicControlServiceRestart = false }
synchronized(MEDIA_PLAYER_LOCK) { radioManager.lastPlayedMediaUrl = null }
stopForeground(true)
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}
The notification creation:
override fun createMediaControlNotificationIfNeeded(context: Context, selectedRadio: Radio): Notification {
val resultIntent = Intent(context, RadioDetailActivity::class.java)
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(resultIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
val playIntent = Intent(context, NotificationReceiver::class.java).apply {
putExtra(MusicState::class.java.name, MusicState.PLAY)
}
val pauseIntent = Intent(context, NotificationReceiver::class.java).apply {
putExtra(MusicState::class.java.name, MusicState.PAUSE)
}
val notificationManager = NotificationManagerCompat.from(context)
#Suppress("DEPRECATION") val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_icon)
.setContentTitle(selectedRadio.name)
.setDefaults(0)
.setOngoing(true)
.setNotificationSilent()
.addAction(
R.drawable.ic_notification_pause,
context.getString(R.string.pause),
PendingIntent.getBroadcast(context, 1, pauseIntent, 0)
)
.addAction(
R.drawable.ic_notification_play,
context.getString(R.string.play),
PendingIntent.getBroadcast(context, 2, playIntent, 0)
)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(
MediaSessionCompat(
context,
RadioDetailActivity::class.java.name
).sessionToken
)
)
.setContentIntent(resultPendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
context.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
builder.setChannelId(CHANNEL_ID)
}
return builder.build()
}
If you need any other resources please let me know and help if you can! I'm struggling with this problem for weeks now.. :(
UPDATE
Now I throw my media control notification in every 2 minutes to update previous, so the app can survive like 30 minutes on the affected phone, but still not a working solution...
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())
}
}
}
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"`
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!