Android ConnectivityService - "onUnhold" not called - android

I'm trying to use self-managed ConnectivityService to manage calls in-app - https://developer.android.com/guide/topics/connectivity/telecom/selfManaged
It is poorly documented and there are hardly any examples on the web, however, I've managed to make it work quite well except for one scenario - getting "onUnhold" callback in one case.
So, we are in a self-managed VoIP call on my phone (A), then we receive a regular GSM call from the second phone (B), and when I answer this on phone A, I get "onHold()" callback in my Connection object where I can put my VoIP call on hold.
Then, when I end this GSM call from phone A, I got "onUnhold()" callback from where I can set my VoIP call to be active again, BUT when the call will be ended by phone B, there is no callback whatsoever, nothing in my Connection object or even in my ConnectionService.
There is even an issue in the Android tracker that describes the exact same thing and it's really well documented, with examples on GitHub and all: https://issuetracker.google.com/issues/223757078 but Google claims that this is an expected behavior.
How can I "unHold" my VoIP connection when the remote user will end the call then? I've tried to listen PhoneStateListener/TelephonyCallback, but there I get states also from my own call and I can't distinguish if the state was from my or GSM call...

Related

System managed connection - place outbound call without treating it as a sim call

I'm working on implementing the calling functionality for a voip app and struggling with making an outbound call with a system managed connection service **
The part I'm struggling with is as follows:
val extras = Bundle()
extras.putString(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle.id) // I assume I need this, otherwise how will it know which connection service to invoke?
telecomManager.placeCall(Uri.parse("tel:+4412345"), extras)
what I'd like to happen is that the default dialler is invoked and then because I told it my own phone account handle it will then callback to my own implementation of connection service via: onCreateOutgoingConnection where I can then rig up the voip call and maintain the connection state myself.
What actually happens however is that the default dialler is invoked and then it just attempts to ring it as it would with a normal sim call (so I can't broker the connection myself via webrtc)
My setup for the phone account is as follows:
val extras = Bundle()
val builder = PhoneAccount.builder(phoneAccountHandle, label)
builder.setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER) // because I want to manage my own call connections but use default UI
builder.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) // because I want to make calls
// builder.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) // commented out because otherwise I have to be self managed for inbound
builder.setExtras(extras)
val phoneAccount = builder.build()
telecomManager.registerPhoneAccount(phoneAccount)
For what it's worth I've figured out the inbound side of things and that's working pretty well (I receive a firebase message, I tell telecom there is an inbound call, it then calls back to onCreateIncomingConnection, it then invokes the default in call UI, and I then crack on with completing the webrtc connection between both parties)
I'm fairly new to android development so I'm hoping that I've missed something "obvious" as opposed to what I'm attempting being impossible. Can anyone give me any help on this?
Note: I'm aware of the documentation at https://developer.android.com/guide/topics/connectivity/telecom/selfManaged but that is for a self managed connection, (also, I did initially follow this when attempting a self-managed solution but then gave up (see below). In my case I don't really want to show my own UI (because that would break symmetry with the inbound calls - and then I have the pain of trying to interoperate with other UIs correctly))
** The reason I've chosen system managed over self managed is that I have to play nicely with other sim calls and I had a world of pain trying to handle the case that my app call follows an ongoing sim call (seemed that nothing I did to my own connection did anything to cause a change in the other connection (like hold the call) so I wound up with two calls going on at the same time).
Update: helps if I add the right kind of extra! it should be parcelable and provide the phoneAccountHandle ... managed to get a bit further and may answer my own question if I get this working ...
My mistake here (left in original post for posterity) is that instead of adding the phone account handle id as a string extra when placing the call, I should have supplied the actual phone account handle as a parcelable extra. This now brings up the default UI, calls out to my connection service and then does nothing (which is fine because I haven't rigged up the webrtc yet)

Confused about Android Telecom subsystem order of events on incoming calls

In the following link:
https://developer.android.com/guide/topics/connectivity/telecom/selfManaged
I see the quote
Use the addNewIncomingCall(PhoneAccountHandle, Bundle) method to
inform the telecom subsystem about the new incoming call.
This makes no sense to me. How is my app notifying the Telecom subsystem about incoming calls? Isn't it the other way? It seems like a new call enters the device and the Telecom framework routes it to an app to handle the logic, e.g. my application. How would my app ever know about an incoming call before the Telecom system? What mechanism would notify my application of an incoming call? What service or activity would even call the addNewIncomingCall method?
Now, my intuition says I need to use a receiver to listen for an incoming call by detecting a change in the phone state. However, the following link says that is not the proper way.
https://developer.android.com/guide/topics/connectivity/telecom
That instead you should be using the inCallService and connectionService, which are services I am currently using.
Traditionally, standalone calling apps have relied on listening to the
phone state to try to determine when other calls are taking place.
This is problematic as the phone state does not take into account
other calling apps the user may have installed. Using a self-managed
ConnectionService helps you ensure that your app will interoperate not
only with native telephony calling on the device, but also other
standalone calling apps implementing this API. The Self-Managed
ConnectionService API also manages audio routing and focus for you.
I'm just not sure how they fit into the bigger picture of receiving incoming calls. All the documentation says you don't need to implement the inCallService if you don't want your own custom user interface. But I don't see how the connectionService gets a reference to the Call object otherwise.
Does anybody have a good flow chart or other graphical representation of how the Telecom subsystem functions at a high level? How the callbacks work in the framework?
Ideally I want to use the default dialer interface, dial pads, everything but be notified first of an incoming call and programmatically silently reject the call or pass it through to the default dialer.
Thanks!

Android app architecture with BLE

I am developing an android app with BLE API from android. My app needs to connect to a BLE device, and remain connected as long as it is in range and turned on. I need to read data from it, and write data to it.
I am trying to follow the MVP architecture pattern, not strictly since activities are the starting point. But anyway, I wanted to know where should I put the interaction with Bluetooth? I am searching for answers for the following questions. I have searched StackOverflow, but couldn't find what I was looking for.
Should it be in a service bounded to the UI just like in googlesample ble app ? But, I think that would break the whole mvp architecture.
Should it be a bounded service at all ? If no, what would be the best way to implement the service? In my mind, if it's not bounded to the view, and there is a callback from the background service to display something on the UI, there is a possibility of undefined behavior.
Who should initiate the Bluetooth interaction ? The application class or some activity ?
I am looking for mainly architectural guidance, and best way to go about developing this app.
Since you have the requirement that the Bluetooth connection should keep working in the background, you should have a Foreground Service somewhere running in your app process. This will make sure your app process will be kept alive, but requires an icon to be displayed in the phone/tablet's top bar.
Whether you actually put your BLE code in this service class or not doesn't matter for the functionality.
There are of course many ways to achieve good architecture but here is my approach.
My approach would be to have a singleton class that handles all your BLE scanning, connections and GATT interactions (from now on called Manager). Since some BLE operations needs an Android Context, a good way is to use the Application context as context. Either follow Static way to get 'Context' on Android? to be able to fetch that context at any time or subclass the Application class and from its onCreate call some initialization method in your Manager and pass the context. Now you can keep all BLE functionality completely separated from Android Service/Activity/Application stuff. I don't really see the point in using bounded services etc. as long as you keep everything in the same process.
To implement a scan functionality, you can have a method in your Manager that creates Scanner objects. Write the Scanner class as a wrapper to Android's BLE scanner and expose methods to start/stop scan. When you create a Scanner that method should also take an interface as argument used for callbacks (device reports and errors). This class can now be used in for example an Activity. Just make sure that the scanner gets stopped in the Activity's onStop method to avoid leakage of objects.
There are several reasons for having a wrapped custom Scanner object instead of using Android's BLE scan API directly in the Activity. First you can apply the appropriate filtering and processing of advertising packets so it handles your type of peripheral and can show high level parameters (decoded from advertising data) in your custom advertising report callback. The manager should also listen to broadcasts when Bluetooth gets started/stopped/restarted and keep track of all started Scanners so the Scanners are restarted seamlessly when Bluetooth restarts (if you want this functionality). You may also want to keep track of timestamps of all scan starts/stops so you can workaround the new restrictions in Nougat that limits it to 5 scans per 30 seconds.
Use a similar approach when you want to connect to your peripherals. You can for example let the Manager create Device objects which have methods to start/stop the connection and have a callback interface to report events. For each supported feature (for example read some remote value) you should expose a method which starts the requests and have a callback which is called when the result arrives. Then your Manager and Device class takes care of the GATT stuff (including enqueuing all your GATT requests so you only have one outstanding GATT operation at a time). Just make sure you can always abort or ignore the result when you don't want the result, for example if an Activity's onStop or onDestroy method is called.
Since you probably want to reconnect automatically in case the device gets disconnected, you should use the autoConnect flag and set it to true when establishing the connection, which assures this. Again, the Manager should keep track of all active Device objects and automatically recreate the BluetoothGatt object when Bluetooth is restarted.
To be able to display different kind of UI stuff, like for example automatically show a warning message in your Activity when Bluetooth is turned off and remove it when Bluetooth is turned on, you should be able to register Listeners to your Manager. Have a method in your Manager for registering/unregistering a listener (which is really just a Callback) object, keep track of all the listeners and when Bluetooth state change happens, call all listeners. Then in your Activity's onStart you register a listener and in onStop you unregister it. You can have a similar approach for your Device's BLE notifications, where applicable.
What's left is how you deal with different Threads. As you might know most BLE callbacks from Android's API happen on Binder threads, so you may not update the UI from them. If you otherwise in your app don't use anything other than the main thread, you can for example post all invocations of callbacks in the Manager to the main thread, or maybe move to the main thread directly when the callback from Android's BLE stack arrives (but then be aware of things like https://bugs.chromium.org/p/chromium/issues/detail?id=647673). Just make sure you never touch the same variables from different threads.
Also if you target API 23 or higher you need UI code to let the user give permission to Location to be able to start scan. I suggest you implement this in your UI code and not in the Manager, or implement some "wrapper" or helper method in the Manager to do this.
RxCentralBle provides a paradigm for use in an app. The library design clearly shows the structure of the library. In short, RxCentralBle provides reactive interfaces for the primary Bluetooth LE actions:
BluetoothDetector - detect phone Bluetooth State
Scanner - scan for peripherals
ConnectionManager - connect to a peripheral
PeripheralManager - queue operations to communicate with a peripheral
It's recommended to subscribe to these interfaces on a background thread and ensure resources and subscriptions live at the application scope i.e. member variables of your Application class. As long as your Application is running, all Bluetooth LE resources will remain alive and active.
Check out RxCentralBle's Wiki and sample app to learn more.

Outgoing Call End Classification

I just want to know if when an outgoing call ends, we can differentiate and know if it was due to normal hangup (normal call end), or call end due to disconnection (loss of signal, network congestion, or any reason from the carrier)
There are two main interfaces to call events depending on whether you are using the standard call app provided or are implementing your own user interface to manage calls.
If you are using the standard call service then you limited to whatever callbacks the Android telephony manager propvides:
https://developer.android.com/reference/android/telephony/TelephonyManager.html
This is a fairly limited set of events and I don't think will give you what you are looking for. It will allow you detect that the phone has gone from off hook to idle state, but not why.
If you are implementing the user interface yourself for call control, then you have access to a rusher set of events via the InCallService:
https://developer.android.com/reference/android/telecom/InCallService.html
This provides more information in callbacks but it still does not give a 'disconnect reason' for calls, AFAIK.
Note also that to use the InCallService your app must be registered as the default phone app.

How do I know who ended the call in Android - User or Remote User?

I want to specifically know who ended the call. I have setup a broadcast receiver for
"android.intent.action.PHONE_STATE"
When I detect a transition from Off hook to idle, I know the call has ended. But how do I know who ended the call?
Thanks a ton!
I'm afraid there's presently no way to determine if the user pressed "end call" or if the other end (or ends, in a group call) terminated. The only workaround I can suggest is monitoring the other states to observe if the phone state ringing was encountered. In such a case, you could assume the user is making the phone call as opposed to receiving it.
Bear in mind that there are other problems related to PHONE_STATE, such as handling multiple calls simultaneously.
In retrospect, I'm not entirely sure what you mean with "who". As for other apps ending the call: there is no official API to end phone calls; only through reflection can an app invoke the TelephonyService's endCall() function. Here, too, it is not possible to determine if the call was terminated through user interaction or not.

Categories

Resources