How to show downloading progress Exoplayer - android

I am trying to download a video for offline in exoplayer, I want to show downloading progress inside an activity.
How can I bind to the DownloadService in exoplayer. so that I can update the current downloading progress in an activity? I try to override onBind method but there is no onBind method.
DownloadService
class MediaDownloadService : DownloadService(
C.DOWNLOAD_NOTIFICATION_ID, 1000,
C.CHANNEL_ID, R.string.channel_name, R.string.channel_description
) {
private lateinit var downloadManager: DownloadManager
override fun onCreate() {
downloadManager = DownloadUtil.getDownloadManager(this)
downloadManager.addListener(object : DownloadManager.Listener {
override fun onDownloadChanged(downloadManager: DownloadManager, download: Download) {
if (download.bytesDownloaded == download.contentLength) {
toast("Download Completed!")
}
debug(download.failureReason)
}
})
super.onCreate()
}
override fun getDownloadManager(): DownloadManager {
return downloadManager
}
override fun getForegroundNotification(downloads: MutableList<Download>): Notification {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notificationHelper = DownloadNotificationHelper(this, C.CHANNEL_ID)
return notificationHelper.buildProgressNotification(
R.drawable.ic_notification,
pendingIntent,
"simple message",
downloads
)
}
override fun getScheduler(): Scheduler? {
return null
}
inner class DownloadBinder: Binder() {
val service: MediaDownloadService
get() = this#MediaDownloadService
}
}

To show the current download progress in an activity
You need to override and listen to the DownloadTracker.Listener which will enable you to know the different state of your download.
When the state is at Download.STATE_DOWNLOADING start a coroutine/flow to send the current value of the download to your activity.
I have used this flow (which send every 1 second the value of the percentDownload of the download you'd like)
val downloadManager: DownloadManager // is set before in my object
suspend fun getCurrentProgressDownload(uri: Uri?): Flow<Float?> {
var percent: Float? = downloadManager.currentDownloads.find { it.request.uri == uri }?.percentDownloaded
return callbackFlow {
while(percent != null) {
percent = downloadManager.currentDownloads.find { it.request.uri == uri }?.percentDownloaded
offer(percent)
withContext(Dispatchers.IO) {
delay(1000)
}
}
}
}
Display it the way you like
I have created a repository where you can see download progress in the activity, This is merely an example that could use some optimisation.
https://github.com/yoobi/exoplayer-kotlin/tree/master/downloadvideo/src/main/java/io/github/yoobi/downloadvideo
if some thing are not clear don't hesitate to ask

Related

Media session has "no title" on AOD (Always on display)

In my app I displayed a notification with a foreground service, which is in charge of playing music. The notification is handled by
com.google.android.exoplayer2.ui.PlayerNotificationManager
android.support.v4.media.session.MediaSessionCompat
com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
mediaSession = MediaSessionCompat(this, "Player", null, null)
mediaSession.isActive = true
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlayer(exoPlayer)
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
this,
"notification_channel_player",
R.string.notification_channel_name_player,
0,
PLAYER_NOTIFICATION_ID,
object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun createCurrentContentIntent(player: Player?): PendingIntent? {
// intent
}
override fun getCurrentLargeIcon(player: Player?, callback: PlayerNotificationManager.BitmapCallback?): Bitmap? {
// large icon
}
override fun getCurrentContentText(player: Player?): String? {
// artist
}
override fun getCurrentContentTitle(player: Player?): String {
// title
}
},
object : NotificationListener {
override fun onNotificationPosted(notificationId: Int, notification: Notification?, ongoing: Boolean) {
startForeground(notificationId, notification)
}
})
playerNotificationManager.setSmallIcon(R.drawable.ic_notification)
// has previous and next
playerNotificationManager.setUseNavigationActions(true)
playerNotificationManager.setUseNavigationActionsInCompactView(true)
// no fast-forward and rewind
playerNotificationManager.setFastForwardIncrementMs(0)
playerNotificationManager.setRewindIncrementMs(0)
// no stop
playerNotificationManager.setUseStopAction(false)
playerNotificationManager.setMediaSessionToken(mediaSession.sessionToken)
playerNotificationManager.setPlayer(exoPlayer)
When the screen is on, there is no problem displaying the content title and text. But when I lock screen and in AOD mode, on my Pixel 3 I see a "No title" displayed. But if I use Apple Music, it displays the title and artists very well.
My app :
Apple music:
My question is, how can I configure this title and text based on my current implementation? Thanks.
I just answer my own question because I have found and solved the problem.
I have only set the notification's media description adapter, but in fact, the media session needs to set metadata too.
Since we are using mediaSessionConnector, it can be setup by passing a QueueNavigator to the mediaSessionConnector, so we can use the player instance and window index to build current media's metadata. e.x:
val timelineQueueNavigator = object : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(player: Player?, windowIndex: Int): MediaDescriptionCompat {
player?.let { safePlayer ->
return MediaDescriptionCompat.Builder().apply {
setTitle("......")
setSubtitle("......")
}.build()
}
return MediaDescriptionCompat.Builder().build()
}
}
mediaSessionConnector.setQueueNavigator(timelineQueueNavigator)
Another point is, by default the mediaSessionConnector use MediaSessionConnector.DefaultMediaMetadataProvider. It doesn't setup METADATA_KEY_ARTIST which will be used in AOD mode as the artist. So I have created my own MediaMetadataProvider, adding METADATA_KEY_ARTIST.
if (description.subtitle != null) {
val subTitle = description.subtitle.toString()
builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, subTitle)
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, subTitle)
}
Here I used METADATA_KEY_TITLE and METADATA_KEY_ARTIST for title and the description:
MediaMetaData data = PlayerManager.getInstance().getCurrentMediaMetaData();
Bitmap bitmap = ((BitmapDrawable) AppController.getInstance().getResources().getDrawable(R.drawable.app_logo)).getBitmap();
Bundle extras = new Bundle();
extras.putString(MediaMetadataCompat.METADATA_KEY_TITLE,data.getMediaTitle());
extras.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, data.getMediaAlbum());
return new MediaDescriptionCompat.Builder()
.setIconBitmap(bitmap)
.setExtras(extras)
.build();
You may have to build the notification like this:
Notification.Builder(context, channel).setContentTitle("Title").setContentText("Description").build()
Please, add your code here. It will be easier to help.
EDIT:
You are not returning the title at the adapter:
override fun getCurrentContentTitle(player: Player?): String = "Add the title here"

ExoPlayer problems trying to download current track

I am using Exoplayer to create my own music player. I am also adding the option to download the track but I have a problem when I am trying to download the track that I am playing. I add a notification to the download to check the progress of the download and it appears but it even doesn't start. What I think is that it might have some kind of problem with the buffering cache and the download since they are stored in the same folder.
To download the tracks I do the following:
override fun addDownloadTrack(track: Track) {
getIfTrackIsCached.run({ isCached ->
if (!isCached) {
val data = Util.toByteArray(track.title.byteInputStream())
val downloadRequest =
DownloadRequest(track.id, DownloadRequest.TYPE_PROGRESSIVE, Uri.parse(track.href), Collections.emptyList(), track.id, data)
DownloadService.sendAddDownload(context, ExoPlayerDownloadService::class.java, downloadRequest, false)
}
}, ::onError, GetIfTrackIsCached.Params(track.id))
}
This is the DownloadService:
class ExoPlayerDownloadService : DownloadService(
FOREGROUND_NOTIFICATION_ID,
DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
Constants.CHANNEL_DOWNLOAD_ID,
R.string.exo_download_notification_channel_name) {
private val manager: DownloadManager by inject()
private val channelIsCached: ChannelPublisher<CachedMedia> by inject(PUBLISHER_IS_CACHED)
private val notificationHelper: DownloadNotificationHelper by inject()
private var nextNotificationId: Int = FOREGROUND_NOTIFICATION_ID
override fun onCreate() {
super.onCreate()
if (!manager.isInitialized) {
manager.maxParallelDownloads = MAX_PARALLEL_DOWNLOADS
}
}
override fun getDownloadManager(): DownloadManager = manager
override fun getForegroundNotification(downloads: MutableList<Download>?): Notification {
var text = ""
var index = 1
downloads?.forEach { text += "${if (downloads.size > 1) "${index++} - " else ""}${Util.fromUtf8Bytes(it.request.data)}\n" }
return notificationHelper.buildProgressNotification(R.drawable.ic_stat_downloading, null, text, downloads)
}
override fun getScheduler(): Scheduler? = null
override fun onDownloadChanged(download: Download?) {
val notification = when (download?.state) {
Download.STATE_COMPLETED -> {
channelIsCached.publish(CachedMedia(download.request.id, true))
notificationHelper.buildDownloadCompletedNotification(R.drawable.ic_stat_download_complete, null, Util.fromUtf8Bytes(download.request.data))
}
Download.STATE_FAILED ->
notificationHelper.buildDownloadFailedNotification(R.drawable.ic_stat_download_failed, null, Util.fromUtf8Bytes(download.request.data))
else -> null
}
notification?.let { NotificationUtil.setNotification(this#ExoPlayerDownloadService, ++nextNotificationId, it) }
}
companion object {
private const val MAX_PARALLEL_DOWNLOADS = 3
private const val FOREGROUND_NOTIFICATION_ID = 2000
}
}
And to create the cache I use this:
SimpleCache(File(androidContext().cacheDir, CACHE_MEDIA_FOLDER), NoOpCacheEvictor(), get<DatabaseProvider>())
How can I avoid conflicts between buffering cache and downloaded files?
I had this issue also, and found the solution!
The downloading documentation states
The CacheDataSource.Factory should be configured as read-only to avoid downloading that content as well during playback.
To do this you must call setCacheWriteDataSinkFactory(null) on your CacheDataSource.Factory object.
This will prevent the stream from writing to the cache, allowing the downloader to write as expected.

Repeat function to call data from server every second

So if I press the Start button I want to repeat the function "fetchCarData()" every Second till I press the End Button. What's the best way to achieve this, is there any fancy Kotlin way of implementing such a thing?
class CarDataSourceImpl(private val carDataService: CarDataService) : CarDataSource {
//Live Data List that can be accessed only by this class
private val _loadedCarData = MutableLiveData<CarResponse>()
//actual Live Data List observed by the Views
override val loadedCarData: LiveData<CarResponse>
get() = _loadedCarData
//Fetch new Data and notify Observers via Live Data
override suspend fun fetchCarData() {
try {
val fetchedCarData = carDataService
.getData()
.await()
_loadedCarData.postValue(fetchedCarData)
} catch (e: NoConnectivityException) {
Log.e("Connectivity", "No Connection", e)
}
}
}
var timer=Timer()
private fun callfunctioneverysecond() {
val minTask=object : TimerTask() {
override fun run() {
//call function here
}
}
timer=Timer()
timer.schedule(minTask, 0L, 1000 * 1)
// timer.cancel()//call timer.cancel when click End button
}

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"`

Android ConnectionService incoming calls

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!

Categories

Resources