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