簡體   English   中英

在Dialer應用程序頂部檢測來電並在后台打開服務

[英]Detecting incoming call and opening a service in background on top of the dialer app

在檢測到來電后,我在來電上打開一個聊天圖標,如聊天圖標。 但是我面臨兩個問題:
1.當我的應用程序關閉(即使在后台也沒有運行)時,未檢測到來電。
2.當我的手機被鎖定時,聊天圖標不會出現。 聊天圖標隱藏在來電的撥號器應用程序后面。

我正在使用Broadcast Receiver通過PhoneCallReceiver類接收傳入的呼叫,該類調用在CallReceiver類下defined方法,並且在檢測到傳入呼叫時,我正在啟動服務ChatHeadService ,該服務將打開一個類似於圖標的聊天。 我已附上聊天圖標顯示方式的屏幕截圖。 自過去六個月以來,我一直面臨着這個問題,但無法解決。 任何幫助,將不勝感激。

編譯版本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等級26

API等級18
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;
    }
}

調用結束后調用此方法

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>

或在代碼中注冊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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM