乘风原创程序

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

    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>
    

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

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