Android 5.0'da (Lollipop) gelen aramalar programlı olarak nasıl yanıtlanabilir?


89

Gelen aramalar için özel bir ekran oluşturmaya çalışırken, gelen bir aramayı programlı olarak cevaplamaya çalışıyorum. Aşağıdaki kodu kullanıyorum ancak Android 5.0'da çalışmıyor.

// 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");

oh adamım, neden bununla karşılaşıyorsun, sadece kaydır Man! bana daha kolay görünüyor \ m /
nobalG

Android kullanıcıları için özel bir gelen arama ekranı oluşturuyorum.
maveroid

2
Kimse? Ben de bununla ilgileniyorum! Pek çok şey denedim ama işe yaramadı: /
Arthur

1
@nobalG programlı bir şekilde diyor
dsharew

1
@maveroid, Android 5.0 için bir geçici çözüm buldunuz mu?
arthursfreire

Yanıtlar:


156

Android 8.0 Oreo ile güncelleme

Soru başlangıçta Android L desteği için sorulmuş olsa da, insanlar hala bu soruyu ve cevabı buluyor gibi görünüyor, bu nedenle Android 8.0 Oreo'da sunulan iyileştirmeleri açıklamaya değer. Geriye dönük uyumlu yöntemler hala aşağıda açıklanmaktadır.

Ne değişti?

Android 8.0 Oreo'dan başlayarak , TELEFON izin grubu ayrıca ANSWER_PHONE_CALLS iznini de içerir . İznin adından da anlaşılacağı gibi, bunu tutmak, uygulamanızın, kullanıcıyı yansıtma veya simüle etme yoluyla sistemde herhangi bir hackleme olmaksızın, uygun bir API çağrısı aracılığıyla gelen çağrıları programlı olarak kabul etmesine olanak tanır.

Bu değişikliği nasıl kullanacağız?

Daha eski Android sürümlerini destekliyorsanız, bu yeni API çağrısını bu eski Android sürümleri için desteği sürdürürken kapsülleyebilmeniz için çalışma zamanında sistem sürümünü kontrol etmelisiniz . Daha yeni Android sürümlerinde standart olduğu gibi, çalışma zamanında bu yeni izni almak için çalışma zamanında izin talep etmeyi takip etmelisiniz .

İzni aldıktan sonra, uygulamanız sadece TelecomManager'ın acceptRingingCall yöntemini çağırmalıdır . Bu durumda temel bir çağrı aşağıdaki gibi görünür:

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();

Yöntem 1: TelephonyManager.answerRingingCall ()

Cihaz üzerinde sınırsız kontrole sahip olduğunuz zamanlar için.

Bu nedir?

Gizli, dahili bir yöntem olan TelephonyManager.answerRingingCall () vardır. Interweb'lerde tartışılan ve başlangıçta umut verici görünen ITelephony.answerRingingCall () için bir köprü görevi görür. Öyle değil geçerli 4.4.2_r1 sadece içinde taahhüt tanıtıldı olarak 83da75d Android 4.4 için KitKat ( 4.4.3_r1 üzerine çizgi 1537 taahhüt olarak) ve daha sonra "yeniden" f1e1e77 Lollipop (için 5.0.0_r1 üzerine hat 3138 ) nedeniyle nasıl Git ağacı yapılandırıldı. Bu, şu an itibariyle küçük pazar payına dayalı olarak muhtemelen kötü bir karar olan Lollipop'lu cihazları desteklemediğiniz sürece, bu rotaya giderken yine de geri dönüş yöntemleri sağlamanız gerektiği anlamına gelir.

Bunu nasıl kullanacağız?

Söz konusu yöntem, SDK uygulamalarının kullanımından gizlendiğinden, çalışma zamanı sırasında yöntemi dinamik olarak incelemek ve kullanmak için yansımayı kullanmanız gerekir . Düşünme konusuna aşina değilseniz, hızlıca Yansıma nedir ve neden yararlıdır? Okuyabilirsiniz. . Ayrıca, eğer ilgileniyorsanız , Trail: The Reflection API'de ayrıntıları daha derinlemesine inceleyebilirsiniz .

Ve bu kodda nasıl görünüyor?

// 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);
}

Bu gerçek olamayacak kadar iyi!

Aslında ufak bir sorun var. Bu yöntem tamamen işlevsel olmalıdır, ancak güvenlik yöneticisi arayanların android.permission.MODIFY_PHONE_STATE yapmasını ister . Bu izin, sistemin yalnızca kısmen belgelenmiş özellikleri kapsamındadır, çünkü 3. kişilerin dokunması beklenmez (bunun için belgelerden de görebileceğiniz gibi). Bunun <uses-permission>için bir eklemeyi deneyebilirsiniz , ancak bu bir işe yaramayacaktır çünkü bu iznin koruma düzeyi imza | sistemdir ( 5.0.0_r1'deki core / AndroidManifest satır 1201'e bakın ).

Belirli "boru sözdizimi" ile ilgili ayrıntıları kaçırdığımızı görmek için 2012'de oluşturulan Sürüm 34785: Güncelleme android: koruma Düzeyi belgelerini okuyabilirsiniz , ancak etrafta yapılan deneylerden, tüm bunlar anlamına gelen bir "VE" İznin verilebilmesi için belirtilen bayrakların yerine getirilmesi gerekir. Bu varsayım altında çalışmak, başvurunuzu almanız gerektiği anlamına gelir:

  1. Sistem uygulaması olarak kuruldu.

    Bu iyi olmalı ve kullanıcılardan kurtarmada bir ZIP kullanarak yüklemelerini isteyerek gerçekleştirilebilir, örneğin, Google uygulamalarını önceden paketlenmemiş özel ROM'lara köklenirken veya yüklerken.

  2. ROM olarak da bilinen sistem ile aynı imzayla imzalanmıştır.

    Sorunların ortaya çıktığı yer burasıdır. Bunu yapmak için, çerçeveleri / tabanı imzalamak için kullanılan anahtarların elinizde olması gerekir. Nexus fabrika görüntüleri için yalnızca Google'ın anahtarlarına erişmeniz gerekmez, aynı zamanda diğer tüm OEM'lerin ve ROM geliştiricilerinin anahtarlarına da erişmeniz gerekir. Bu mantıklı görünmüyor, bu nedenle uygulamanızı özel bir ROM oluşturarak ve kullanıcılarınızdan buna geçiş yapmalarını isteyerek (bu zor olabilir) veya izin koruma seviyesinin atlanabileceği bir istismar bularak sistem anahtarları ile imzalatabilirsiniz. (bu da zor olabilir).

Ek olarak, bu davranış, 34792 Sayılı: Android Jelly Bean / 4.1: android.permission.READ_LOGS ile ilgili gibi görünmektedir .

TelephonyManager ile çalışmak kulağa hoş geliyor, ancak pratikte yapılması o kadar kolay olmayan uygun izni almadığınız sürece işe yaramayacaktır.

TelephonyManager'ı başka şekillerde kullanmaya ne dersiniz?

Ne yazık ki, harika araçları kullanmak için android.permission.MODIFY_PHONE_STATE'i tutmanızı gerektiriyor gibi görünüyor, bu da bu yöntemlere erişmekte zorlanacağınız anlamına geliyor.


Yöntem 2: servis çağrısı SERVİS KODU

Cihazda çalışan derlemenin belirtilen kodla çalışıp çalışmayacağını ne zaman test edebilirsiniz.

TelephonyManager ile etkileşime servicegirmeden , çalıştırılabilir dosya aracılığıyla hizmetle etkileşim kurma olasılığı da vardır .

Bu nasıl çalışıyor?

Oldukça basit, ancak bu rota hakkında diğerlerine göre daha az belge var. Çalıştırılabilir dosyanın iki bağımsız değişken aldığından emin olduğumuzu biliyoruz - hizmet adı ve kod.

  • Hizmet adı biz kullanıma istediğim telefon .

    Bu koşarak görülebilir service list.

  • Kod biz kullanıma istiyoruz edilmiş görünen 6 ama şimdi görünüyor 5 .

    O dayalı alınmış gibi görünüyor IBinder.FIRST_CALL_TRANSACTION şimdi birçok sürümleri için +5 (den 1.5_r4 için 4.4.4_r1 ) ancak yerel test sırasında kod 5 gelen aramayı cevaplamak için çalıştı. Lollipo her yerinde büyük bir güncelleme olduğu için, burada da değişmiş olan anlaşılabilir iç kısımlar.

Bu, komutuyla sonuçlanır service call phone 5.

Bunu programlı olarak nasıl kullanıyoruz?

Java

Aşağıdaki kod, kavramın bir kanıtı olarak işlev görmesi için yapılmış kaba bir uygulamadır. Gerçekten devam etmek ve bu yöntemi kullanmak istiyorsanız, muhtemelen sorunsuz su kullanımı için yönergelere göz atmak ve muhtemelen Chainfire tarafından geliştirilmiş daha tam gelişmiş libsuperuser'a geçmek istersiniz .

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();
}

Belirgin

<!-- Inform the user we want them root accesses. -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>

Bu gerçekten kök erişimi gerektiriyor mu?

Ne yazık ki öyle görünüyor. Üzerinde Runtime.exec kullanmayı deneyebilirsiniz , ancak bu rotada hiç şansım olmadı.

Bu ne kadar kararlı?

Sorduğuna sevindim Belgelenmemesi nedeniyle, bu, yukarıdaki görünen kod farkında gösterildiği gibi çeşitli sürümlerde kırılabilir. Hizmet adı muhtemelen çeşitli yapılarda telefon olarak kalmalıdır , ancak bildiğimiz kadarıyla, kod değeri aynı sürümün birden çok yapısında değişebilir (örneğin, OEM'in dış görünümüyle yapılan dahili değişiklikler) ve dolayısıyla kullanılan yöntemi bozabilir. Bu nedenle testin bir Nexus 4 (mako / occam) üzerinde yapıldığından bahsetmeye değer. Şahsen bu yöntemi kullanmamanızı tavsiye ederim, ancak daha kararlı bir yöntem bulamadığım için bunun en iyi atış olduğuna inanıyorum.


Orijinal yöntem: Kulaklık tuş kodu amaçları

Yerleşmeniz gereken zamanlar için.

Aşağıdaki bölüm kuvvetle etkilendi Bu yanıt ile Riley C .

Orijinal soruda yayınlanan simüle edilmiş kulaklıklı mikrofon seti amacı yöntemi, beklendiği gibi yayınlanıyor gibi görünüyor, ancak aramayı cevaplama amacına ulaşmış gibi görünmüyor. Bu amaçları yerine getirmesi gereken bir kod varmış gibi görünse de, bunlar önemsenmiyor, bu da bu yönteme karşı bir tür yeni karşı önlem olması gerektiği anlamına gelmelidir. Günlük de ilgi çekici bir şey göstermiyor ve kişisel olarak Android kaynağını araştırmanın, Google'ın yine de kullanılan yöntemi kolayca bozan küçük bir değişiklik yapma olasılığı nedeniyle faydalı olacağına inanmıyorum.

Şu anda yapabileceğimiz bir şey var mı?

Davranış, yürütülebilir girdi kullanılarak tutarlı bir şekilde yeniden oluşturulabilir. O kendisi için biz sadece geçmek, bir tuş kodu argüman alır KeyEvent.KEYCODE_HEADSETHOOK . Yöntem, kök erişimini bile gerektirmez, bu da onu genel halkın genel kullanım durumlarına uygun hale getirir, ancak yöntemde küçük bir dezavantaj vardır - kulaklık düğmesi basma etkinliği bir izin gerektirecek şekilde belirlenemez, yani gerçek gibi çalışır düğmeye basın ve tüm zincir boyunca kabarcıklar yükseliyor, bu da düğmeye basma simülasyonunu ne zaman yapacağınız konusunda dikkatli olmanız gerektiği anlamına geliyor, çünkü daha yüksek önceliğe sahip hiç kimse işleme hazır değilse müzik çaları çalmaya başlamak için tetikleyebilir. olay.

Kod mu?

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

Android 8.0 Oreo ve üstü için güzel bir genel API var.

Android 8.0 Oreo'dan önce herkese açık bir API yoktur. Dahili API'ler sınırsızdır veya dokümantasyonsuzdur. Dikkatli hareket etmelisiniz.


Kulaklıklı mikrofon setinin anahtar kodu amaçlarıyla ilgili olarak, herhangi bir nedenle buradaki Google kaynağını kontrol ettiniz mi? Komik olan, aramaların bu amaçlarla hala kolayca reddedilebilmesidir (sadece uzun basmayı taklit edin), ancak hiçbir şey cevap veremez. Henüz açık bir izin kontrolü veya başka bir olası engel bulamadım ve ikinci bir göz grubunun bir şeyi ortaya çıkaracağını umuyorum.
Riley C

Biraz meşguldü, dolayısıyla gecikme - bunu anlamak için biraz zaman harcamaya çalışacak. Hızlı bir bakıştan sonra CallsManager, HeadsetMediaButton'ı oluşturuyor gibi görünüyor. Oradaki oturum geri araması, MediaSessionManager'dan gelen geri aramalar üzerine handleHeadsetHook'u (KeyEvent) çağırmaya dikkat etmelidir. Tüm kodlar eşleşiyor gibi görünüyor ... ama merak ediyorum, birisi test etmek için KeyEvent.ACTION_DOWN amacını kaldırabilir mi? (Yani, KeyEvent.ACTION_UP'ı yalnızca bir kez çalıştırın.)
Valter Jansons

Aslında, HeadsetMediaButton MediaSession ile çalışır ve MediaSessionManager ile doğrudan etkileşime girmez ...
Valter Jansons

1
Orijinal Yöntemin işe yaramadığı bir durum bulmayı başaranlar için (örneğin, Lollipop'un sorunları var), iyi ve kötü haberlerim var: ACTION_UP'ın kodumda% 100 çalışmasını sağladım. FULL_WAKE_LOCK. PARTIAL_WAKE_LOCK ile çalışmayacaktır. Bunun neden olduğuna dair kesinlikle hiçbir belge yok. Deneme kodumu daha kapsamlı bir şekilde test ettiğimde, bunu ileride vereceğim bir yanıtta ayrıntılarıyla anlatacağım. Kötü haber, elbette, FULL_WAKE_LOCK'un kullanımdan kaldırılmasıdır, bu nedenle bu, yalnızca Google API'da tuttuğu sürece devam edecek bir düzeltmedir.
leRobot

1
Orijinal cevabın yakalanması çoğu durumda çağrılmaz. Önce exec'i arayıp sonra da düğmeyi yukarı aşağı çağırmayı daha iyi buldum.
Warpzit

36

Tam çalışan çözüm @ Valter Strods koduna dayanmaktadır.

Çalışmasını sağlamak için, kodun yürütüldüğü kilit ekranında (görünmez) bir etkinlik görüntülemeniz gerekir.

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>

Çağrı Kabul Etme Etkinliği

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();
         }
     }
}

Tarzı

<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>

Sonunda sihri ara!

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);

1
Burada mi, kodu "Nihayet sihri çağırın" altına eklemek için varsayalım. Android 6.0 için çalışacak mı
Akshay Shah

Buraya, bir Samsung A3 2016 cihazında bir sorunu çözen yayınHeadsetConnected (boolean bağlı) olduğunu söylemeye geldim. Bu olmadan, çok benzer bir yöntem (ayrı, şeffaf etkinlik ve çağrılar ve başka şeyler için iş parçacıkları kullanarak) yaklaşık 20 test edilmiş cihaz için tam olarak çalışıyordu, sonra bu A3 geldi ve beni yeni cevaplar için bu soruyu yeniden kontrol etmeye zorladı. Kodumla karşılaştırdıktan sonra, önemli fark buydu!
leRobot

1
Bir aramayı nasıl reddedebilirim? Bunu göstermek için cevabı güncelleyebilir misiniz?
Amanni

@leRobot Bu cevap, yayınlamak için bir HTC cihazı olup olmadığını kontrol ederHeadsetConnected, bunun bir Samsung A3 2016 cihazı olup olmadığını nasıl kontrol edebilirsiniz? Bu arada bu gerçekten iyi bir cevap, uygulamam ekran kilitli olsa bile telefon aramasına cevap verebilir.
eepty

@eepty Derleme verileri için resmi cihaz referansını kullanabilirsiniz. ( support.google.com/googleplay/answer/1727131?hl=en ). A3 2016 için Build.MODEL.startsWith ("SM-A310") kullanıyorum. AMA! A3 2016'nın kulaklığa bağlı yayınları desteklemediğini doğrulayabilirim! Sorunumu gerçekten çözen şey, sırayı değiştirerek Runtime.getRuntime (). Exec (... bu cihazlar için ilk önce tetikler. Bu cihaz için her seferinde çalışıyor gibi görünüyor ve istisnaya geri dönmüyor.
leRobot

14

Aşağıdaki, benim için çalışan alternatif bir yaklaşımdır. MediaController API'sini kullanarak anahtar olayını telekom sunucusuna gönderir. Bu, uygulamanın BIND_NOTIFICATION_LISTENER_SERVICE iznine sahip olmasını ve kullanıcıdan açıkça Bildirim erişimi vermesini gerektirir :

@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 yukarıdaki kodda sadece boş bir sınıf olabilir.

import android.service.notification.NotificationListenerService;

public class NotificationReceiverService extends NotificationListenerService{
     public NotificationReceiverService() {
     }
}

Manifestteki ilgili bölümle:

    <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>

Olayın hedefi açık olduğundan, bu muhtemelen medya oynatıcıyı tetiklemenin herhangi bir yan etkisinden kaçınmalıdır.

Not: telekom sunucusu, zil olayından hemen sonra aktif olmayabilir. Bunun güvenilir bir şekilde çalışması için, uygulamanın , olayı göndermeden önce telekom sunucusunun ne zaman aktif hale geldiğini izlemek için MediaSessionManager.OnActiveSessionsChangedListener'ı uygulaması yararlı olabilir .

Güncelleme:

In Android O , bir ihtiyaç simüle etmek ACTION_DOWNönce ACTION_UPaksi yukarıda etkisi yoktur. yani aşağıdakiler gereklidir:

m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));

Ancak, Android O'dan beri resmi bir çağrı cevaplama çağrısı mevcut olduğundan (en üstteki cevaba bakın), Android O'dan önce eski bir derleme API seviyesine takılıp kalmadıkça, artık bu hack'e ihtiyaç olmayabilir.


Benim için işe yaramadı. İzin hatası verdi. Uygulamaya verilmeyen bildirime erişim. Android L
Jame

2
Bu, bildirimdeki izni kabul etmenin yanı sıra, sisteme bağlı olarak ayarlar menüsünde bir yerde kullanıcının açıkça izin vermesi için ek bir adım gerektirir.
headuck

Bunun dar bir durumda işe yaradığını bildirmek: Marshmallow ile Galaxy A3 2016. Bunu, FATAL EXCEPTION nedeniyle input keyevent yöntemiyle çalışmayan bir grup A3 cihazda test edeceğim: java.lang.SecurityException: Başka bir uygulamaya enjekte etmek INJECT_EVENTS izni gerektirir. Rahatsız edici cihazlar, kullanıcı tabanımın yaklaşık% 2'sini oluşturuyor ve istisnalarını kopyalamıyorum, ancak bu yöntemi aramayı cevaplayıp cevaplayamayacaklarını görmek için deneyeceğim. Neyse ki uygulamam zaten açık bir bildirim istiyor. başka amaçlar için erişim.
leRobot

Kapsamlı testlerden sonra, "giriş keyevent" ile başarısız olan A3 2016 cihazlarının MediaController # dispatchMediaButtonEvent (<hook KeryEvent>)) yöntemiyle çalışmayı başardığını bildirmekten memnuniyet duyuyorum. Bu, açıkça, yalnızca kullanıcı açık Bildirim Erişimi'ne izin verdikten sonra çalışır, bu nedenle bunun için Android Ayarlarına yönlendiren bir ekran eklemeniz gerekir ve temelde, yanıtta ayrıntılı olarak açıklandığı üzere kullanıcının bunun için fazladan işlem yapması gerekir. Uygulamamda, kullanıcının o ekrana gidip notif eklemeyip istemediğini sormaya devam etmek için ekstra adımlar attık. erişim
leRobot

Bu, Android Nougat'ta çalışır. Aksi takdirde @notz çözümü harika çalışıyor, ancak Android 7'de "Yalnızca sistem medya anahtarı olayını küresel öncelik oturumuna gönderebilir" şeklinde şikayet ediyor.
PB

9

@Muzikant tarafından Yanıta bir nebze durmak için ve benim cihazda biraz daha temiz çalışmak için bunu biraz değiştirmek, denemek input keyevent 79için sabiti KeyEvent.KEYCODE_HEADSETHOOK . Çok kabaca:

    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();

Oldukça kötü kodlama kurallarını bağışlayın, Runtime.exec () çağrılarında çok bilgili değilim. Cihazımın köklü olmadığını ve kök ayrıcalıkları istemediğimi unutmayın.

Bu yaklaşımın sorunu, yalnızca belirli koşullar altında çalışmasıdır (benim için). Yani, yukarıdaki iş parçacığını, bir arama çalarken kullanıcının seçtiği bir menü seçeneğinden çalıştırırsam, arama gayet iyi yanıtlar. Gelen çağrı durumunu izleyen bir alıcıdan çalıştırırsam, tamamen göz ardı edilir.

Dolayısıyla, Nexus 5'imde kullanıcı odaklı yanıtlama için iyi çalışıyor ve özel bir arama ekranının amacına uygun olmalıdır. Herhangi bir tür otomatik arama kontrol tipi uygulama için çalışmaz.

Ayrıca, tüm olası uyarılar da dahil olmak üzere, muhtemelen bir veya iki güncellemede çalışmayı bırakacaktır.


input keyevent 79Sony Xperia 5.0'da iyi çalışıyor. Bir etkinlikten veya bir yayın alıcısından arama yapıldığında çalışır.
nicolas

0

adb komutları aracılığıyla adb tarafından bir çağrı nasıl alınır

Android'in ön ucunda devasa bir JVM ile Linux olduğunu unutmayın. Bir komut satırı uygulaması indirebilir ve telefona root atabilirsiniz ve artık normal bir Linux bilgisayarınız ve her şeyi yapan komut satırınız var. Komut dosyalarını çalıştırın, ssh bile yapabilirsiniz (OpenVPN hilesi)


0

Teşekkürler @notz'un cevabı benim için Lolillop'ta çalışıyor. Bu kodun eski android SDK ile çalışmasını sağlamak için şu kodu yapabilirsiniz:

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();  
    }  
  }  
}  

0

Aramaları otomatik olarak cevapladıktan sonra Telefon Hoparlörü nasıl açılır.

Yukarıdaki problemimi setSpeakerphoneOn ile çözdüm. Bir telefon aramasını otomatik olarak yanıtlamak için kullanım durumu genellikle hoparlörlü telefonun da yararlı olmasını gerektireceğinden, burada yazmaya değer olduğunu düşünüyorum. Bu konudaki herkese tekrar teşekkürler, ne harika bir iş.

Bu benim için ROOT olmadan Nexus 4 cihazımda Android 5.1.1'de çalışıyor. ;)

İzin gerekli:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

Java Kodu:

// 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
}

1
İlginç. Aslında aramayı cevaplamaya ve hoparlörü birlikte açmaya çalışıyorum, bu nedenle bu yaklaşım her ikisini de çözüyor gibi görünüyor :). Yine de diğer cevaplardaki bazı yorumlarla benzer bir sorum var: bu kod nereye gidiyor?
fangmobile

-1

Aşağıdaki komutu root olarak çalıştırın:

input keyevent 5

Önemli olayları simüle etme hakkında daha fazla ayrıntı burada .

Komutları uygulamanızdan root olarak çalıştırmak için oluşturduğum bu temel sınıfı kullanabilirsiniz .


1
Normal bir kullanıcı profiliyle test ederken, bu benim için çağrı içi kullanıcı arayüzünü açtı ve reddetmek / yanıtlamak veya hızlı bir eylem / yanıt kullanmak için sola / sağa kaydırmamı istedi. OP, özel bir gelen arama ekranı oluşturuyorsa , kök altında farklı davranmadığı sürece, bu gerçekten işe yaramaz, sanki normal bir kullanıcı için iyi davranmamış gibi şüpheliyim, arama muhtemelen başarısız olur ve değil farklı bir eylemi tetikler.
Valter Jansons

-2

bunu test edin: önce izinleri ekleyin, ardından kapatmak için killCall () kullanın çağrıyı yanıtlamak için answerCall () kullanın

<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();
    }
}

-2

Bilginize, Android O'da devam eden bir aramayı nasıl sonlandıracağınızla ilgileniyorsanız, Valter'in Method 1: TelephonyManager.answerRingingCall()çalışması için çağırdığınız yöntemi değiştirirsenizendCall .

Sadece gerektirir android.permission.CALL_PHONE izin .

İşte 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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.