我们实现了通信能力:
第一、我们支持sip固话通信功能,是一个完整的电话程序。我们支持sip呼出、sip呼入,打开/关闭扬声器,调整自己说话音量、调整对方音量,调起拨号盘进行指令输出。
第二、axb双呼。流程就是我们调用后端的接口,然后后端调用运营商的接口,然后运营商通过中间号打电话过来,并且打电话给我们要打的那个号码,然后我们之间建立了通信。
第三、支持axb双呼的自动接听,支持使用我们的程序替代系统默认电话程序。
这里我们先主要说下,我们的程序替代系统默认电话程序。
首先需要将我们的APP声明成一个具有电话功能的程序,即让系统觉得我们应用和手机自带的电话程序是一种类型的程序。
那么如何声明呢:
我们需要一个Activity和一个Service,我们就命名成PhoneActivity和PhoneService吧。其中PhoneService需要继承InCallService。
并且我们需要在AndroidManifest.xml中进行注册,代码如下:
<activity android:name=".PhoneActivity">
<!--region provides ongoing call UI必须有,否则调不起设置默认电话-->
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="tel"/>
</intent-filter>
<!--end region-->
<!--region provides dial UI-->
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<!--end region-->
</activity>
<service
android:name=".PhoneService"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true"/>
<intent-filter>
<action android:name="android.telecom.InCallService"/>
</intent-filter>
</service>
为什么要进行这样的注册呢?目的是使我们的程序具有电话程序特征。
PhoneService用于监听电话通信状态。比如说当有来电或者去电,onCallAdded方法会被回调,
当有接听或者挂断,我们可以通过onStateChanged监听到。
然后,当用户在设置中打开开关后,会弹出切换系统默认电话程序的系统弹框。代码如下:
package com.zdj.phone;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.telecom.TelecomManager;
import android.view.View;
import android.widget.ImageView;
import com.zdj.phone.core.PhoneManager;
import com.zdj.systemfuncationlibrary.SystemUtils;
public class PhoneMainActivity extends AppCompatActivity implements View.OnClickListener {
private ImageView switch_default_phone_application;
private SharedPreferences appInfoSP;
private SharedPreferences.Editor appInfoSPEditor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_phone_main);
switch_default_phone_application = findViewById(R.id.switch_default_phone_application);
switch_default_phone_application.setOnClickListener(this);
appInfoSP = getApplicationContext().getSharedPreferences(getPackageName() + "_appInfo", Context.MODE_PRIVATE);
appInfoSPEditor = appInfoSP.edit();
}
@Override
protected void onResume() {
super.onResume();
if (isDefaultPhoneApplication()) {
switch_default_phone_application.setTag(true);
switch_default_phone_application.setImageResource(R.drawable.switch_on);
} else {
switch_default_phone_application.setTag(false);
switch_default_phone_application.setImageResource(R.drawable.switch_off);
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.switch_default_phone_application) {
switchDefaultPhoneApplication();
}
}
private boolean isDefaultPhoneApplication() {
TelecomManager telecomManager = (TelecomManager) getSystemService(TELECOM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (telecomManager.getDefaultDialerPackage() != null) {
String defaultDialerPackage = telecomManager.getDefaultDialerPackage();
if (defaultDialerPackage.equals(getPackageName())) {
return true;
} else {
appInfoSPEditor.putString("defaultDialerPackage", defaultDialerPackage).commit();
}
}
}
return false;
}
private void switchDefaultPhoneApplication() {
if (!(boolean)switch_default_phone_application.getTag()) {
PhoneManager.setDefaultDialerSetWindow(this, getPackageName());
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (("huawei".equals(android.os.Build.BRAND.toLowerCase()) || "honor".equals(android.os.Build.BRAND.toLowerCase()))
&& !SystemUtils.isHarmonyOS()) {
Intent hwIntent = new Intent();
hwIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
hwIntent.setClassName("com.android.settings", "com.android.settings.Settings$PreferredListSettingActivity");
startActivity(hwIntent);
} else {
startActivity(new Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"));
}
} else {
PhoneManager.setDefaultDialerSetWindow(this, appInfoSP.getString("defaultDialerPackage", ""));
}
}
}
}
当将我们的应用设置成系统默认电话程序后,来电和去电都将是走我们的程序了(即走我们写的逻辑,弹出我们的通话界面)
下面,我们再贴出三个很重要的类,依次分别为PhoneActivity、PhoneService、PhoneManager。
PhoneActivity:
package com.zdj.phone.core;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import com.zdj.phone.R;
import com.zdj.phone.widget.DialPadDialog;
import com.zdj.zdjuilibrary.dialog.NormalInteractionDialog;
import java.util.List;
/**
* <pre>
* author : dejinzhang
* time : 2021/09/10
* desc : 使我们的应用成为一个电话程序必需的组件
* </pre>
*/
public class PhoneActivity extends Activity {
private static final int MY_PERMISSIONS_CALL_PHONE = 1;
private DialPadDialog dialPadDialog;
private TextView tv_input_numbers;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_phone);
tv_input_numbers = findViewById(R.id.tv_input_numbers);
dialPadDialog = new DialPadDialog(this, R.style.NotDarkenAndFirstDialogAnimationStyle);
dialPadDialog.setCanceledOnTouchOutside(false);
dialPadDialog.setCallback(new DialPadDialog.Callback() {
@Override
public void call(List<Character> list) {
if (checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(PhoneActivity.this, new String[]{Manifest.permission.CALL_PHONE}, MY_PERMISSIONS_CALL_PHONE);
} else {
callDeal(list);
}
}
@Override
public void collapse() {}
@Override
public void refreshShow(List<Character> list) {
if (list.size() > 0) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
stringBuilder.append(list.get(i));
}
tv_input_numbers.setText(stringBuilder);
tv_input_numbers.setVisibility(View.VISIBLE);
} else {
tv_input_numbers.setVisibility(View.GONE);
}
}
});
Uri uri = getIntent().getData();
if (uri != null) {
String uriString = uri.toString();
if (uriString != null && uriString.startsWith("tel:")) {
String phoneNumber = uriString.substring("tel:".length());
if (!TextUtils.isEmpty(phoneNumber)) {
char[] numbers = phoneNumber.toCharArray();
for (int i = 0; i < numbers.length; i++) {
dialPadDialog.getInputStringList().add(numbers[i]);
}
tv_input_numbers.setText(phoneNumber);
tv_input_numbers.setVisibility(View.VISIBLE);
}
}
}
dialPadDialog.show();
findViewById(R.id.iv_dial_pad).setOnClickListener(v -> dialPadDialog.show());
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_PERMISSIONS_CALL_PHONE) {
if (!(grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
NormalInteractionDialog normalInteractionDialog = new NormalInteractionDialog(this, R.style.DialogNoAnimationStyle);
normalInteractionDialog.init(true, false, null,
getString(R.string.call_permission_hint), getString(R.string.cancel), getString(R.string.go_open));
normalInteractionDialog.show();
normalInteractionDialog.setCallback(new NormalInteractionDialog.Callback() {
@Override
public void done() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
}
@Override
public void cancel() {}
});
} else {
callDeal(dialPadDialog.getInputStringList());
}
}
}
private void callDeal(List<Character> list) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
stringBuilder.append(list.get(i));
}
PhoneManager.call(this, stringBuilder.toString());
}
}
PhoneService:
package com.zdj.phone.core;
import android.content.Intent;
import android.telecom.Call;
import android.telecom.InCallService;
import com.zdj.phone.Config;
import com.zdj.phone.ScreenActivity;
import com.zdj.systemfuncationlibrary.LogUtils;
/**
* <pre>
* author : dejinzhang
* time : 2021/09/10
* desc : 服务
* </pre>
*/
public class PhoneService extends InCallService {
private static final String TAG = "PhoneService";
@Override
public void onCreate() {
super.onCreate();
}
private Call.Callback callback = new Call.Callback() {
@Override
public void onStateChanged(Call call, int state) {
super.onStateChanged(call, state);
LogUtils.i(TAG, "state:" + state);
if (state == Call.STATE_ACTIVE) {
LogUtils.i(TAG, "----接听一个电话----" + call.toString());
//在这里可以发送广播,通知通话界面变化
Intent intent = new Intent();
intent.setAction(ScreenActivity.CALL_NET_ING);
sendBroadcast(intent);
} else if (state == Call.STATE_DISCONNECTED) {
LogUtils.i(TAG, "---挂断一个电话----" + call.toString());
//在这里可以发送广播,通知通话界面变化
Intent intent = new Intent();
intent.setAction(ScreenActivity.CALL_NET_END);
sendBroadcast(intent);
// call.unregisterCallback(callback); //其实这句代码也可以注释掉,因为当连接断开时,即电话挂断的时候,会在进入这个监听后,再回调onCallRemoved,我们在onCallRemoved中取消注册回调即可
}/* else if (state == Call.STATE_CONNECTING) {
LogUtils.i(TAG, "----呼叫,正在建立连接----" + call.toString());
//我们也可以根据业务在这里执行相应动作。
}*/
}
};
@Override
public void onCallAdded(Call call) {
super.onCallAdded(call);
LogUtils.i(TAG, "----添加一个电话----");
call.registerCallback(callback); //注册回调
Call.Details details = call.getDetails();
String phoneNumber = "";
int state;
if (details != null && details.getHandle() != null) {
phoneNumber = details.getHandle().getSchemeSpecificPart();
}
state = call.getState();
LogUtils.i(TAG, "phoneNumber:" + phoneNumber);
LogUtils.i(TAG, "callState:" + state);
Config.call = call;
//在这里可以跳转到通话界面
Intent intent = new Intent();
intent.setClass(this, ScreenActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra("phoneNum", phoneNumber);
if (state == Call.STATE_RINGING) {
intent.putExtra("from", ScreenActivity.NET_IN);
} else if (state == Call.STATE_CONNECTING) {
intent.putExtra("from", ScreenActivity.NET_OUT);
}
startActivity(intent);
}
@Override
public void onCallRemoved(Call call) {
super.onCallRemoved(call);
LogUtils.i(TAG, "----移除一个电话----" + call.toString());
call.unregisterCallback(callback);
}
}
PhoneManager:
package com.zdj.phone.core;
import android.Manifest;
import android.app.Activity;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.telecom.Call;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import static android.content.Context.AUDIO_SERVICE;
import static android.content.Context.TELECOM_SERVICE;
/**
* <pre>
* author : dejinzhang
* time : 2021/09/09
* desc : 电话管理器。该类中包含了对电话操作的各类方法:
* 接听、挂断,以及弹出设置应用为系统默认电话程序(会弹出切换系统电话程序的系统弹框)的方法。
* </pre>
*/
public class PhoneManager {
/**
* 设置应用为系统默认电话程序(会弹出切换系统默认电话程序的系统弹框)
* 注意:
* Android 10以上使用RoleManager
* Android 6 ~ Android 9以上使用TelecomManager
* Android 6以下不支持(因为Android 6以下call.answer call.disconnect等api不能用)
* @param context 上下文
* @param packageName 需要设置为系统默认电话程序的应用包名
*/
public static void setDefaultDialerSetWindow(Context context, String packageName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
if (roleManager != null) {
Intent roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
((Activity)context).startActivityForResult(roleRequestIntent, 999);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
TelecomManager telecomManager = (TelecomManager) context.getSystemService(TELECOM_SERVICE);
if (telecomManager != null) {
Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
context.startActivity(intent);
}
}
}
/**
* 接听电话
* @param call call对象
*/
public static void answer(Call call) {
if (call != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
call.answer(VideoProfile.STATE_AUDIO_ONLY);
}
}
}
/**
* 断开电话,包括来电时的拒接以及接听后的挂断
* @param call call对象
*/
public static void hangup(Call call) {
if (call != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
call.disconnect();
}
}
}
/**
* 打电话
* @param context 上下文
* @param phoneNumber 所要拨打的号码
*/
public static void call(Context context, String phoneNumber) {
if (context.checkSelfPermission(Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phoneNumber));
context.startActivity(intent);
}
}
/**
* 挂起
* @param call call对象
*/
public static void hold(Call call) {
if (call != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
call.hold();
}
}
}
/**
* 取消挂起
* @param call call对象
*/
public static void unHold(Call call) {
if (call != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
call.unhold();
}
}
}
/**
* 指示此 {@code Call} 播放双音多频信号 (DTMF) 音调。
* @param call call对象
* @param digit 输入的指令数字
*/
public static void playDtmfTone(Call call, char digit) {
if (call != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
call.playDtmfTone(digit);
call.stopDtmfTone();
}
}
}
/**
* 打开/关闭扬声器
* @param context 上下文
* @param on true 打开扬声器 false 关闭扬声器
*/
public static void switchSpeaker(Context context, boolean on) {
AudioManager audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
if (audioManager != null) {
audioManager.setSpeakerphoneOn(on);
if (!on && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
}
}
}
关于PhoneManager,我们这里再简单介绍下,PhoneManager是一个包括了接听、挂断等很多操作的管理器。
ok,接下来,我们就是还需要一个通话界面了,我们再贴出我们的通话界面对应的ScreenActivity以及activity_screen.xml。
ScreenActivity:
package com.zdj.phone;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.zdj.phone.adapter.DialPadAdapter;
import com.zdj.phone.core.PhoneManager;
import com.zdj.systemfuncationlibrary.LogUtils;
import com.zdj.systemfuncationlibrary.MatchUtils;
import com.zdj.systemfuncationlibrary.SystemUtils;
import com.zdj.systemfuncationlibrary.UiUtils;
import com.zdj.zdjuilibrary.popup_window.VolumePopupWindow;
/**
* <pre>
* author : dejinzhang
* time : 2021/09/12
* desc : 通话界面
* </pre>
*/
public class ScreenActivity extends Activity implements SensorEventListener {
private static final String TAG = "ScreenActivity";
public static final String CALL_NET_END = "call_net_end";
public static final String CALL_NET_ING = "call_net_ing";
public static final String NET_IN = "net_in";
public static final String NET_OUT = "net_out";
private TextView phoneNumTxt, phoneInfo, inputTxt, statusTxt,
tv_tel_microphone, tv_tel_receiver, tv_on_speakerphone, tv_dial;
private LinearLayout ll_no, ll_ok, ll_relevant_operate;
private GridView gv_dial_pad;
private VolumePopupWindow volumePopupWindow; //调节音量的popupWindow
private String phoneNum = "";
private String phoneFrom = ""; //标记是呼出,还是呼入,即打电话还是接电话。"net_in"---呼入;"net_out"---呼出。
private boolean isPhoneHide; //是否对号码进行脱敏处理
private boolean isSip; //是否是sip固话
private boolean isAxbTwoAutoAnswer; //是否是axb双呼自动接听
private boolean isHeadset; //是否有耳机
private boolean isRing; //是否正在响铃
private boolean isNetting;
private TelephonyManager telephonyManager; //电话管理器
private AudioManager audioManager; //声音管理器
// private SensorManager sensorManager; //传感器管理器
// private Sensor mProximity; //传感器实例
private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener; //更新系统对音频焦点时要调用的回调的接口实例
private static final int MY_PERMISSIONS_RECORD_AUDIO = 1;
private final BroadcastReceiver mEndCallReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
LogUtils.i(TAG, "intent.getAction()===" + intent.getAction());
if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {
telephonyManager = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
switch (telephonyManager.getCallState()) {
case TelephonyManager.CALL_STATE_RINGING: //来电响铃
LogUtils.i(TAG, "---来电话了---");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
LogUtils.i(TAG, "---接听电话---"); //接听电话
Message message = Message.obtain();
message.what = 1;
message.obj = intent.getAction();
callbackHandler.sendMessage(message);
break;
case TelephonyManager.CALL_STATE_IDLE: //挂断电话
LogUtils.i(TAG, "---挂断电话---");
Toast.makeText(ScreenActivity.this, getString(R.string.call_ended), Toast.LENGTH_SHORT).show();
break;
}
} else if (intent.getAction().equals("android.intent.action.HEADSET_PLUG")) {
if (intent.hasExtra("state")) {
if (intent.getIntExtra("state", 0) == 0) {
isHeadset = false;
} else if (intent.getIntExtra("state", 0) == 1) {
isHeadset = true;
if (isRing) {
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
audioManager.setSpeakerphoneOn(false);
audioManager.setMode(AudioManager.MODE_IN_CALL);
} else {
audioManager.setSpeakerphoneOn(false);
}
}
}
} else {
Message msg = Message.obtain();
msg.what = 1;
msg.obj = intent.getAction();
callbackHandler.sendMessage(msg);
}
}
};
private final Handler callbackHandler = new CallbackHandler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
// mProximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
mAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
LogUtils.i(TAG, "---失去焦点---");
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
LogUtils.i(TAG, "---获得焦点---");
}
};
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_screen);
if (getIntent().hasExtra("isPhoneHide")) {
isPhoneHide = getIntent().getBooleanExtra("isPhoneHide", false);
}
if (getIntent().hasExtra("isAxbTwoAutoAnswer")) {
isAxbTwoAutoAnswer = getIntent().getBooleanExtra("isAxbTwoAutoAnswer", false);
}
if (getIntent().hasExtra("from")) {
phoneFrom = getIntent().getStringExtra("from");
}
initView();
if (getIntent().hasExtra("phoneNum")) {
phoneNum = getIntent().getStringExtra("phoneNum");
if (!TextUtils.isEmpty(phoneNum)) {
if (phoneNum.contains("sip:") && phoneNum.contains("@")) {
isSip = true;
phoneNum = phoneNum.substring(phoneNum.indexOf(":") + 1, phoneNum.indexOf("@"));
if (phoneFrom.equals(NET_IN)) {
//调用后端接口获取客户信息并显示在界面上。如果获取不到,表示来电不在库中,此时不显示即可。
}
} else {
isSip = false;
}
} else {
isSip = true; //默认按sip来处理
phoneNum = "未知号码";
}
}
if (phoneFrom.equals(NET_IN)) { //呼入
isRing = true;
audioManager.requestAudioFocus(mAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
if (!isAxbTwoAutoAnswer) {
ll_ok.setVisibility(View.VISIBLE);
ll_no.setVisibility(View.VISIBLE);
SystemUtils.playSound(this, R.raw.ring, true); //播放铃声
callStatus = 2;
} else {
ll_no.setVisibility(View.VISIBLE);
PhoneManager.answer(Config.call);
callStatus = 3;
registerReceiver(mEndCallReceiver, new IntentFilter(CALL_NET_END));
animationHandler.postDelayed(timeRunnable, 1000);
ll_relevant_operate.setVisibility(View.VISIBLE);
tv_tel_receiver.setVisibility(View.GONE);
tv_tel_microphone.setVisibility(View.GONE);
}
} else if (phoneFrom.equals(NET_OUT)) { //呼出
audioManager.requestAudioFocus(mAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
ll_no.setVisibility(View.VISIBLE);
ll_relevant_operate.setVisibility(View.VISIBLE);
callStatus = CONNECTING;
if (!isSip) {
tv_tel_receiver.setVisibility(View.GONE);
tv_tel_microphone.setVisibility(View.GONE);
}
}
StringBuilder stringBuilder = new StringBuilder();
if (isPhoneHide) {
if (!phoneNum.equals("未知号码")) {
stringBuilder.append(UiUtils.desensitizePhoneNum(phoneNum));
} else {
stringBuilder.append(phoneNum);
}
} else {
if (MatchUtils.isLegalMobilePhoneNum(phoneNum)) {
stringBuilder.append(phoneNum.substring(0, 3)).append("-").append(phoneNum.substring(3, 7)).append("-").append(phoneNum.substring(7));
} else if (phoneNum.startsWith("400") && phoneNum.length() == 10) {
stringBuilder.append(phoneNum.substring(0, 3)).append("-").append(phoneNum.substring(3, 6)).append("-").append(phoneNum.substring(6));
} else if (phoneNum.length() > 5) {
if (phoneNum.startsWith("01") || phoneNum.startsWith("02")) {
stringBuilder.append(phoneNum.substring(0, 3)).append("-").append(phoneNum.substring(3));
} else {
stringBuilder.append(phoneNum.substring(0, 4)).append("-").append(phoneNum.substring(4));
}
} else {
stringBuilder.append(phoneNum);
}
}
phoneNumTxt.setText(stringBuilder);
if (!isAxbTwoAutoAnswer) {
registerReceiver(mEndCallReceiver, new IntentFilter(CALL_NET_END));
registerReceiver(mEndCallReceiver, new IntentFilter(CALL_NET_ING));
if (isSip) {
registerReceiver(mEndCallReceiver, new IntentFilter("android.intent.action.PHONE_STATE"));
registerReceiver(mEndCallReceiver, new IntentFilter("android.intent.action.HEADSET_PLUG"));
}
animationHandler.postDelayed(timeRunnable, 1000);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, MY_PERMISSIONS_RECORD_AUDIO);
}
}
@Override
protected void onResume() {
super.onResume();
/*//注册传感器
if (sensorManager != null) {
sensorManager.registerListener(this, mProximity, SensorManager.SENSOR_DELAY_NORMAL);
}*/
//如果是sip,这里要发送CALL_NET_END的广播
}
@Override
protected void onPause() {
super.onPause();
/*//取消注册传感器
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}*/
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
try {
unregisterReceiver(mEndCallReceiver);
} catch (Exception e) {
e.printStackTrace();
}
if (!isAxbTwoAutoAnswer) {
animationHandler.removeCallbacks(timeRunnable);
}
currentTime = 0;
audioManager.abandonAudioFocus(mAudioFocusChangeListener); //释放焦点
//如果是sip的话,这里要进行一些恢复操作
super.onDestroy();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case MY_PERMISSIONS_RECORD_AUDIO:
if (!(grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setMessage(getString(R.string.microphone_permission_request_hint_text))
.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
ActivityCompat.requestPermissions(ScreenActivity.this, new String[]{Manifest.permission.RECORD_AUDIO}, MY_PERMISSIONS_RECORD_AUDIO);
})
.setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
if (isSip) {
//调用sip的挂断接口
} else {
PhoneManager.hangup(Config.call);
}
});
builder.create().show();
}
break;
}
}
private void initView() {
ll_no = findViewById(R.id.ll_no);
ll_ok = findViewById(R.id.ll_ok);
ImageView iv_no = findViewById(R.id.iv_no);
ImageView iv_ok = findViewById(R.id.iv_ok);
phoneNumTxt = findViewById(R.id.call_phone_num);
phoneInfo = findViewById(R.id.phone_info);
inputTxt = findViewById(R.id.input_text);
statusTxt = findViewById(R.id.call_status);
ll_relevant_operate = findViewById(R.id.ll_relevant_operate);
tv_tel_microphone = findViewById(R.id.tv_tel_microphone);
tv_tel_receiver = findViewById(R.id.tv_tel_receiver);
tv_on_speakerphone = findViewById(R.id.tv_on_speakerphone);
tv_dial = findViewById(R.id.tv_dial);
gv_dial_pad = findViewById(R.id.gv_dial_pad);
final DialPadAdapter dialPadAdapter = new DialPadAdapter(this, DialPadAdapter.SCREEN_UI);
gv_dial_pad.setAdapter(dialPadAdapter);
gv_dial_pad.setSelector(new ColorDrawable(Color.TRANSPARENT));
gv_dial_pad.setOnItemClickListener((parent, view, position, id) -> {
String str = dialPadAdapter.getList().get(position).getMainText();
String existsText = inputTxt.getText().toString();
StringBuilder stringBuilder = new StringBuilder();
if (!TextUtils.isEmpty(existsText)) {
stringBuilder.append(existsText);
}
stringBuilder.append(str);
inputTxt.setText(stringBuilder);
inputTxt.setVisibility(View.VISIBLE);
if (isSip) {
//调用sip的输入指令的接口
} else {
PhoneManager.playDtmfTone(Config.call, str.charAt(0));
}
});
View.OnClickListener clickListener = v -> {
if (v.getId() == R.id.tv_tel_microphone) {
tv_tel_microphone.setTag(!((boolean) tv_tel_microphone.getTag()));
tv_tel_microphone.setTextColor(((boolean) tv_tel_microphone.getTag()) ? Color.parseColor("#D6E3F4") : Color.parseColor("#62696C"));
tv_tel_microphone.setCompoundDrawablesRelativeWithIntrinsicBounds(0, ((boolean) tv_tel_microphone.getTag()) ? R.drawable.tel_microphone_highlight : R.drawable.tel_microphone, 0, 0);
if ((boolean)tv_tel_receiver.getTag()) {
tv_tel_receiver.setTag(false);
tv_tel_receiver.setTextColor(Color.parseColor("#62696C"));
tv_tel_receiver.setCompoundDrawablesRelativeWithIntrinsicBounds(0, R.drawable.tel_receiver, 0, 0);
if (volumePopupWindow != null) {
volumePopupWindow.dismiss();
}
}
if ((boolean) tv_tel_microphone.getTag()) {
showVolumePopupWindow(tv_tel_microphone);
} else {
if (volumePopupWindow != null) {
volumePopupWindow.dismiss();
}
}
} else if (v.getId() == R.id.tv_tel_receiver) {
tv_tel_receiver.setTag(!((boolean) tv_tel_receiver.getTag()));
tv_tel_receiver.setTextColor(((boolean) tv_tel_receiver.getTag()) ? Color.parseColor("#D6E3F4") : Color.parseColor("#62696C"));
tv_tel_receiver.setCompoundDrawablesRelativeWithIntrinsicBounds(0, ((boolean) tv_tel_receiver.getTag()) ? R.drawable.tel_receiver_highlight : R.drawable.tel_receiver, 0, 0);
if ((boolean)tv_tel_microphone.getTag()) {
tv_tel_microphone.setTag(false);
tv_tel_microphone.setTextColor(Color.parseColor("#62696C"));
tv_tel_microphone.setCompoundDrawablesRelativeWithIntrinsicBounds(0, R.drawable.tel_microphone, 0, 0);
if (volumePopupWindow != null) {
volumePopupWindow.dismiss();
}
}
if ((boolean)tv_tel_receiver.getTag()) {
showVolumePopupWindow(tv_tel_receiver);
} else {
if (volumePopupWindow != null) {
volumePopupWindow.dismiss();
}
}
} else if (v.getId() == R.id.tv_on_speakerphone) {
tv_on_speakerphone.setTag(!((boolean) tv_on_speakerphone.getTag()));
tv_on_speakerphone.setTextColor(((boolean) tv_on_speakerphone.getTag()) ? Color.parseColor("#D6E3F4") : Color.parseColor("#62696C"));
tv_on_speakerphone.setCompoundDrawablesRelativeWithIntrinsicBounds(0, ((boolean) tv_on_speakerphone.getTag()) ? R.drawable.on_speakerphone_highlight : R.drawable.on_speakerphone, 0, 0);
PhoneManager.switchSpeaker(ScreenActivity.this, (boolean)tv_on_speakerphone.getTag());
} else if (v.getId() == R.id.tv_dial) {
tv_dial.setTag(!((boolean)tv_dial.getTag()));
if ((boolean)tv_dial.getTag()) {
gv_dial_pad.setVisibility(View.VISIBLE);
} else {
gv_dial_pad.setVisibility(View.GONE);
}
}
};
tv_tel_microphone.setOnClickListener(clickListener);
tv_tel_microphone.setTag(false);
tv_tel_receiver.setOnClickListener(clickListener);
tv_tel_receiver.setTag(false);
tv_on_speakerphone.setOnClickListener(clickListener);
tv_on_speakerphone.setTag(false);
tv_dial.setOnClickListener(clickListener);
tv_dial.setTag(false);
//接听电话
iv_ok.setOnClickListener(v -> {
if (phoneFrom.equals(NET_IN)) {
if (isSip) {
//调用sip的接听电话的接口
Intent intent = new Intent();
intent.setAction(ScreenActivity.CALL_NET_ING);
sendBroadcast(intent);
ll_relevant_operate.setVisibility(View.VISIBLE);
} else {
PhoneManager.answer(Config.call);
ll_relevant_operate.setVisibility(View.VISIBLE);
tv_tel_receiver.setVisibility(View.GONE);
tv_tel_microphone.setVisibility(View.GONE);
}
}
});
//挂断电话
iv_no.setOnClickListener(v -> {
if (phoneFrom.equals(NET_IN) || phoneFrom.equals(NET_OUT)) {
if (isSip) {
//调用sip的挂断电话的接口
} else {
PhoneManager.hangup(Config.call);
}
}
});
}
//未知
private static final int UNKNOWN = 0;
//正在呼叫
private static final int CONNECTING = 1;
//正在响铃(等待接听)
private static final int RING = 2;
//通话中
private static final int CONNECTED = 3;
private int callStatus = UNKNOWN;
private int currentTime;
private Runnable timeRunnable = new Runnable() {
@Override
public void run() {
currentTime++;
switch (callStatus) {
case CONNECTING:
if (currentTime % 4 == 0) {
statusTxt.setText("正在呼叫");
} else if (currentTime % 4 == 1) {
statusTxt.setText(" 正在呼叫.");
} else if (currentTime % 4 == 2) {
statusTxt.setText(" 正在呼叫..");
} else if (currentTime % 4 == 3) {
statusTxt.setText(" 正在呼叫...");
}
break;
case RING:
break;
case CONNECTED:
statusTxt.setText(new StringBuilder().append(UiUtils.formatAA(currentTime / 60)).append(":").append(UiUtils.formatAA(currentTime % 60)));
break;
}
animationHandler.postDelayed(timeRunnable, 1000);
}
};
private final Handler animationHandler = new Handler();
/**
* 注意:该方法目前不会被回调,因为通过传感器判断距离,进一步自动打开/关闭扬声器的功能,目前禁掉了,但是代码保留,需要用开放即可。
* @param event
*/
@Override
public void onSensorChanged(SensorEvent event) {
if (isHeadset) {
return;
}
float[] its = event.values;
LogUtils.i(TAG, "its[0]:" + its[0]);
if (!(boolean) tv_on_speakerphone.getTag()) { //表示未手动打开扬声器
if (its[0] == 0.0) { //贴近手机
PhoneManager.switchSpeaker(this, false);
tv_on_speakerphone.setTextColor(Color.parseColor("#62696C"));
tv_on_speakerphone.setCompoundDrawablesRelativeWithIntrinsicBounds(0, R.drawable.on_speakerphone, 0, 0);
tv_on_speakerphone.setTag(false);
} else { //远离手机
PhoneManager.switchSpeaker(this, true);
tv_on_speakerphone.setTextColor(Color.parseColor("#D6E3F4"));
tv_on_speakerphone.setCompoundDrawablesRelativeWithIntrinsicBounds(0, R.drawable.on_speakerphone_highlight, 0, 0);
tv_on_speakerphone.setTag(true);
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
private void showVolumePopupWindow(final View targetView) {
if (volumePopupWindow == null) {
volumePopupWindow = new VolumePopupWindow(LayoutInflater.from(this).inflate(R.layout.popup_window_volume, null),
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
volumePopupWindow.setCallback(progress -> {
if (targetView.getId() == R.id.tv_tel_microphone) {
//调用sip的接口调节话筒的音量
} else if (targetView.getId() == R.id.tv_tel_receiver) {
//调用sip的接口调节听筒的音量
}
});
if (targetView.getId() == R.id.tv_tel_microphone) {
// volumePopupWindow.initProgress(); //initProgress方法中的参数使用sip的接口获取
} else if (targetView.getId() == R.id.tv_tel_receiver) {
// volumePopupWindow.initProgress(); //initProgress方法中的参数使用sip的接口获取
}
int[] location = new int[2];
targetView.getLocationOnScreen(location);
int temp_width = (UiUtils.dpToPx(this, 80) - targetView.getWidth()) / 2;
volumePopupWindow.showAtLocation(targetView, Gravity.NO_GRAVITY, location[0] - temp_width, location[1] - UiUtils.dpToPx(this, 90));
}
class CallbackHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 1) {
String action = String.valueOf(msg.obj);
if (action.equals(CALL_NET_ING)) { //正在通话
if (isNetting) {
return;
}
isNetting = true;
isRing = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
audioManager.setSpeakerphoneOn(false);
audioManager.setMode(AudioManager.MODE_IN_CALL);
} else {
audioManager.setSpeakerphoneOn(false);
}
currentTime = 0;
callStatus = CONNECTED;
if (ll_ok != null) {
ll_ok.setVisibility(View.GONE);
}
if (ll_no != null) {
ll_no.setVisibility(View.VISIBLE);
}
SystemUtils.stopSound(); //停止播放铃声
} else if (action.equals(CALL_NET_END)) {
if (ll_ok != null) {
ll_ok.setVisibility(View.GONE);
}
if (ll_no != null) {
ll_no.setVisibility(View.GONE);
}
isRing = false;
LogUtils.i(TAG, "---call_net_end---");
SystemUtils.stopSound(); //停止播放铃声
statusTxt.setText(getString(R.string.call_ended));
Toast.makeText(ScreenActivity.this, getString(R.string.call_ended), Toast.LENGTH_SHORT).show();
finish();
} else if (action.equals("android.intent.action.PHONE_STATE")) {
//调用sip的挂断电话的接口
}
}
}
}
}
activity_screen.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#2f343a">
<!--号码-->
<TextView
android:id="@+id/call_phone_num"
android:layout_width="match_parent"
android:layout_height="90dp"
android:gravity="bottom|center_horizontal"
android:text="@string/unknown_call"
android:textColor="@color/white"
android:textSize="32dp"/>
<!--电话归属信息:比如客户名称、联系人姓名-->
<TextView
android:id="@+id/phone_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:layout_marginTop="10dp"
android:lineSpacingExtra="5dp"
android:gravity="center"
android:visibility="gone"/>
<!--用户输入的指令,比如拨打10086的时候调起软键盘输入1-->
<TextView
android:id="@+id/input_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="18dp"
android:layout_marginTop="10dp"
android:visibility="gone"/>
<!--通话状态,比如显示"正在呼叫"或者当前的通话时长等等-->
<TextView
android:id="@+id/call_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:textColor="@color/white"
android:textSize="16dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<com.zdj.zdjuilibrary.view.RippleView
android:id="@+id/layout_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff2f343a"
android:fitsSystemWindows="true"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/call_icon"/>
</com.zdj.zdjuilibrary.view.RippleView>
<GridView
android:id="@+id/gv_dial_pad"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:verticalSpacing="10dp"
android:horizontalSpacing="40dp"
android:numColumns="3"
android:layout_gravity="center"
android:background="#ff2f343a"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:visibility="gone"/>
</FrameLayout>
<LinearLayout
android:id="@+id/ll_relevant_operate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:background="#272e34"
android:visibility="gone">
<TextView
android:id="@+id/tv_dial"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dial"
android:textSize="14dp"
android:textColor="#62696c"
android:drawablePadding="10dp"
android:drawableTop="@drawable/dial_pad"
android:gravity="center"/>
<TextView
android:id="@+id/tv_tel_microphone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/microphone"
android:textSize="14dp"
android:textColor="#62696C"
android:drawablePadding="10dp"
android:drawableTop="@drawable/tel_microphone"
android:gravity="center"
android:layout_marginLeft="70dp"/>
<TextView
android:id="@+id/tv_tel_receiver"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/earpiece"
android:textSize="14dp"
android:textColor="#62696c"
android:drawablePadding="10dp"
android:drawableTop="@drawable/tel_receiver"
android:gravity="center"
android:layout_marginLeft="70dp"/>
<TextView
android:id="@+id/tv_on_speakerphone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/speaker"
android:textSize="14dp"
android:textColor="#62696C"
android:drawablePadding="10dp"
android:drawableTop="@drawable/on_speakerphone"
android:gravity="center"
android:layout_marginLeft="70dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#272c32"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/ll_no"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp"
android:layout_marginBottom="25dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="@+id/iv_no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/hang_up"/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_ok"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp"
android:layout_marginBottom="25dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="@+id/iv_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/answer"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
ok,到此为止,我们就讲完了。
完整的代码,可以移步我的github: