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) {
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?
final String LOG_TAG = "TelephonyAnswer";
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
try {
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
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) {
}
} catch (IOException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Oczywisty
<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) {
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.