Java 在拨号器应用程序上检测来电并在后台打开服务

Java 在拨号器应用程序上检测来电并在后台打开服务,java,android,service,broadcastreceiver,Java,Android,Service,Broadcastreceiver,在检测到来电后,我会在来电时打开一个类似messenger的聊天图标。但我面临两个问题: 1.我的应用程序关闭时未检测到来电(即使在后台也未运行)。 2.当我的手机被锁定时,聊天图标不会出现。来电时,聊天图标隐藏在拨号器应用程序后面。 我正在使用Broadcast Receiver接收来电,使用PhoneCallReceiver类调用CallReceiver类下定义的方法,并在检测来电时启动服务ChatHeadService,该服务会打开一个类似聊天的图标。我附上了聊天图标的屏幕截图。在过去的

在检测到来电后,我会在来电时打开一个类似messenger的聊天图标。但我面临两个问题:
1.我的应用程序关闭时未检测到来电(即使在后台也未运行)。
2.当我的手机被锁定时,聊天图标不会出现。来电时,聊天图标隐藏在拨号器应用程序后面。

我正在使用
Broadcast Receiver
接收来电,使用
PhoneCallReceiver
类调用
CallReceiver
类下定义的
方法,并在检测来电时启动服务
ChatHeadService
,该服务会打开一个类似聊天的图标。我附上了聊天图标的屏幕截图。在过去的6个月里,我一直面临着这个问题,但一直无法解决。任何帮助都将不胜感激。

编译DK23版
buildToolsVersion“27.0.3”
targetSdkVersion 23

我在两台API级别为18和26的设备上测试了该应用程序。在API级别18中,我的应用程序运行良好,上述两个问题都已修复。但在API级别26中,我的应用程序工作了,但没有工作,聊天图标隐藏在拨号器应用程序后面。

我在Oreo API 26的传入调用中遇到以下错误。
06-13 16:22:23.969 1238-4375/?W/BroadcastQueue:Permission Denial:receiving Intent{act=android.Intent.action.PHONE_STATE flg=0x1000010(有附加项)}到com.skype.m2/com.skype.nativephone.connector.NativePhoneCallReceiver需要android.Permission.READ_PHONE_STATE,因为发送方android(uid 1000)


API等级26

API等级18


AndroidManifest.xml

CallReceiver.java

ChatHeadService.java


在调用结束后调用此方法

private void alert(Context ctx) {
        StringBuffer sb = new StringBuffer();
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        Cursor cur = getContentResolver().query(CallLog.Calls.CONTENT_URI,
                null, null, null, CallLog.Calls.DATE + " DESC limit 1;");
        //Cursor cur = getContentResolver().query( CallLog.Calls.CONTENT_URI,null, null,null, android.provider.CallLog.Calls.DATE + " DESC");

        int number = cur.getColumnIndex( CallLog.Calls.NUMBER );
        int duration = cur.getColumnIndex( CallLog.Calls.DURATION);
        int type = cur.getColumnIndex(CallLog.Calls.TYPE);
        int date = cur.getColumnIndex(CallLog.Calls.DATE);
        sb.append( "Call Details : \n");
        phNumber = null;
        callDuration = null;
        callType = null;
        callDate = null;
        String dir = null;
        String callDayTime = null;
        while ( cur.moveToNext() ) {
            phNumber = cur.getString( number );
            callDuration = cur.getString( duration );
            callType = cur.getString( type );
            callDate = cur.getString( date );
            callDayTime = new Date(Long.valueOf(callDate)).toString();



            int dircode = Integer.parseInt(callType);
            switch (dircode) {
                case CallLog.Calls.OUTGOING_TYPE:
                    dir = "OUTGOING";
                    break;

                case CallLog.Calls.INCOMING_TYPE:
                    dir = "INCOMING";
                    break;

                case CallLog.Calls.MISSED_TYPE:
                    dir = "MISSED";
                    break;
            }

//            sb.append( "\nPhone Number:--- "+phNumber +" \nCall duration in sec :--- "+callDuration );
            sb.append("\nPhone Number:--- " + phNumber + " \nCall Type:--- " + dir + " \nCall Date:--- " + callDayTime + " \nCall duration in sec :--- " + callDuration);

            sb.append("\n----------------------------------");
            Log.e("dir",dir);
        }
        cur.close();

        callType=dir;
        callDate=callDayTime;
        Log.e("call ",phNumber+" duration"+callDuration+" type "+callType+" date "+callDate);

        startactivity(ctx);
    }

它将为您提供上次通话的详细信息

不久前我发现了这个示例。我只加了一句,电话是打进来的还是打出去的。通过意图将数据传递给服务,并使用它执行服务。应在api 23中工作。在最新版本中,我不能保证这一点

public class CallReceiver extends BroadcastReceiver {

private final static String TAG = "CallReceiver";
private static PhoneCallStartEndDetector listener;
private String outgoingSavedNumber;
protected Context savedContext;

@Override
public void onReceive(Context context, Intent intent) {

    this.savedContext = context;
    if (listener == null) {

        listener = new PhoneCallStartEndDetector();
    }

    String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);

    if (phoneState == null) {

        listener.setOutgoingNumber(intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));

    } else if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {

        listener.setOutgoingNumber(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
    }

    TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    telephony.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);

}

//Deals with actual events
private class PhoneCallStartEndDetector extends PhoneStateListener {
    int lastState = TelephonyManager.CALL_STATE_IDLE;
    boolean isIncoming;
    boolean isOutgoing;
    String savedNumber;  //because the passed incoming is only valid in ringing

    private PhoneCallStartEndDetector() {

    }

    //The outgoing number is only sent via a separate intent, so we need to store it out of band
    private void setOutgoingNumber(String number) {

        savedNumber = number;
    }

    Intent serviceIntent = new Intent(savedContext, YourService.class);

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        super.onCallStateChanged(state, incomingNumber);

        if (lastState == state) {
            //No change, debounce extras
            return;
        }

        switch (state) {

            case TelephonyManager.CALL_STATE_RINGING:

                isIncoming = true;
                savedNumber = incomingNumber;

                serviceIntent.putExtra("label", value);
                savedContext.startService(serviceIntent);

                break;

            case TelephonyManager.CALL_STATE_OFFHOOK:
                //Transition of ringing->offhook are pickups of incoming calls.  Nothing donw on them
                if (lastState != TelephonyManager.CALL_STATE_RINGING) {

                    if (!isOutgoing) {

                        isOutgoing = true;

                    }

                    if (!savedNumber.equals("")) {

                        serviceIntent.putExtra("label", value);
                        savedContext.startService(serviceIntent);
                    }
                }
                break;

            case TelephonyManager.CALL_STATE_IDLE:
                //Went to idle-  this is the end of a call.  What type depends on previous state(s)
                if (lastState == TelephonyManager.CALL_STATE_RINGING) {
                    //Ring but no pickup-  a miss

                    savedContext.stopService(serviceIntent);

                } else if (isIncoming) {

                    savedContext.stopService(serviceIntent);

                } else {

                    if (isOutgoing) {

                        savedContext.stopService(serviceIntent);

                        isOutgoing = false;
                    }
                }

                break;
        }

        lastState = state;
    }
}
}

在清单中注册此接收者,这应适用于api 25:

<receiver
        android:name=".calls.CallReceiver"
        android:enabled="true">
        <intent-filter android:priority="-1">
            <action android:name="android.intent.action.PHONE_STATE" />
            <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
        </intent-filter>
    </receiver>
当然,要使用此代码,您需要授予权限。在api级别低于23的清单中:

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

若你们不想在后台运行应用程序,那个么试着看看如何在其他应用程序上运行和显示标签。@Jarvis我希望我的应用程序在后台运行。我希望即使我的应用程序关闭,广播接收器也能正常工作。使用startForeground()方法在后台运行服务
public void onCallStateChanged(上下文上下文、int状态、字符串编号)
这不是重写方法,您应该使用侦听器通知入/出呼叫开始。让我们搜索一个示例,其中扩展了
PhoneStateListener
。@grabarz121您能给我一个示例或一些链接吗?还记得关于读取呼叫日志的权限吗
android.permission.read\u call\u LOG
我不想要这个方法。我已经知道来电的详细情况了。我想在我遇到问题的来电中打开聊天图标。你在哪个设备上测试应用程序?@miteshmakwana Api 26 Orei在不同的设备上测试我在Api级别为17的不同设备上使用我的应用程序,我的应用程序在该设备上工作。因此,我认为这个问题在某种程度上与当前api级别的权限有关。我理解您的应用程序完全无法工作。签入oreo设备应用程序管理器,您的应用程序是否已授予权限。根据api 23,清单中的权限不会自动授予,您必须询问用户(启动活动中最好的)。如果已授予权限,请尝试以编程方式注册您的broadcastreceiver。在api 26中,接收器和服务存在限制。在代码中添加一些日志,并尝试在启动活动中注册此日志,然后进行呼叫。我已在oreo设备上手动授予应用程序所需的所有权限。我不知道如何以编程方式注册广播接收器。
registerReceiver()
context.registerReceiver()
我应该在哪个类中调用
registerReceiver
context.registerReceiver
private void alert(Context ctx) {
        StringBuffer sb = new StringBuffer();
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        Cursor cur = getContentResolver().query(CallLog.Calls.CONTENT_URI,
                null, null, null, CallLog.Calls.DATE + " DESC limit 1;");
        //Cursor cur = getContentResolver().query( CallLog.Calls.CONTENT_URI,null, null,null, android.provider.CallLog.Calls.DATE + " DESC");

        int number = cur.getColumnIndex( CallLog.Calls.NUMBER );
        int duration = cur.getColumnIndex( CallLog.Calls.DURATION);
        int type = cur.getColumnIndex(CallLog.Calls.TYPE);
        int date = cur.getColumnIndex(CallLog.Calls.DATE);
        sb.append( "Call Details : \n");
        phNumber = null;
        callDuration = null;
        callType = null;
        callDate = null;
        String dir = null;
        String callDayTime = null;
        while ( cur.moveToNext() ) {
            phNumber = cur.getString( number );
            callDuration = cur.getString( duration );
            callType = cur.getString( type );
            callDate = cur.getString( date );
            callDayTime = new Date(Long.valueOf(callDate)).toString();



            int dircode = Integer.parseInt(callType);
            switch (dircode) {
                case CallLog.Calls.OUTGOING_TYPE:
                    dir = "OUTGOING";
                    break;

                case CallLog.Calls.INCOMING_TYPE:
                    dir = "INCOMING";
                    break;

                case CallLog.Calls.MISSED_TYPE:
                    dir = "MISSED";
                    break;
            }

//            sb.append( "\nPhone Number:--- "+phNumber +" \nCall duration in sec :--- "+callDuration );
            sb.append("\nPhone Number:--- " + phNumber + " \nCall Type:--- " + dir + " \nCall Date:--- " + callDayTime + " \nCall duration in sec :--- " + callDuration);

            sb.append("\n----------------------------------");
            Log.e("dir",dir);
        }
        cur.close();

        callType=dir;
        callDate=callDayTime;
        Log.e("call ",phNumber+" duration"+callDuration+" type "+callType+" date "+callDate);

        startactivity(ctx);
    }
public class CallReceiver extends BroadcastReceiver {

private final static String TAG = "CallReceiver";
private static PhoneCallStartEndDetector listener;
private String outgoingSavedNumber;
protected Context savedContext;

@Override
public void onReceive(Context context, Intent intent) {

    this.savedContext = context;
    if (listener == null) {

        listener = new PhoneCallStartEndDetector();
    }

    String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);

    if (phoneState == null) {

        listener.setOutgoingNumber(intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));

    } else if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {

        listener.setOutgoingNumber(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
    }

    TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    telephony.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);

}

//Deals with actual events
private class PhoneCallStartEndDetector extends PhoneStateListener {
    int lastState = TelephonyManager.CALL_STATE_IDLE;
    boolean isIncoming;
    boolean isOutgoing;
    String savedNumber;  //because the passed incoming is only valid in ringing

    private PhoneCallStartEndDetector() {

    }

    //The outgoing number is only sent via a separate intent, so we need to store it out of band
    private void setOutgoingNumber(String number) {

        savedNumber = number;
    }

    Intent serviceIntent = new Intent(savedContext, YourService.class);

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        super.onCallStateChanged(state, incomingNumber);

        if (lastState == state) {
            //No change, debounce extras
            return;
        }

        switch (state) {

            case TelephonyManager.CALL_STATE_RINGING:

                isIncoming = true;
                savedNumber = incomingNumber;

                serviceIntent.putExtra("label", value);
                savedContext.startService(serviceIntent);

                break;

            case TelephonyManager.CALL_STATE_OFFHOOK:
                //Transition of ringing->offhook are pickups of incoming calls.  Nothing donw on them
                if (lastState != TelephonyManager.CALL_STATE_RINGING) {

                    if (!isOutgoing) {

                        isOutgoing = true;

                    }

                    if (!savedNumber.equals("")) {

                        serviceIntent.putExtra("label", value);
                        savedContext.startService(serviceIntent);
                    }
                }
                break;

            case TelephonyManager.CALL_STATE_IDLE:
                //Went to idle-  this is the end of a call.  What type depends on previous state(s)
                if (lastState == TelephonyManager.CALL_STATE_RINGING) {
                    //Ring but no pickup-  a miss

                    savedContext.stopService(serviceIntent);

                } else if (isIncoming) {

                    savedContext.stopService(serviceIntent);

                } else {

                    if (isOutgoing) {

                        savedContext.stopService(serviceIntent);

                        isOutgoing = false;
                    }
                }

                break;
        }

        lastState = state;
    }
}
<receiver
        android:name=".calls.CallReceiver"
        android:enabled="true">
        <intent-filter android:priority="-1">
            <action android:name="android.intent.action.PHONE_STATE" />
            <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
        </intent-filter>
    </receiver>
IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("android.intent.action.PHONE_STATE");
    CallReceiver receiver = new CallReceiver();
    registerReceiver(receiver, intentFilter);
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Manifest.permission.READ_PHONE_STATE