Kiedy próbuję utworzyć niestandardowy ekran dla połączeń przychodzących, próbuję programowo odebrać połączenie przychodzące. Używam następującego kodu, ale nie działa on w systemie Android 5.0.
// Simulate a press of the headset button to pick up the call
Intent buttonDown = new Intent(Intent.ACTION_MEDIA_BUTTON);
buttonDown.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonDown, "android.permission.CALL_PRIVILEGED");
// froyo and beyond trigger on buttonUp instead of buttonDown
Intent buttonUp = new Intent(Intent.ACTION_MEDIA_BUTTON);
buttonUp.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonUp, "android.permission.CALL_PRIVILEGED");
Odpowiedzi:
Zaktualizuj do Androida 8.0 Oreo
Mimo że pierwotnie pytanie dotyczyło obsługi Androida L, ludzie nadal wydają się trafiać na to pytanie i odpowiedź, dlatego warto opisać ulepszenia wprowadzone w systemie Android 8.0 Oreo. Metody kompatybilności wstecznej są nadal opisane poniżej.
Co się zmieniło?
Począwszy od Androida 8.0 Oreo , grupa uprawnień PHONE zawiera również uprawnienie ANSWER_PHONE_CALLS . Jak sugeruje nazwa uprawnienia, przytrzymanie go umożliwia aplikacji programowe akceptowanie połączeń przychodzących za pośrednictwem odpowiedniego wywołania interfejsu API bez hakowania systemu przy użyciu odbicia lub symulowania użytkownika.
Jak wykorzystujemy tę zmianę?
Jeśli obsługujesz starsze wersje systemu Android, sprawdź wersję systemu w czasie wykonywania , aby móc hermetyzować to nowe wywołanie interfejsu API, zachowując jednocześnie obsługę starszych wersji systemu Android. Powinieneś postępować zgodnie z żądaniem uprawnień w czasie wykonywania, aby uzyskać nowe uprawnienia w czasie wykonywania, co jest standardem w nowszych wersjach Androida.
Po uzyskaniu pozwolenia aplikacja musi po prostu wywołać metodę acceptRingingCall operatora TelecomManager . Podstawowe wywołanie wygląda więc następująco:
TelecomManager tm = (TelecomManager) mContext .getSystemService(Context.TELECOM_SERVICE); if (tm == null) { // whether you want to handle this is up to you really throw new NullPointerException("tm == null"); } tm.acceptRingingCall();
Metoda 1: TelephonyManager.answerRingingCall ()
Gdy masz nieograniczoną kontrolę nad urządzeniem.
Co to jest?
Istnieje TelephonyManager.answerRingingCall (), która jest ukrytą, wewnętrzną metodą. Działa jako pomost dla ITelephony.answerRingingCall (), który został omówiony w interwebach i wydaje się obiecujący na początku. To jest nie dostępny na 4.4.2_r1 jak została ona wprowadzona dopiero w popełnić 83da75d na Androidzie 4.4 KitKat ( linia 1537 na 4.4.3_r1 ), a później „ponownie” w popełnić f1e1e77 dla Lollipop ( linia 3138 na 5.0.0_r1 ) ze względu na sposób Drzewo Git zostało ustrukturyzowane. Oznacza to, że jeśli nie obsługujesz tylko urządzeń z Lollipopem, co jest prawdopodobnie złą decyzją ze względu na niewielki udział w rynku w tej chwili, nadal musisz zapewnić metody awaryjne, jeśli pójdziesz tą drogą.
Jak byśmy to wykorzystali?
Ponieważ omawiana metoda jest niewidoczna dla aplikacji SDK, należy użyć odbicia, aby dynamicznie zbadać i używać metody w czasie wykonywania. Jeśli nie jesteś zaznajomiony z refleksją, możesz szybko przeczytać Co to jest refleksja i dlaczego jest przydatna? . Możesz również zagłębić się w szczegóły w Trail: The Reflection API, jeśli chcesz to zrobić.
A jak to wygląda w kodzie?
// set the logging tag constant; you probably want to change this final String LOG_TAG = "TelephonyAnswer"; TelephonyManager tm = (TelephonyManager) mContext .getSystemService(Context.TELEPHONY_SERVICE); try { if (tm == null) { // this will be easier for debugging later on throw new NullPointerException("tm == null"); } // do reflection magic tm.getClass().getMethod("answerRingingCall").invoke(tm); } catch (Exception e) { // we catch it all as the following things could happen: // NoSuchMethodException, if the answerRingingCall() is missing // SecurityException, if the security manager is not happy // IllegalAccessException, if the method is not accessible // IllegalArgumentException, if the method expected other arguments // InvocationTargetException, if the method threw itself // NullPointerException, if something was a null value along the way // ExceptionInInitializerError, if initialization failed // something more crazy, if anything else breaks // TODO decide how to handle this state // you probably want to set some failure state/go to fallback Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e); }
To jest zbyt piękne by było prawdziwe!
Właściwie jest jeden drobny problem. Ta metoda powinna być w pełni funkcjonalna, ale menedżer bezpieczeństwa chce, aby dzwoniący zatrzymywali android.permission.MODIFY_PHONE_STATE . To uprawnienie dotyczy tylko częściowo udokumentowanych funkcji systemu, ponieważ osoby trzecie nie powinny go dotykać (jak widać w dokumentacji). Możesz spróbować dodać
<uses-permission>
do niego a, ale to nic nie da, ponieważ poziom ochrony dla tego uprawnienia to signature | system ( patrz wiersz 1201 core / AndroidManifest w wersji 5.0.0_r1 ).Możesz przeczytać numer 34785: Aktualizacja dokumentacji android: protectionLevel, która została utworzona w 2012 roku, aby zobaczyć, że brakuje nam szczegółów na temat określonej „składni potoku”, ale po eksperymentach wydaje się, że musi ona działać jako „AND”, co oznacza wszystkie określone flagi muszą być spełnione, aby zezwolenie zostało udzielone. Przy takim założeniu oznaczałoby to, że musisz mieć swoją aplikację:
Zainstalowana jako aplikacja systemowa.
Powinno to być w porządku i można to osiągnąć, prosząc użytkowników o zainstalowanie przy użyciu ZIP podczas odzyskiwania, na przykład podczas rootowania lub instalowania aplikacji Google na niestandardowych ROM-ach, które nie mają ich jeszcze w pakiecie.
Podpisany tym samym podpisem, co frameworki / podstawa, czyli system, czyli ROM.
Tu pojawiają się problemy. Aby to zrobić, musisz mieć ręce na klawiszach używanych do podpisywania struktur / bazy. Musiałbyś nie tylko uzyskać dostęp do kluczy Google do obrazów fabrycznych Nexusa, ale musiałbyś również uzyskać dostęp do kluczy wszystkich innych producentów OEM i programistów ROM. Nie wydaje się to prawdopodobne, więc możesz podpisać aplikację za pomocą kluczy systemowych, tworząc niestandardową pamięć ROM i prosząc użytkowników o przełączenie się na nią (co może być trudne) lub znajdując exploit, za pomocą którego można ominąć poziom ochrony uprawnień (co również może być trudne).
Ponadto wydaje się, że to zachowanie jest związane z problemem 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS już nie działa, który wykorzystuje ten sam poziom ochrony wraz z nieudokumentowaną flagą programistyczną.
Praca z TelephonyManager brzmi dobrze, ale nie będzie działać, jeśli nie uzyskasz odpowiedniego pozwolenia, co w praktyce nie jest takie łatwe.
A co z korzystaniem z TelephonyManager w inny sposób?
Niestety, wydaje się, że musisz przytrzymać android.permission.MODIFY_PHONE_STATE, aby użyć fajnych narzędzi, co z kolei oznacza, że będziesz mieć trudności z uzyskaniem dostępu do tych metod.
Metoda 2: wezwanie serwisu KOD SERWISOWY
Kiedy możesz przetestować, że kompilacja uruchomiona na urządzeniu będzie działać z określonym kodem.
Bez możliwości interakcji z TelephonyManager istnieje również możliwość interakcji z usługą za pośrednictwem
service
pliku wykonywalnego.Jak to działa?
Jest to dość proste, ale dokumentacji na temat tej trasy jest jeszcze mniej niż innych. Wiemy na pewno, że plik wykonywalny przyjmuje dwa argumenty - nazwę usługi i kod.
Nazwa usługi, której chcemy użyć, to telefon .
Można to zobaczyć, biegnąc
service list
.Kod chcemy użytku wydaje się być 6 , ale wydaje się być teraz 5 .
Wygląda na to, że został oparty na IBinder.FIRST_CALL_TRANSACTION + 5 dla wielu wersji teraz (od 1.5_r4 do 4.4.4_r1 ), ale podczas lokalnych testów kod 5 działał, aby odebrać połączenie przychodzące. Ponieważ Lollipo to ogromna aktualizacja dookoła, zrozumiałe jest, że tutaj również uległy zmianie elementy wewnętrzne.
Wynika to z polecenia
service call phone 5
.Jak używamy tego programowo?
Jawa
Poniższy kod to przybliżona implementacja, która ma służyć jako dowód słuszności koncepcji. Jeśli rzeczywiście chcesz skorzystać z tej metody, prawdopodobnie zechcesz zapoznać się ze wskazówkami dotyczącymi bezproblemowego korzystania z su i prawdopodobnie przejść na pełniejszą wersję libsuperuser firmy Chainfire .
try { Process proc = Runtime.getRuntime().exec("su"); DataOutputStream os = new DataOutputStream(proc.getOutputStream()); os.writeBytes("service call phone 5\n"); os.flush(); os.writeBytes("exit\n"); os.flush(); if (proc.waitFor() == 255) { // TODO handle being declined root access // 255 is the standard code for being declined root for SU } } catch (IOException e) { // TODO handle I/O going wrong // this probably means that the device isn't rooted } catch (InterruptedException e) { // don't swallow interruptions Thread.currentThread().interrupt(); }
Oczywisty
<!-- Inform the user we want them root accesses. --> <uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
Czy to naprawdę wymaga dostępu do roota?
Niestety, na to wygląda. Możesz spróbować użyć na nim Runtime.exec , ale nie udało mi się znaleźć szczęścia na tej trasie.
Jak stabilne jest to?
Cieszę się, że zapytałeś. Ze względu na brak udokumentowania może to występować w różnych wersjach, co ilustruje pozorna różnica w kodzie powyżej. Nazwa usługi prawdopodobnie powinna pozostać telefonem w różnych kompilacjach, ale z tego, co wiemy, wartość kodu może się zmieniać w wielu kompilacjach tej samej wersji (wewnętrzne modyfikacje przez, powiedzmy, skórkę producenta OEM), co z kolei łamie zastosowaną metodę. Warto zatem wspomnieć, że testy odbyły się na Nexusie 4 (mako / occam). Osobiście odradzałbym stosowanie tej metody, ale ponieważ nie jestem w stanie znaleźć bardziej stabilnej metody, uważam, że jest to najlepszy strzał.
Oryginalna metoda: intencje kodu klucza zestawu słuchawkowego
Na chwile, kiedy musisz się ustatkować.
Poniższy rozdział był pod silnym wpływem tej odpowiedzi przez Riley C .
Wydaje się, że metoda symulowanego zamiaru zestawu słuchawkowego, przedstawiona w pierwotnym pytaniu, jest nadawana tak, jak można by się spodziewać, ale nie wydaje się, aby spełniała ona cel, jakim jest odebranie połączenia. Chociaż wydaje się, że istnieje kod, który powinien obsługiwać te zamiary, po prostu nie dbamy o to, co musi oznaczać, że muszą istnieć jakieś nowe środki zaradcze przeciwko tej metodzie. Dziennik również nie pokazuje nic interesującego i osobiście nie wierzę, że przekopywanie się przez źródło Androida w tym celu będzie opłacalne tylko ze względu na możliwość wprowadzenia przez Google niewielkiej zmiany, która i tak łatwo złamie zastosowaną metodę.
Czy jest coś, co możemy teraz zrobić?
Zachowanie można konsekwentnie odtwarzać za pomocą wejściowego pliku wykonywalnego. Pobiera argument kodu klucza, dla którego po prostu przekazujemy KeyEvent.KEYCODE_HEADSETHOOK . Ta metoda nie wymaga nawet dostępu do konta roota, dzięki czemu nadaje się do powszechnych zastosowań w społeczeństwie, ale ma małą wadę - nie można określić zdarzenia naciśnięcia przycisku zestawu słuchawkowego jako wymagającego pozwolenia, co oznacza, że działa jak prawdziwy naciśnięcie przycisku i bulgotanie w całym łańcuchu, co z kolei oznacza, że musisz uważać, kiedy symulować naciśnięcie przycisku, ponieważ może to na przykład spowodować uruchomienie odtwarzacza muzyki, jeśli nikt inny o wyższym priorytecie nie jest gotowy do obsługi wydarzenie.
Kod?
new Thread(new Runnable() { @Override public void run() { try { Runtime.getRuntime().exec("input keyevent " + Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK)); } catch (IOException e) { // Runtime.exec(String) had an I/O problem, try to fall back String enforcedPerm = "android.permission.CALL_PRIVILEGED"; Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra( Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)); Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra( Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK)); mContext.sendOrderedBroadcast(btnDown, enforcedPerm); mContext.sendOrderedBroadcast(btnUp, enforcedPerm); } } }).start();
tl; dr
Istnieje ładny publiczny interfejs API dla systemu Android 8.0 Oreo i nowszych.
Nie ma publicznego interfejsu API przed wersją Android 8.0 Oreo. Wewnętrzne interfejsy API są zabronione lub po prostu bez dokumentacji. Należy postępować ostrożnie.
źródło
W pełni działające rozwiązanie oparte jest na kodzie @Valter Strods.
Aby to działało, musisz wyświetlić (niewidoczne) działanie na ekranie blokady, na którym wykonywany jest kod.
AndroidManifest.xml
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <activity android:name="com.mysms.android.lib.activity.AcceptCallActivity" android:launchMode="singleTop" android:excludeFromRecents="true" android:taskAffinity="" android:configChanges="orientation|keyboardHidden|screenSize" android:theme="@style/Mysms.Invisible"> </activity>
Aktywność akceptacji połączenia
package com.mysms.android.lib.activity; import android.app.Activity; import android.app.KeyguardManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.telephony.TelephonyManager; import android.view.KeyEvent; import android.view.WindowManager; import org.apache.log4j.Logger; import java.io.IOException; public class AcceptCallActivity extends Activity { private static Logger logger = Logger.getLogger(AcceptCallActivity.class); private static final String MANUFACTURER_HTC = "HTC"; private KeyguardManager keyguardManager; private AudioManager audioManager; private CallStateReceiver callStateReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); } @Override protected void onResume() { super.onResume(); registerCallStateReceiver(); updateWindowFlags(); acceptCall(); } @Override protected void onPause() { super.onPause(); if (callStateReceiver != null) { unregisterReceiver(callStateReceiver); callStateReceiver = null; } } private void registerCallStateReceiver() { callStateReceiver = new CallStateReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); registerReceiver(callStateReceiver, intentFilter); } private void updateWindowFlags() { if (keyguardManager.inKeyguardRestrictedInputMode()) { getWindow().addFlags( WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); } else { getWindow().clearFlags( WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); } } private void acceptCall() { // for HTC devices we need to broadcast a connected headset boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER) && !audioManager.isWiredHeadsetOn(); if (broadcastConnected) { broadcastHeadsetConnected(false); } try { try { logger.debug("execute input keycode headset hook"); Runtime.getRuntime().exec("input keyevent " + Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK)); } catch (IOException e) { // Runtime.exec(String) had an I/O problem, try to fall back logger.debug("send keycode headset hook intents"); String enforcedPerm = "android.permission.CALL_PRIVILEGED"; Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra( Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)); Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra( Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK)); sendOrderedBroadcast(btnDown, enforcedPerm); sendOrderedBroadcast(btnUp, enforcedPerm); } } finally { if (broadcastConnected) { broadcastHeadsetConnected(false); } } } private void broadcastHeadsetConnected(boolean connected) { Intent i = new Intent(Intent.ACTION_HEADSET_PLUG); i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); i.putExtra("state", connected ? 1 : 0); i.putExtra("name", "mysms"); try { sendOrderedBroadcast(i, null); } catch (Exception e) { } } private class CallStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { finish(); } } }
Styl
<style name="Mysms.Invisible"> <item name="android:windowFrame">@null</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowAnimationStyle">@null</item> </style>
Wreszcie wezwij magię!
Intent intent = new Intent(context, AcceptCallActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); context.startActivity(intent);
źródło
Poniżej przedstawiono alternatywne podejście, które zadziałało dla mnie. Wysyła kluczowe zdarzenie do serwera telekomunikacyjnego bezpośrednio za pomocą interfejsu API MediaController. Wymaga to, aby aplikacja miała uprawnienia BIND_NOTIFICATION_LISTENER_SERVICE i otrzymała wyraźne zezwolenie na dostęp do powiadomień od użytkownika:
@TargetApi(Build.VERSION_CODES.LOLLIPOP) void sendHeadsetHookLollipop() { MediaSessionManager mediaSessionManager = (MediaSessionManager) getApplicationContext().getSystemService(Context.MEDIA_SESSION_SERVICE); try { List<MediaController> mediaControllerList = mediaSessionManager.getActiveSessions (new ComponentName(getApplicationContext(), NotificationReceiverService.class)); for (MediaController m : mediaControllerList) { if ("com.android.server.telecom".equals(m.getPackageName())) { m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK)); log.info("HEADSETHOOK sent to telecom server"); break; } } } catch (SecurityException e) { log.error("Permission error. Access to notification not granted to the app."); } }
NotificationReceiverService.class
w powyższym kodzie może być po prostu pusta klasa.import android.service.notification.NotificationListenerService; public class NotificationReceiverService extends NotificationListenerService{ public NotificationReceiverService() { } }
Z odpowiednią sekcją w manifeście:
<service android:name=".NotificationReceiverService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter>
Ponieważ cel zdarzenia jest wyraźny, powinno to prawdopodobnie uniknąć jakiegokolwiek efektu ubocznego uruchamiania odtwarzacza multimedialnego.
Uwaga: serwer telekomunikacyjny może nie być aktywny natychmiast po zdarzeniu dzwonienia. Aby to działało niezawodnie, może być przydatne, aby aplikacja zaimplementowała MediaSessionManager.OnActiveSessionsChangedListener w celu monitorowania, kiedy serwer telekomunikacyjny staje się aktywny, przed wysłaniem zdarzenia.
Aktualizacja:
W Androidzie O trzeba symulować
ACTION_DOWN
wcześniejACTION_UP
, w przeciwnym razie powyższe nie daje efektu. tzn. potrzebne są:m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)); m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
Ale ponieważ oficjalne wezwanie do odebrania połączenia jest dostępne od Androida O (patrz najlepsza odpowiedź), może nie być już potrzeby tego hackowania, chyba że utkniesz ze starym poziomem kompilacji API przed Androidem O.
źródło
Aby nieco rozwinąć odpowiedź @Muzikant i nieco ją zmodyfikować, aby działała nieco czysto na moim urządzeniu, wypróbuj
input keyevent 79
stałą dla KeyEvent.KEYCODE_HEADSETHOOK . Z grubsza:new Thread(new Runnable() { @Override public void run() { try { Runtime.getRuntime().exec( "input keyevent " + KeyEvent.KEYCODE_HEADSETHOOK ); } catch (Throwable t) { // do something proper here. } } }).start();
Wybaczcie dość złe konwencje kodowania, nie jestem zbyt dobrze zorientowany w wywołaniach Runtime.exec (). Zwróć uwagę, że moje urządzenie nie jest zrootowane ani nie żądam uprawnień roota.
Problem z tym podejściem polega na tym, że działa tylko w określonych warunkach (jak dla mnie). Oznacza to, że jeśli uruchomię powyższy wątek z opcji menu wybranej przez użytkownika podczas dzwonienia połączenia, połączenie zostanie odebrane poprawnie. Jeśli uruchomię go z odbiornika, który monitoruje stan połączenia przychodzącego, zostanie całkowicie zignorowany.
Tak więc na moim Nexusie 5 działa dobrze w przypadku odbierania przez użytkownika i powinien pasować do niestandardowego ekranu połączenia. Po prostu nie będzie działać w przypadku żadnych zautomatyzowanych aplikacji sterujących połączeniami.
Warto również zwrócić uwagę na wszystkie możliwe zastrzeżenia, w tym również to, że prawdopodobnie przestanie działać w jednej lub dwóch aktualizacji.
źródło
input keyevent 79
działa dobrze na Sony Xperia 5.0. Działa podczas dzwonienia z aktywności lub z odbiornika.za pomocą poleceń adb Jak odebrać połączenie przez adb
Pamiętaj, że Android to Linux z ogromną maszyną JVM z przodu. Możesz pobrać aplikację wiersza poleceń i zrootować telefon, a teraz masz zwykły komputer z systemem Linux i wiersz poleceń, który wykonuje wszystkie normalne czynności. Uruchamiaj skrypty, możesz nawet ssh do niego (sztuczka OpenVPN)
źródło
Dzięki @notz, odpowiedź działa dla mnie na Lolillop. Aby kod działał ze starym zestawem SDK systemu Android, możesz wykonać ten kod:
if (Build.VERSION.SDK_INT >= 21) { Intent answerCalintent = new Intent(context, AcceptCallActivity.class); answerCalintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); context.startActivity(answerCalintent); } else { if (telephonyService != null) { try { telephonyService.answerRingingCall(); } catch (Exception e) { answerPhoneHeadsethook(); } } }
źródło
Jak włączyć głośnik po automatycznym odebraniu połączeń.
Powyższy problem rozwiązałem za pomocą setSpeakerphoneOn. Myślę, że warto go tutaj opublikować, ponieważ przypadek użycia automatycznego odbierania połączenia telefonicznego często wymagałby również użycia zestawu głośnomówiącego. Jeszcze raz dziękuję wszystkim w tym wątku za wspaniałą pracę.
To działa dla mnie na Androidzie 5.1.1 na moim Nexusie 4 bez ROOT. ;)
Wymagane pozwolenie:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
Kod Java:
// this means the phone has answered if(state==TelephonyManager.CALL_STATE_OFFHOOK) { // try and turn on speaker phone final Handler mHandler = new Handler(); mHandler.postDelayed(new Runnable() { @Override public void run() { AudioManager audioManager = (AudioManager) localContext.getSystemService(Context.AUDIO_SERVICE); // this doesnt work without android.permission.MODIFY_PHONE_STATE // audioManager.setMode(AudioManager.MODE_IN_CALL); // weirdly this works audioManager.setMode(AudioManager.MODE_NORMAL); // this is important audioManager.setSpeakerphoneOn(true); // note the phone interface won't show speaker phone is enabled // but the phone speaker will be on // remember to turn it back off when your done ;) } }, 500); // half a second delay is important or it might fail }
źródło
Uruchom następujące polecenie jako root:
input keyevent 5
Więcej szczegółów na temat symulacji kluczowych wydarzeń tutaj .
Możesz użyć tej klasy bazowej, którą utworzyłem, do uruchamiania poleceń jako root z Twojej aplikacji.
źródło
przetestuj to: najpierw dodaj uprawnienia, a następnie użyj funkcji killCall (), aby się rozłączyć, użyj funkcji answerCall (), aby odebrać połączenie
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission> public void killCall() { try { TelephonyManager telephonyManager = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE); Class classTelephony = Class.forName(telephonyManager.getClass().getName()); Method methodGetITelephony = classTelephony.getDeclaredMethod("getITelephony"); methodGetITelephony.setAccessible(true); Object telephonyInterface = methodGetITelephony.invoke(telephonyManager); Class telephonyInterfaceClass = Class.forName(telephonyInterface.getClass().getName()); Method methodEndCall = telephonyInterfaceClass.getDeclaredMethod("endCall"); methodEndCall.invoke(telephonyInterface); } catch (Exception ex) { Log.d(TAG, "PhoneStateReceiver **" + ex.toString()); } } public void answerCall() { try { Runtime.getRuntime().exec("input keyevent " + Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK)); } catch (IOException e) { answerRingingCallWithIntent(); } } public void answerRingingCallWithIntent() { try { Intent localIntent1 = new Intent(Intent.ACTION_HEADSET_PLUG); localIntent1.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); localIntent1.putExtra("state", 1); localIntent1.putExtra("microphone", 1); localIntent1.putExtra("name", "Headset"); getContext().sendOrderedBroadcast(localIntent1, "android.permission.CALL_PRIVILEGED"); Intent localIntent2 = new Intent(Intent.ACTION_MEDIA_BUTTON); KeyEvent localKeyEvent1 = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK); localIntent2.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent1); getContext().sendOrderedBroadcast(localIntent2, "android.permission.CALL_PRIVILEGED"); Intent localIntent3 = new Intent(Intent.ACTION_MEDIA_BUTTON); KeyEvent localKeyEvent2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK); localIntent3.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent2); getContext().sendOrderedBroadcast(localIntent3, "android.permission.CALL_PRIVILEGED"); Intent localIntent4 = new Intent(Intent.ACTION_HEADSET_PLUG); localIntent4.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); localIntent4.putExtra("state", 0); localIntent4.putExtra("microphone", 1); localIntent4.putExtra("name", "Headset"); getContext().sendOrderedBroadcast(localIntent4, "android.permission.CALL_PRIVILEGED"); } catch (Exception e2) { e2.printStackTrace(); } }
źródło
Do Twojej wiadomości, jeśli interesuje Cię, jak zakończyć trwające połączenie na Androidzie O, Valter zadziała,
Method 1: TelephonyManager.answerRingingCall()
jeśli zmienisz wywoływaną metodęendCall
.Wymaga tylko
android.permission.CALL_PHONE
pozwolenia.Oto kod:
// set the logging tag constant; you probably want to change this final String LOG_TAG = "TelephonyAnswer"; public void endCall() { TelephonyManager tm = (TelephonyManager) mContext .getSystemService(Context.TELEPHONY_SERVICE); try { if (tm == null) { // this will be easier for debugging later on throw new NullPointerException("tm == null"); } // do reflection magic tm.getClass().getMethod("endCall").invoke(tm); } catch (Exception e) { // we catch it all as the following things could happen: // NoSuchMethodException, if the answerRingingCall() is missing // SecurityException, if the security manager is not happy // IllegalAccessException, if the method is not accessible // IllegalArgumentException, if the method expected other arguments // InvocationTargetException, if the method threw itself // NullPointerException, if something was a null value along the way // ExceptionInInitializerError, if initialization failed // something more crazy, if anything else breaks // TODO decide how to handle this state // you probably want to set some failure state/go to fallback Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e); } }
źródło