【问题标题】:Detecting incoming call and opening a service in background on top of the dialer app在拨号器应用程序顶部检测来电并在后台打开服务
【发布时间】:2018-06-13 19:09:29
【问题描述】:

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

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

compileSdkVersion 23
buildToolsVersion '27.0.3'
targetSdkVersion 23

我在 API 级别 18 和 API 级别 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 (has extras) } to com.skype.m2/com.skype.nativephone.connector.NativePhoneCallReceiver requires android.permission.READ_PHONE_STATE due to sender android (uid 1000)

API 级别 26

API 级别 18


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.tarun.notifyme2">

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="23" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.Settings.ACTION_MANAGE_OVERLAY_PERMISSION" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission
        android:name="android.permission.MODIFY_PHONE_STATE"
        tools:ignore="ProtectedPermissions" />

    <application
        android:allowBackup="true"
        android:enabled="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SignUp">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SendNoti" />

        <receiver android:name=".CallReceiver"
            android:enabled="true">
            <intent-filter android:priority="1000">
                <action android:name="android.intent.action.PHONE_STATE" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>


        <service
            android:name=".ChatHeadService"
            android:exported="true"
            android:enabled="true"/>

        <service android:name=".FirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        <service android:name=".FirebaseInstanceIDService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
            </intent-filter>
        </service>

        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBar" />
        <activity android:name=".MainChat" />
        <activity android:name=".ChatRoom" />
        <activity android:name=".Feedback" />
    </application>

</manifest>

PhonecallReceiver.java

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import java.util.Date;

public abstract class PhonecallReceiver extends BroadcastReceiver
{
    private static int lastState = TelephonyManager.CALL_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;

    @Override
    public void onReceive(Context context, Intent intent)
    {
        try
        {
            if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL"))
            {
                savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
            }
            else
            {
                String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
                String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
                int state = 0;
                if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE))
                {
                    state = TelephonyManager.CALL_STATE_IDLE;
                }
                else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK))
                {
                    state = TelephonyManager.CALL_STATE_OFFHOOK;
                }
                else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING))
                {
                    state = TelephonyManager.CALL_STATE_RINGING;
                }

                onCallStateChanged(context, state, number);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    //Derived classes should override these to respond to specific events of interest
    protected void onIncomingCallStarted(Context ctx, String number, Date start){}
    protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end){}

    public void onCallStateChanged(Context context, int state, String number)
    {
        if(lastState == state)
        {
            //No change, debounce extras
            return;
        }
        switch (state)
        {
            case TelephonyManager.CALL_STATE_RINGING:
                isIncoming = true;
                callStartTime = new Date();
                savedNumber = number;
                onIncomingCallStarted(context, number, callStartTime);
                break;

            case TelephonyManager.CALL_STATE_OFFHOOK:
                if (isIncoming)
                {
                    onIncomingCallEnded(context,savedNumber,callStartTime,new Date());
                }

            case TelephonyManager.CALL_STATE_IDLE:
                if(isIncoming)
                {
                    onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
                }
        }
        lastState = state;
    }
}

CallReceiver.java

import android.app.Activity;
import android.app.Dialog;
import android.app.Notification;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.Toast;
import android.os.Handler;
import java.util.Date;

public class CallReceiver extends PhonecallReceiver
{
    Context context;

    @Override
    protected void onIncomingCallStarted(final Context ctx, String number, Date start)
    {
        Toast.makeText(ctx,"New Incoming Call"+ number,Toast.LENGTH_LONG).show();
        context =   ctx;
        final Intent intent = new Intent(context, ChatHeadService.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intent.putExtra("phone_no",number);
        SharedPreferences.Editor editor = ctx.getSharedPreferences("Notify", Context.MODE_PRIVATE).edit();
        editor.putString("incomingNo",number);
        editor.commit();
        new Handler().postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                //start service which opens a chat icon after 2 seconds wait
                context.startService(intent);
            }
        },2000);

    }

    @Override
    protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end)
    {
        final Intent intent = new Intent(context, ChatHeadService.class);
        ctx.stopService(intent);
        Toast.makeText(ctx,"Bye Bye"+ number,Toast.LENGTH_LONG).show();
    }
}

ChatHeadService.java

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;

public class ChatHeadService extends Service {

    private WindowManager windowManager;
    private ImageView chatHead;
    WindowManager.LayoutParams params;
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int res = super.onStartCommand(intent, flags, startId);
        return res;
    }
    @Override
    public void onCreate() {
        super.onCreate();

        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        chatHead = new ImageView(this);
        chatHead.setImageResource(R.drawable.bell2);
        chatHead.setClickable(true);

        params= new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.TOP | Gravity.LEFT;
        params.x = 0;
        params.y = 400;

        windowManager.addView(chatHead, params);

        chatHead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(ChatHeadService.this, SendNoti.class)
                                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                stopSelf();
            }
        });

        //this code is for dragging the chat head
        chatHead.setOnTouchListener(new View.OnTouchListener() {
            private int initialX;
            private int initialY;
            private float initialTouchX;
            private float initialTouchY;
            int flag=0;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    initialX = params.x;
                    initialY = params.y;
                    initialTouchX = event.getRawX();
                    initialTouchY = event.getRawY();
                    if(flag==3){
                        flag=1;
                        return true;
                    }else{
                        flag=1;
                        return false;
                    }
                case MotionEvent.ACTION_UP:
                    if(flag==3){
                        flag=2;
                        return true;
                    }else{
                        flag=2;
                        return false;
                    }
                case MotionEvent.ACTION_MOVE:
                    flag=3;
                    params.x = initialX
                            + (int) (event.getRawX() - initialTouchX);
                    params.y = initialY
                            + (int) (event.getRawY() - initialTouchY);
                    windowManager.updateViewLayout(chatHead, params);
                    return true;
                default:
                    Toast.makeText(getApplicationContext(),"You ckiced the imageview",Toast.LENGTH_LONG).show();
                    Log.i("tag","You clicked the imageview");
                /*
                Intent i = new Intent(view.getContext(),SendNoti.class);
                startActivity(i);
                stopSelf();*/
                    return true;
                }
            }
        });
        /*
        Snackbar.make(chatHead, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show();*/

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (chatHead != null)
            windowManager.removeView(chatHead);
        stopSelf();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }
}

【问题讨论】:

  • 如果您不想在后台运行应用程序,请尝试查看远足和在其他应用程序上显示贴纸的方式。
  • @Jarvis 我希望我的应用程序在后台运行。即使我的应用程序关闭,我也希望广播接收器能够工作。但我做不到。
  • 使用startForeground()方法在后台运行服务
  • public void onCallStateChanged(Context context, int state, String number) 这不是被覆盖的方法,您应该使用监听器来通知呼入/呼出开始。让我们搜索一个例子,PhoneStateListener 是扩展的。
  • @grabarz121 你能给我一个例子或一些链接吗?

标签: java android service broadcastreceiver


【解决方案1】:

前段时间我发现了这个例子。我只添加了这一点,来电或去电。通过意图将您的数据传递给服务并使用它来执行服务。应该在 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>

或者在代码中注册BroadcastReceiver,这应该在api 26中工作:

IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("android.intent.action.PHONE_STATE");
    CallReceiver receiver = new CallReceiver();
    registerReceiver(receiver, intentFilter);

当然,要使用此代码,您需要授予权限。在 api 级别低于 23 的清单中:

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

对于 api 23 和最新版本,请向用户询问权限:

Manifest.permission.READ_PHONE_STATE

【讨论】:

  • 我在 api 级别 17 的不同设备中使用了我的应用程序,并且我的应用程序在该设备上运行。所以我认为这个问题在某种程度上与当前 api 级别的权限有关。
  • 我了解您的应用程序完全不起作用。检查奥利奥设备应用程序管理器,您的应用程序是否已授予权限。来自清单的 api 23 权限不会自动授予,您必须向用户询问它(最好在启动活动中)。如果授予权限,请尝试以编程方式注册您的广播接收器。在 api 26 中有关于接收器和服务的限制。在您的代码中添加一些日志并尝试在启动活动中注册它,然后拨打电话。
  • 我已在我的 oreo 设备上手动授予我的应用程序所需的所有权限。我不明白如何以编程方式注册广播接收器。
  • registerReceiver()context.registerReceiver()
  • 我应该在哪个班级打电话registerReceivercontext.registerReceiver
【解决方案2】:

调用结束后调用该方法

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

它会给你最后一次通话的详细信息

【讨论】:

  • 记住读取通话记录的权限android.permission.READ_CALL_LOG
  • 我不想要这种方法。我已经有了来电的详细信息。我想在遇到问题的来电中打开聊天图标。
  • 您在哪个设备上测试应用程序?
  • @miteshmakwana API 26 奥利奥
  • 我在不同的设备上测试
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-09
  • 1970-01-01
相关资源
最近更新 更多