欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

sip来电

程序员文章站 2022-04-01 10:30:00
前面刚说了锁屏下要接电话 紧接着又提出假如有多通电话同时进来怎么处理 笔者就矜矜业业又去测试了普通电话的多通电话 结果发现当前有一个电话正在进来 又有另外一个电话打进来会直接挂断 那这显然不能达到老板想要同时接多个电话的要求了 笔者只要又矜矜业业研究怎么处理多通电话的逻辑了 其实笔者也快要被逼疯了 要求太多 笔者改烦了 需求老是变更 算了 还是先来讲讲通话的逻辑吧1.这个软电话软件是拿linphone改的 所以本人的所有实现逻辑是基于linphone的下面来看一下他的电话状态侦听...

前面刚说了锁屏下要接电话 紧接着又提出假如有多通电话同时进来怎么处理 笔者就矜矜业业又去测试了普通电话的多通电话 结果发现当前有一个电话正在进来 又有另外一个电话打进来会直接挂断 那这显然不能达到老板想要同时接多个电话的要求了 笔者只要又矜矜业业研究怎么处理多通电话的逻辑了 其实笔者也快要被逼疯了 要求太多 笔者改烦了 需求老是变更 算了 还是先来讲讲通话的逻辑吧

1.这个软电话软件是拿linphone改的 所以本人的所有实现逻辑是基于linphone的
下面来看一下他的电话状态侦听

 // Core侦听器
        mCoreListener = new CoreListenerStub() {
            /**
             * 通话状态监听
             *
             * @param core
             * @param call
             * @param state
             * @param message
             */
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
                //获得的是手机的状态是否空闲  是否响铃 是否可用
                mSiphoneManager = new SiphoneManager(getApplicationContext());
                Log.i("[server] 通话状态 [", state, "]");
                if (state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia) {
                    // Toast.makeText(SiphoneService.this, "接收到来电", Toast.LENGTH_SHORT).show();

					//来电只有一个时才会振铃
                    if (core.getCallsNb() == 1){
                        setRingMode();
                    }
                    String phoneNum=call.getRemoteAddress().getUsername();
                    //点亮屏幕
                    acquireWake();
                    try {
                    //查找这个电话号码的相关信息  并在来电页面上显示
                        searchInfoByPhoneNumber(phoneNum);
                        if(!phoneNums.contains(phoneNum)){
                            phoneNums.add(phoneNum);
                        }
                        showNotification(phoneNum);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(core.getCallsNb() == 1){
                        onIncomingReceived();
                    } else if(core.getCallsNb() >= 2){
                     //电话大于或等于2时  首先第一个来的电话这时候已经跳转到来电界面  其他电话我采用一个悬浮窗的listview列表进行显示  可以对其他电话进行操作  可以选择接听或者挂断
                        Call[] calls = getCore().getCalls();
                        multiCallList.clear();
                        for(int i = 0; i < calls.length ; i ++){
                            Call.State callState = calls[i].getState();
                            //这里的flag的意思是把没有跳转到来电界面的电话 或者 不是通话中的电话都进行悬浮表示  
                            boolean flag = mCall == calls[i] || callState == Call.State.StreamsRunning || callState == Call.State.Resuming;
                            if(!flag){
                            //CallBean这个类包含了电话  和关于这个电话的来电信息
                                CallBean callBean = new CallBean(calls[i],details);
                                multiCallList.add(callBean);
                            }

                        }
                        .//代表是否绑定来电悬浮窗的服务  没有绑定先绑定  绑定了的话  可能要更新listview  这个现实电话悬浮窗的表格写了adapter 更新一下就好
                        if(!isMultiBound){
                            startMultiIncomingCallFloatService();
                        }else{
                            getMultiCallAdapter().notifyDataSetChanged();
                        }
                    }

                }else{
                //不是来电则关闭震动
                    closeVibrate();
                }
                if (state == Call.State.OutgoingInit) {
                    onCallStarted();
                } else if (state == Call.State.Connected) {
                    // This stats means the call has been established, let's start the CallActivity
//                    Intent intent = new Intent(LinphoneService.this, CallActivity.class);
                    // As it is the Service that is starting the activity, we have to give this flag
               if(isMultiBound && core.getCallsNb() == 1){
               //如果只有一通电话  就没有必要悬浮显示其他的电话了 直接显示当前通话中的电话就好
                        unbindService(mCallServiceConnection);
                        isMultiBound = false;
                    }else if(isMultiBound && core.getCallsNb() >= 2){
                    //电话数量大于等于2时  且此时有电话connected 代表这通电话马上在通话中了 那就没有悬浮显示这通电话  
                        Call[] calls = getCore().getCalls();
                        multiCallList.clear();
                        for(int i = 0; i < calls.length ; i ++){
                            if(call != calls[i]){
                                CallBean callBean = new CallBean(calls[i],details);
                                multiCallList.add(callBean);
                            }
                        }
                        //再次进行adapter的更新
                        getMultiCallAdapter().notifyDataSetChanged();
                    }
                    //这个电话被接通了 那就要跳转到通话界面的
                    onCallStarted();
                }else if(state == Call.State.End || state == Call.State.Released){
              //有电话挂断了 只有一通电话的话 那肯定也没必要悬浮显示其他电话  就解绑悬浮电话的服务
                    if(isMultiBound && core.getCallsNb() == 1){
                        unbindService(mCallServiceConnection);
                        isMultiBound = false;
                    }else if(isMultiBound && core.getCallsNb() >= 2){
                    //每次都需要将multiCallList清空一下  避免数据重复  将不是通话中的电话进行悬浮显示 
                        Call[] calls = getCore().getCalls();
                        multiCallList.clear();
                        for(int i = 0; i < calls.length ; i ++){
                            Call.State callState = calls[i].getState();
                            if(!(callState == Call.State.StreamsRunning || callState == Call.State.Resuming)){
                                CallBean callBean = new CallBean(calls[i],details);
                                multiCallList.add(callBean);
                            }

                        }
                        getMultiCallAdapter().notifyDataSetChanged();
                    }
                }

            }
        };

这个电话侦听器就是监听电话的来电状态 连接状态 通话状态 结束状态 暂停状态等
2.那既然有电话侦听器 就必然有通话界面 到底是哪个电话的处理吧 要显示正在通话中的电话号码吧

int callNum = mCore.getCallsNb();
        switch (callNum){
            case 0:
                break;
            case 1:
                onePhone = true;
                Call call = mCore.getCurrentCall();
                if(call != null){
//                    Call.State state = call.getState();
//                    if(state.equals(Call.State.IncomingEarlyMedia) || state.equals(Call.State.IncomingReceived)){
//                        onIncomingReceived();
//                        return;
//                    }
                    getPhoneInfo(call);
                }else{
                    //本来正在通话中  接起了别人打来的电话 导致当前通话被挂断 第二个电话挂断以后 当前通话还处于暂停状态  就直接恢复当前的电话状态为通话中
                    //mCore.getCurrentCall()是为空的  but mCore.getCalls()会重新找到我们被暂停的通话
                    Call[] pausedCall = mCore.getCalls();
                    if(pausedCall[0] != null){
                        getPhoneInfo(pausedCall[0]);
                    }
                }
                break;
            default:
                Call[] calls = mCore.getCalls();
                int len = calls.length-1;
                Call lastCall = null;
                Call.State state = null;
                //找到多通电话中  正在通话中的电话  比如 1082 先打进来 2082后打进来  我接了1082  那么2082的状态处于正在呼入状态  1082处于正在通话中
                do{
                    lastCall = calls[len];
                    state = lastCall.getState();
                    len--;
                }while ((!(state == Call.State.StreamsRunning || state == Call.State.Resuming)) && len >= 0);

                //表示当前至少的两通电话 没有正在通话中的  或许是暂停状态或许是来电状态 此处选择了对最后一通电话进行处理  
                if(len < 0 &&(!(state == Call.State.StreamsRunning || state == Call.State.Resuming))){
                    lastCall = calls[calls.length-1];
                }
                getPhoneInfo(lastCall);

                break;
        }


    }
    //来电
    private void onIncomingReceived() {
        Intent intent = new Intent(this, CallIncomingActivity.class);
        // 该Flag相当于Activity启动模式中的standard
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    private void getPhoneInfo(Call call) {
        Address address = call.getRemoteAddress();

        if (address != null) {
            mContactName.setText(address.getUsername());
        } else {
            mContactName.setText(getAddressDisplayName(address));
        }
        //电话正在通话中  显示对方的电话号码和时长
        if(call.getState() == Call.State.StreamsRunning || call.getState() == Call.State.Resuming){
            mCallTimer.setVisibility(View.VISIBLE);
            connecting.setVisibility(View.GONE);
            updateCurrentCallTimer(call);
        }else if(/*onePhone&&*/(call.getState() == Call.State.Paused || call.getState() == Call.State.Pausing)){
            //找到被暂停的电话
            call.resume();
            updateCurrentCallTimer(call);

            Call[] calls = SiphoneService.getCore().getCalls();
            multiCallList.clear();
            for(int i = 0; i < calls.length ; i ++){
                if(call != calls[i]){
                    CallBean callBean = new CallBean(calls[i],details);
                    multiCallList.add(callBean);
                }
            }
            getMultiCallAdapter().notifyDataSetChanged();
        }else if(call.getState() == Call.State.IncomingEarlyMedia || call.getState() == Call.State.IncomingReceived){
            //找到呼入中的电话  接起了另外一个电话
            onIncomingReceived();
        }
    }

主要是实时更新你到底接了哪通电话 显示这通电话的信息 要是这通电话结束了 就会直接恢复上一通电话 可能被你暂停掉了 不过一般情况下 我相信你把别人暂停了 别人为啥要等你这通电话打完了 我估计直接挂断了 下面的操作更绝了 悬浮显示的电话可以选择接通或者挂断 你接通后会把前面通话中的电话暂停掉 再来电话在接听 循环下去 笔者测试了三通电话 接通第三通电话 前面两通都处于暂停状态 然后骚操作来了 被暂停的电话还可以再点击一下继续接听 那其余两通电话继续被暂停 我就最开始被这个给绕的云里雾里 耗时一天半 终于处理好相关逻辑

3.下面讲一下悬浮窗的表现

public class FloatMultiCallWindowServices  extends Service {
    public static WindowManager mWindowManager;
    public static WindowManager.LayoutParams wmParams;
    private LayoutInflater inflater;
    //浮动布局view
    public static View mFloatingLayout;
    //容器父布局
    private LinearLayout smallFloatLayout;
    private ListView callList;
    private final String TAG = this.getClass().getSimpleName();
    private SharePManager spManager;

    public static MultiCallAdapter myAdapter;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //悬浮框点击事件的处理
        initFloating();
        return new FloatMultiCallWindowServices.MyBinder();
    }



    public class MyBinder extends Binder {
        public FloatMultiCallWindowServices getService() {
            return FloatMultiCallWindowServices.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //设置悬浮窗基本参数(位置、宽高等)
        initWindow();
    }

    /**
     * 设置悬浮框基本参数(位置、宽高等)
     */
    private void initWindow() {
        spManager = new SharePManager(this,SharePManager.USER_FILE_NAME);
        mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        //设置好悬浮窗的参数
        wmParams = getParams();
        // 悬浮窗默认显示以左上角为起始坐标
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;
        //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
        wmParams.x = mTouchStartX;
        wmParams.y = mTouchStartY;
        wmParams.y = mTouchStartY;

        //得到容器,通过这个inflater来获得悬浮窗控件
        inflater = LayoutInflater.from(getApplicationContext());
        // 获取浮动窗口视图所在布局
        mFloatingLayout = inflater.inflate(R.layout.layout_multicall, null);
        // 添加悬浮窗的视图
        mWindowManager.addView(mFloatingLayout, wmParams);
    }

    private WindowManager.LayoutParams getParams() {
        wmParams = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //背景透明
        wmParams.format = PixelFormat.RGBA_8888;
        //设置可以显示在状态栏上
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;

        wmParams.x = mTouchStartX;
        wmParams.y = mTouchStartY;

        //设置悬浮窗口长宽数据
        wmParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        return wmParams;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    private void initFloating() {
        smallFloatLayout = mFloatingLayout.findViewById(R.id.multi_callincoming);
        callList = mFloatingLayout.findViewById(R.id.multi_call_list);
        myAdapter = new MultiCallAdapter(this, multiCallList);
        callList.setAdapter(myAdapter);
        myAdapter.notifyDataSetChanged();
        //悬浮框触摸事件,设置悬浮框可拖动
        callList.setOnTouchListener(new FloatingListener());
    }



    //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
    private int mTouchStartX = 0, mTouchStartY = 70;
    private int mTouchCurrentX, mTouchCurrentY;
    //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
    private int mStartX, mStartY, mStopX, mStopY;
    //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
    private boolean isMove;

    private class FloatingListener implements View.OnTouchListener {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    isMove = false;
                    mTouchStartX = (int) event.getRawX();
                    mTouchStartY = (int) event.getRawY();
                    mStartX = (int) event.getX();
                    mStartY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mTouchCurrentX = (int) event.getRawX();
                    mTouchCurrentY = (int) event.getRawY();
                    wmParams.x += mTouchCurrentX - mTouchStartX;
                    wmParams.y += mTouchCurrentY - mTouchStartY;
                    spManager.putInt("mTouchStartX",wmParams.x);
                    spManager.putInt("mTouchStartY",wmParams.y);
                    mWindowManager.updateViewLayout(mFloatingLayout, wmParams);

                    mTouchStartX = mTouchCurrentX;
                    mTouchStartY = mTouchCurrentY;

                    break;
                case MotionEvent.ACTION_UP:
                    mStopX = (int) event.getX();
                    mStopY = (int) event.getY();
                    if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
                        isMove = true;
                    }
                    break;
                default:
                    break;
            }
            //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
            return isMove;
        }

    }


    public static MultiCallAdapter getMultiCallAdapter(){
        return myAdapter;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        removeWindowView();
        Log.i(TAG, "onDestroy");
    }

    private void removeWindowView() {
        if (mFloatingLayout != null) {
            //移除悬浮窗口
            Log.i(TAG, "removeView");
            mWindowManager.removeView(mFloatingLayout);
        }
    }
}

悬浮窗服务想要使用当然需要在AndroidManifest.xml中绑定 大家都懂得 这里就不写了 还要如何开启悬浮窗服务 和解绑悬浮窗服务
开启服务

  @RequiresApi(api = Build.VERSION_CODES.M)
    private void startMultiIncomingCallFloatService() {
        if (!Settings.canDrawOverlays(this)) {
            Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
            startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())));
        } else {
            //开启服务显示悬浮框
            Intent floatIntent = new Intent(this, FloatMultiCallWindowServices.class);
            isMultiBound = bindService(floatIntent, mCallServiceConnection, Context.BIND_AUTO_CREATE);
        }
    }

解绑服务
if(isMultiBound){
unbindService(mCallServiceConnection);
isBound = false;
}
4.悬浮窗的服务说完了 还有就是悬浮窗中电话用listview显示 的适配器

    public class MultiCallAdapter extends BaseAdapter {

    private List<CallBean> mData = new ArrayList<>();
    private LayoutInflater mInflater;//布局装载器对象

    public MultiCallAdapter(Context context, List<CallBean> mList) {
        mData = mList;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup) {

        ViewHolder viewHolder;
        //如果view未被实例化过,缓存池中没有对应的缓存
        if (convertView == null) {
            viewHolder = new ViewHolder();
            // 由于我们只需要将XML转化为View,并不涉及到具体的布局,所以第二个参数通常设置为null
            convertView = mInflater.inflate(R.layout.call_item, null);

            //对viewHolder的属性进行赋值
            viewHolder.phoneNum = (TextView) convertView.findViewById(R.id.tv_phoneNum);
            viewHolder.phoneInfo = (TextView) convertView.findViewById(R.id.tv_phone_info);
            viewHolder.acceptCall = (ImageView) convertView.findViewById(R.id.accept_call);
            viewHolder.declineCall = (ImageView) convertView.findViewById(R.id.hangup_call);
            viewHolder.callSelection = (LinearLayout) convertView.findViewById(R.id.call_selection);
            viewHolder.pausedCall = (ImageView) convertView.findViewById(R.id.call_paused);
            //通过setTag将convertView与viewHolder关联
            convertView.setTag(viewHolder);
        }else{//如果缓存池中有对应的view缓存,则直接通过getTag取出viewHolder
            viewHolder = (ViewHolder) convertView.getTag();
        }
        // 取出bean对象
        final CallBean bean = mData.get(position);
        final String callNum = bean.getCall().getRemoteAddress().getUsername();
        // 设置控件的数据
        viewHolder.phoneNum.setText(callNum);
        viewHolder.phoneInfo.setText(bean.getCallInfo());
        Call.State state = bean.getCall().getState();
        if(state == Call.State.Paused || state == Call.State.Pausing){
            viewHolder.callSelection.setVisibility(View.GONE);
            viewHolder.pausedCall.setVisibility(View.VISIBLE);
        }else if(state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia){
            viewHolder.callSelection.setVisibility(View.VISIBLE);
            viewHolder.pausedCall.setVisibility(View.GONE);
        }
        //可以选择接起电话或者直接就把他挂断了 就万事大吉
        viewHolder.acceptCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //SiphoneService.connectedCall.pause();
                cancelNotification(callNum);
                bean.getCall().accept();
            }
        });
        viewHolder.declineCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cancelNotification(callNum);
                bean.getCall().terminate();
            }
        });
//点击被暂停的电话  则可以使这通电话恢复通话中的状态  
        viewHolder.pausedCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Call call = bean.getCall();
                //找到被暂停的电话
                call.resume();

                Call[] calls = SiphoneService.getCore().getCalls();
                multiCallList.clear();
                for(int i = 0; i < calls.length ; i ++){
                    if(call != calls[i]){
                        CallBean callBean = new CallBean(calls[i],bean.getCallInfo());
                        multiCallList.add(callBean);
                    }
                }
                getMultiCallAdapter().notifyDataSetChanged();
            }
        });
        return convertView;

    }
}
// ViewHolder用于缓存控件,三个属性分别对应item布局文件的六个控件
class ViewHolder{
    public TextView phoneNum;
    public TextView phoneInfo;
    public LinearLayout callSelection;
    public ImageView acceptCall;
    public ImageView declineCall;
    public ImageView pausedCall;
}

最后贴一下call_item.xml布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <TextView
            android:id="@+id/tv_phoneNum"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="12345678901"
            android:textSize="12sp" />

        <TextView
            android:id="@+id/tv_phone_info"
            android:layout_width="100dp"
            android:layout_height="60dp"
            android:layout_weight="6"
            android:ellipsize="end"
            android:maxLines="4"
            android:padding="0dp"
            android:layout_gravity="center"
            android:gravity="center_vertical"
            android:text="822-1087/Mr Wang Li/王000/1111111/测试18-22名称被修改2/000000002/网络数据_已处理/WES软件服务/kbc2021-参观-000008//wes/"
            android:textSize="11sp" />
        <LinearLayout
            android:id="@+id/call_selection"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/accept_call"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_weight="1"
                android:padding="1dp"
                android:contentDescription="@string/content_description_accept"
                android:src="@drawable/call_start" />

            <ImageView
                android:id="@+id/hangup_call"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_weight="1"
                android:contentDescription="@string/content_description_accept"
                android:src="@drawable/hangup" />
        </LinearLayout>
        <ImageView
            android:id="@+id/call_paused"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:visibility="gone"
            android:contentDescription="@string/content_description_pause"
            android:src="@drawable/pause_click" />
    </LinearLayout>
</LinearLayout>

其余的就不多介绍了 我估计也没公司这么变态 要同时处理那么多的电话 就记录一下自己的艰辛 将来能想起在领导的各种无理要求下苦苦求生的自己
最后悬浮列表的每一项长这样
sip来电
要是有被暂停的电话 那接听和挂断的图标消失 变成暂停的图标 点击暂停的图标 会恢复当前的电话为通话中 这个悬浮窗是可以拖动的哦

本文地址:https://blog.csdn.net/weixin_45567968/article/details/108736465