Počínaje Lollipopem je broadcast PHONE_STATE (viz. ACTION_PHONE_STATE_CHANGED) pro příchozí hovory posílán dvakrát. Důvody jsou skryté v temných zákoutích operačního systému a v hlavách Google developerů.
Ponoříme-li se hlouběji do probému a pokusíme se najít zdroj duplicity, můžeme poměrně rychle vystopovat její původ v TelephonyRegistry. Pro Android 5.0 by měl callstack vypadat nějak takto:
Pojďme se podívat co v této části kódu páni inženýři schovali. V kódu je plno FIXME, TODO a "to be unhidden" komentářů a je velmi špatně dokumentovaný. I přesto se dá odhadnout co bylo cílem. Celé to vypadá jako nedokončená snaha o podporu více SIM karet přímo v jádru systému, tedy bez nutnosti úprav od výrobců telefonů.
Možná jste si všimli, že BroadcastReciever dostane navíc jeden nedokumentovaný Extra parametr pojmenovaný "subscription". Jeho definici najdeme v PhoneConstants.SUBSCRIPTION_KEY, kterážto konstanta je pro vývojáře v rámci aktuálního SDK nedostupná. Budeme potřebovat zjistit, jakých hodnot může parametr "subscription" nabývat a co tyto hodnoty znamenají.
Když si projdeme callstack popsaný výše, je vidět že jeho hodnoty se nastavují v metodě DefaultPhoneNotifier.notifyPhoneState(), jako výstup z Phone.getSubId(). Dle mých pozorování můžeme dostat:
Bohužel SubscriptionManager.DEFAULT_SUB_ID také není v aktuálním SDK dostupné pro vývojáře (možná pomocí reflexe?). Co je ale horší, že jeho hodnota se mění napříč verzemi operačního systému:
Pěkný binec!
Konečně jsme u řešení. Naštěstí je poměrně přímočaré. Použijeme subscription Extra a vyfiltrujeme broadcast s výchozím subscription ID. Dle mého názoru nejlepší způsob jak toho docílit je:
public void onReceive(Context context, Intent intent) {
Object obj = intent.getExtras().get("subscription");
long subId;
if(obj == null) {
subId = Long.MIN_VALUE; // subscription not in extras
} else {
subId = Long.valueOf(obj.toString()); // subscription is long or int
}
if(subId < Integer.MAX_VALUE) {
// hurray, this is called only once on all operating system versions!
}
}
Musíme načíst subscription jako obecný objekt, protože je v extras uložený jako long for Android 5.0 a int pro Android 5.1.
Metoda uvedená výše stále funguje. Od Android 6 subscription extra není již připojeno k PHONE_STATE broadcastům. Existuje nový broadcast s SUBSCRIPTION_PHONE_STATE action kde je subscription extra připojen.