检测拨出电话何时开始播放回铃音

时间:2018-07-31 10:29:49

标签: android android-activity sip gsm

当它开始播放回铃音时,我正在尝试检测拨出电话的状态。我尝试了各种方法来检测这种状态。以下是其中一些:

1。使用PhoneStateListener

Cannot detect when outgoing call is answered in Android

import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;

public class CustomPhoneStateListener extends PhoneStateListener {

    public void onCallStateChanged(int state, String num) {
        Log.d(CallStatusPlugin.TAG, ">>>state changed" + state);
    }
}

但是诸如TelephonyManager.CALL_STATE_IDLETelephonyManager.CALL_STATE_OFFHOOK之类的状态并没有提供这些状态。

2。使用READ_PRECISE_PHONE_STATE

将方法添加到上述相同的电话状态监听器中

public void onPreciseCallStateChanged() {
    Log.d(CallStatusPlugin.TAG, "onPreciseCallStateChanged");
}

但是根据我的研究,读取精确状态要求该应用程序必须是系统应用程序。

3。使用NotificationListener

public class CustomNotificationListener extends NotificationListenerService {

    public static final String TAG = "CallStatusPlugin";

    public CustomNotificationListener() {
        Log.v(TAG, ">>> CustomNotificationListener");
    }

    public void onNotificationPosted(StatusBarNotification sbn) {
        Log.i(TAG, "New Notification");

        Bundle extras = sbn.getNotification().extras;

        if ("Ongoing call".equals(extras.getString(Notification.EXTRA_TEXT))) {
            Log.v(TAG, "outgoing call");
        } else if ("Dialing".equals(extras.getString(Notification.EXTRA_TEXT))) {
            Log.v(TAG, "dialling call");
        }
    }
}

但这无济于事,因为当回铃音开始为拨出电话播放时,操作系统不会更改通知。

4。使用BroadcastReceiver

public class CallBroadcastReceiver extends BroadcastReceiver {

    public static final String TAG = "CallStatusPlugin";

    public void onReceive(Context context, Intent intent) {
        String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);

        Log.i(TAG, "CallBroadcastReceiver state: " + state);

        // TelephonyManager.EXTRA_FOREGROUND_CALL_STATE = "foreground_state"
        Log.d(TAG, "new state >>>>" + intent.getIntExtra("foreground_state", -2));
    }
}

但也没有帮助。

5。使用反射:

我还尝试使用反射来获取默认拨号程序的实例,但没有成功:

    //com.samsung.android.incallui
    Reflections reflections = new Reflections("com.samsung.android.contacts", new SubTypesScanner(false));

    final ClassLoader classLoader = this.getClass().getClassLoader();
    ClassLoader[] loaders = { classLoader };

    ConfigurationBuilder configurationBuilder = (ConfigurationBuilder) reflections.getConfiguration();
    configurationBuilder.setClassLoaders(loaders);

    Log.d(TAG, "cl" + classLoader.toString());
    Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class);
    Log.d(TAG, "allclasses" + allClasses.toString());

我无法上任何课(也许我没有正确使用反射)

6。使用InCallService

通过用自定义拨号器替换默认拨号器(我不想使用)来获得Call状态。

import android.os.Bundle;
import android.telecom.Call;
import android.telecom.InCallService;
import android.util.Log;

public class CustomInCallService extends InCallService {

    public static final String TAG = "CallStatusPlugin";

    @Override
    public void onCallAdded(Call call) {
        Log.d(TAG, "onCallAdded: " + call.getState());

        call.registerCallback(
                new Call.Callback() {
                    @Override
                    public void onConnectionEvent (Call call, String event, Bundle extras) {
                        Log.d(TAG, "Call.Callback.onConnectionEvent: " + event + ", " + call.getState());
                    }

                    @Override
                    public void onStateChanged (Call call, int state) {
                        Log.d(TAG, "Call.Callback.onStateChanged: " + state + ", " + call.getState());
                    }
                }
        );
    }
}

onStateChanged终于给了我一些东西:

通话状态-> 9(STATE_CONNECTING)-> 1(STATE_DIALING)-> 4(STATE_ACTIVE)(回答时)-> 7( STATE_DISCONNECTING

但是,当拨出电话存在一些问题(例如,已拨电话无法接通或电话已关闭)时,呼叫状态也会更改为STATE_DIALING。因此,这意味着我们不能说DIALING状态是呼出电话开始播放回铃音的状态。

7。从反射中使用CallManager

添加2018年8月3日

public class OutCallLogger extends BroadcastReceiver {

    public static final String TAG = "CallStatusPlugin";

    public OutCallLogger() {
        Log.e(TAG, "\n\n\nOutCallLogger Instance Created\n\n\n");
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
        Log.i(TAG, "OutCallLogger state: " + state);

        String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
        Log.i(TAG, "Outgoing Number: " + number);

        // TelephonyManager.EXTRA_FOREGROUND_CALL_STATE = "foreground_state"
        Log.d(TAG, "new state >>>>" + intent.getIntExtra("foreground_state", -2));
        Log.d(TAG, "new state >>>>" + intent.getIntExtra("ringing_state", -2));
        Log.d(TAG, "new state >>>>" + intent.getIntExtra("background_state", -2));
        Log.d(TAG, "new state >>>>" + intent.getIntExtra("disconnect_cause", -2));

        final ClassLoader classLoader = this.getClass().getClassLoader();

        try {
            Class<?> callManagerClass = classLoader.loadClass("com.android.internal.telephony.CallManager");
            Log.e(TAG, "CallManager: Class loaded " + callManagerClass.toString());

            Method[] methods = callManagerClass.getDeclaredMethods();
            for (Method m : methods) {
                Log.e(TAG, "Methods: " + m.getName());
            }

            Method getInstanceMethod = callManagerClass.getDeclaredMethod("getInstance");
            getInstanceMethod.setAccessible(true);
            Log.e(TAG, "CallManager: Method loaded " + getInstanceMethod.getName());

            Object callManagerObject = getInstanceMethod.invoke(null);
            Log.e(TAG, "CallManager: Object loaded " + callManagerObject.getClass().getName());

            Method getAllPhonesMethod = callManagerClass.getDeclaredMethod("getAllPhones");
            Log.e(TAG, "CallManager: Method loaded " + getAllPhonesMethod.getName());

            Method getForegroundCallsMethod = callManagerClass.getDeclaredMethod("getForegroundCalls");
            Log.e(TAG, "CallManager: Method loaded " + getForegroundCallsMethod.getName());
            List foregroundCalls = (List) getForegroundCallsMethod.invoke(callManagerObject);
            Log.e(TAG, "Foreground calls: " + foregroundCalls + ", " + foregroundCalls.size());

            Method getBackgroundCallsMethod = callManagerClass.getDeclaredMethod("getBackgroundCalls");
            Log.e(TAG, "CallManager: Method loaded " + getForegroundCallsMethod.getName());
            List backgroundCalls = (List) getBackgroundCallsMethod.invoke(callManagerObject);
            Log.e(TAG, "Background calls: " + backgroundCalls + ", " + backgroundCalls.size());

            Timer timer = new Timer();

            // keep printing all the for 20 seconds to check if we got one
            TimerTask doAsynchronousTask = new TimerTask() {
                long t0 = System.currentTimeMillis();

                @Override
                public void run() {
                    // cancel the timer after 20 seconds
                    if (System.currentTimeMillis() - t0 > 20 * 1000) {
                        cancel();
                        return;
                    }

                    try {
                        List phonesObject = (List) getAllPhonesMethod.invoke(callManagerObject);
                        Log.e(TAG, "All phones " + phonesObject + ", " + phonesObject.size());

                        List foregroundCalls = (List) getForegroundCallsMethod.invoke(callManagerObject);
                        Log.e(TAG, "Foreground calls: " + foregroundCalls + ", " + foregroundCalls.size());

                        Object backgroundCalls = getBackgroundCallsMethod.invoke(callManagerObject);
                        Log.e(TAG, "Background calls: " + backgroundCalls);

                        String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
                        Log.i(TAG, "New state: " + state);
                    } catch (Exception e) {
                        Log.e(TAG, ">>>1. " + e.getMessage());
                    }
                }
            };

            timer.schedule(doAsynchronousTask, 0, 1000); //execute in every 1000 ms
        } catch (ClassNotFoundException e) {
            Log.e(TAG, ">>>2. " + e.getMessage());
        } catch (NoSuchMethodException e) {
            Log.e(TAG, ">>>3. " + e.getMessage());
        } catch (InvocationTargetException e) {
            Log.e(TAG, ">>>4. " + e.getMessage());
        } catch (IllegalAccessException e) {
            Log.e(TAG, ">>>5. " + e.getMessage());
        }
    }
}

但是我得到所有后台/前台通话和电话作为清空结果:

08-03 15:19:22.638  2586  4636 E CallStatusPlugin: All phones [], 0
08-03 15:19:22.639  2586  4636 E CallStatusPlugin: Foreground calls: [], 0
08-03 15:19:22.639  2586  4636 E CallStatusPlugin: Background calls: []

不确定操作系统是否在使用CallManager

经过2-3周的研究,我了解到:

  1. 隐藏的API可以为我们提供与OS的更深入的集成(但Google may remove access to hidden API in Android P
  2. Ringing sound of an outgoing call == Ringback tone
  3. State dialing !== State of playing ringback tone
  4. 但是我没有得出一个结论,我可以说这是我们可以确定拨出电话开始播放回铃音状态的方式

我通过adb logcat '*:V'读取所有操作系统日志来进行调试,并在听到回铃音(adb logcat -b system '*:V')时看到正在打印日志:

07-31 13:34:13.487  3738 29960 I Telephony: AsyncConnectTonePlayer : play
07-31 13:34:13.784  3273  7999 D SSRM:p  : SIOP:: AP = 330, PST = 313 (W:26), BAT = 294, USB = 0, CHG = 0
07-31 13:34:13.902  3738 29960 I Telephony: AsyncConnectTonePlayer : onCompletion
07-31 13:34:14.304  3273 15438 D CustomFrequencyManagerService: releaseDVFSLockLocked : Getting Lock type frm List : DVFS_MIN_LIMIT  frequency : 1352000  uid : 1000  pid : 3273  tag : com.samsung.android.incallui@2
07-31 13:34:14.639  3273  7999 D AudioService: getStreamVolume 0 index 10
07-31 13:34:16.449  4175  4175 D io_stats: !@ 179,0 r 264349 9232882 w 465999 10111332 d 42634 3418440 f 235485 235449 iot 468730 459107 th 51200 0 0 pt 0 inp 0 0 104823.814

然后,我开始搜索与 io stats事件android gsm连接事件相关的内容,但是我无法找出任何方法来获取该状态的特定信息拨出电话,呼叫开始播放回铃音。

我查看了各种Android代码以发现一些提示:

  1. TelephonyConnection
  2. GsmConnection

我应该寻求什么提示或指导来实现这一目标?像GSM连接一样,ConnectionServiceConnectivityManagerConnectionServiceConnectionRequest都是什么?

编辑1:

阅读更多日志会导致我进入SipManager,并且了解到SIP Basic Call Flow Examples,我认为Android用于拨打或接听电话。

07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:131 | Start add(alm)
07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:138 | AlarmMsg is not null
07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:140 | Sending AlarmMessage 0 to TUs
07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:145 | End add(alm)
07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:DNS | DnsResult.cxx:240 | Whitelisting 2405:200:380:1581::42(28): 2405:200:380:1581::42
07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:DNS | RRVip.cxx:128 | updating an existing vip: 2405:200:380:1581::42 with 2405:200:380:1581::42
07-31 16:29:55.335  4076  5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:71 | Send to TU: TU: CALL-SESSION(8) size=0 
07-31 16:29:55.335  4076  5081 I reSIProcate: 
07-31 16:29:55.335  4076  5081 I reSIProcate: SipResp: 180 tid=935e2afa889bdcad cseq=1 INVITE contact=xxxx@10.56.68.219:5070 / 1 from(wire)
07-31 16:29:55.336  4076  5081 D StackIF : readMessage: messageType 2 tid 0 pduLength 920
07-31 16:29:55.336  4076  5081 D SECIMSJ[0]: [UNSL]< NOTIFY_SIP_MESSAGE
07-31 16:29:55.336  4076  5081 D StackIF[0]: processNotify: id NOTIFY_SIP_MESSAGE
07-31 16:29:55.340  4076  5081 D SIPMSG[0]: [<--] SIP/2.0 180 Ringing [CSeq: 1 INVITE]
07-31 16:29:55.340  4076  4896 D ResipRawSipHandler: handleMessage: event: 100
07-31 16:29:55.340  4076  5082 D OpenApiServiceModule: handleMessage: what 100
07-31 16:29:55.341  4076  4896 D ResipVolteHandler: handleMessage: evt 114
07-31 16:29:55.341  4076  5081 D CpAudioEngineClient: SAE_NotiInfo: SAE_NotiInfo string: 16:29:55.341<180 Ringing:1 INVITE
07-31 16:29:55.341  4076  5081 D VoIpEngineProxy: NotiInfo:Sending IPC_IMS_INFO Noti

会有用吗?

1 个答案:

答案 0 :(得分:1)

现在,在Android R中,我们拥有公共api的权限。

READ_PRECISE_PHONE_STATE

允许只读访问精确的电话状态。允许读取有关特殊用途应用程序(例如拨号器,运营商应用程序或ims应用程序)的电话状态的详细信息。

也许您现在可以将其用于精确的电话状态,我已经在自定义Android系统中获得了精确的电话状态。