📜 ⬆️ ⬇️

First impressions of the development under Android - we write handsfree

Most recently I got an androphone (LG Optimus) and decided to try my hand at writing software for it. Having read about the platform device, at first I was very happy for its simplicity, convenience and consistency. But in practice, everything turned out to be not so rosy ...

As a sample, the pen took up an application that would automatically answer incoming calls when the headset is connected. It is strange, of course, that such a simple function is not in the out-of-box system. And in the market there was only one application that could do it, and not very reliable. Let's try to correct this misunderstanding.

At first glance, the application should be very simple:
  1. In the manifest, we hang the receiver on messages about changing the status of the line ( TelephonyManager.ACTION_PHONE_STATE_CHANGED ) and monitor incoming calls.
  2. When a call arrives, we check if the headset is connected. First of all, I was interested in bluetooth, but it would also be good to track a wired headset.
  3. If the headset is connected, we tell the phone to answer the call.

With the first paragraph there were no special problems. But the other two were not so trivial.
')

Answer call


I'll start with the third item. In Android, there is no API for answering an incoming call. If in versions 1.x it was still possible to reach some undocumented classes and methods that allowed this, then in my 2.2 all these holes were securely closed. Pretty googling found the same way: to portray the press of a button on a headset ( Intent.ACTION_MEDIA_BUTTON , KeyEvent.KEYCODE_HEADSETHOOK ). True, there are some subtleties. This event must be sent via sendOrderedBroadcast (not sendBroadcast ). Otherwise, it will get to all the receivers, and among them there may be, for example, an audio player, which will start up cheerfully if it finds something to play. Interestingly, the player runs in the emulator even when using ordered broadcast, although everything works as it should on the phone. Another difference between the emulator and the hardware: in the first one, it’s enough to send a message about the release of the button (by the way, the player starts only by the press message). In the phone, it is necessary to generate both pressing and releasing. They say that in 1.x it was possible to get to the function injectKeyEvent (or some similar one) and that would be more correct. But in 2.2, the protection of this function was repaired and it is not available.

Headset Status


Now the most difficult thing is point 2. Check if the headset is connected. The problem is the same - in Android there is no ready-made API to find out. And what's even more interesting, the actions for wired and wireless headsets will be noticeably different.

In principle, for both headsets, you can track the connection / disconnection messages, which is where I started. For wired, this is Intent.ACTION_HEADSET_PLUG (the headset status is transmitted as an extra parameter), for wireless - a pair of BluetoothDevice.ACTION_ACL_CONNECTED and BluetoothDevice.ACTION_ACL_DISCONNECTED messages.

The first thing you want to do is to register the receivers of these messages right in the manifest so that you do not miss anything. With a wireless headset, this number passes, but for some reason ACTION_HEADSET_PLUG is sent with the FLAG_RECEIVER_Riced_ONLY flag, that is, receivers do not receive it from the manifest (the documentation says nothing about it, so it’s very similar to an error). It is necessary to register them in the code. This means that you have to start a service that will programmatically register the desired receiver and will remain running all the time. Here, however, there is also a rake: such a service can be killed by the system at any time (which is what happened in my testing process), as a result of which we can skip some messages. Apparently, a similar approach was used in the only application from the market, which I mentioned at the beginning of the article - it was not always the right time to determine the connection of the headset. I had to run it by hand.

An exit was found. It comes to the aid of the fact that the message ACTION_HEADSET_PLUG is sticky. That is, the latest state of the headset is reported to all newly registered listeners. It turns out that the state of the wireless headset can be checked at any time by calling the registerReceiver (null, headsetPlugFilter) and checking the returned Intent. This makes me happy. Service is not needed, point 2 for a wired headset is implemented.

Go to the wireless headset. There will definitely have to track the connection / disconnection messages - the current state of the headset at any time can not be checked. But you can use the manifest for registering the receiver. Another inconvenience is that the current state should be stored somewhere, and the usual variable cannot be used for this, since no code is executed between calls. I chose for this purpose a hidden parameter in the application settings. Hidden in the sense that it is not displayed in the settings window (I will not view this window - everything is simple there).

Call waiting


I almost forgot about another important point. If a person is already on the phone, then most likely he does not need new incoming calls to be answered automatically. I made for this a separate option in the settings. Accordingly, before answering, we must check whether we had an active conversation at the time of the call. Again, the API does not provide any means for this. All that is available is a working receiver for calls. We can track the change in the status of the phone in offhook or idle. But using this application to store this state (as for the state of a wireless headset) can be quite expensive. Therefore, again, we need a working service that will store the state of offhook in memory. And in order to reduce the probability of a system killing a service at the wrong time, you can temporarily run it through startForeground. When calls are completed, the service can be suspended by calling stopForeground.

Impressions


As a result, the plans were implemented, although for this we had to write more code than planned, and the result does not look very neat. Honestly, I still don’t understand why the API doesn’t have any ready-made functions like isHeadsetConnected and answerCall that would greatly simplify life.

By the way, in my original plan there was another option to announce the caller using TextToSpeech . But it turned out that it is impossible to redirect the sound from the TTS to the wireless headset at the time of the call (if someone knows a way, share it), and there is no special meaning in talking to the phone in your pocket. I did not bother - I just threw out this function entirely. Although, of course, a little pity.

In general, the convenience of the platform has so far proved only a theory. In practice, you constantly stand against strange and illogical restrictions, and it is not always possible to bypass them. But the idea is good. Let's see what will happen next. I hope that they will bring the API to the mind and use it will be really nice.

Source: https://habr.com/ru/post/112295/


All Articles