简单来电秀功能,效果如图:
底部附上demo
一、新建一个PhoneCallService服务,在服务中监听来电等状态,且控制UI显示
public class PhoneCallService extends InCallService {private final Call.Callback callback = new Call.Callback() {@Overridepublic void onStateChanged(Call call, int state) {super.onStateChanged(call, state);switch (state) {case Call.STATE_ACTIVE: {break;}case Call.STATE_DISCONNECTED: {ActivityStack.getInstance().finishActivity(PhoneCallActivity.class);break;}}}};@Overridepublic void onCallAdded(Call call) {super.onCallAdded(call);call.registerCallback(callback);PhoneCallManager.call = call;CallType callType = null;if (call.getState() == Call.STATE_RINGING) {callType = CallType.CALL_IN;} else if (call.getState() == Call.STATE_CONNECTING) {callType = CallType.CALL_OUT;}if (callType != null) {Call.Details details = call.getDetails();String phoneNumber = details.getHandle().getSchemeSpecificPart();PhoneCallActivity.actionStart(this, phoneNumber, callType);}}@Overridepublic void onCallRemoved(Call call) {super.onCallRemoved(call);call.unregisterCallback(callback);PhoneCallManager.call = null;}public enum CallType {CALL_IN,CALL_OUT,}
二、在MainActivity中设置应用为默认来电主题,与权限设置
public class MainActivity extends AppCompatActivity {@SuppressLint("UseSwitchCompatOrMaterialCode")private Switch switchPhoneCall;@SuppressLint("UseSwitchCompatOrMaterialCode")private Switch switchListenCall;private CompoundButton.OnCheckedChangeListener switchCallCheckChangeListener;private final ActivityResultLauncher<Intent> dialerRequestLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {if (result.getResultCode() == Activity.RESULT_OK) {Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用",Toast.LENGTH_SHORT).show();}});@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {switchPhoneCall = findViewById(R.id.switch_default_phone_call);switchListenCall = findViewById(R.id.switch_call_listenr);switchPhoneCall.setOnClickListener(v -> {// 发起将本应用设为默认电话应用的请求,仅支持 Android M 及以上if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (switchPhoneCall.isChecked()) {// Android 10 之后需要通过 RoleManager 修改默认电话应用if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);dialerRequestLauncher.launch(intent);} else {Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName());startActivity(intent);}} else {// 取消时跳转到默认设置页面startActivity(new Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"));}} else {Toast.makeText(MainActivity.this, "Android 6.0 以上才支持修改默认电话应用!", Toast.LENGTH_LONG).show();switchPhoneCall.setChecked(false);}});// 检查是否开启了权限switchCallCheckChangeListener = (buttonView, isChecked) -> {if (isChecked && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M&& !Settings.canDrawOverlays(MainActivity.this)) {// 请求 悬浮框 权限askForDrawOverlay();// 未开启时清除选中状态,同时避免回调switchListenCall.setOnCheckedChangeListener(null);switchListenCall.setChecked(false);switchListenCall.setOnCheckedChangeListener(switchCallCheckChangeListener);return;}if (isChecked && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&ContextCompat.checkSelfPermission(this,Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {Toast.makeText(this, "缺少获取电话状态权限", Toast.LENGTH_SHORT).show();return;}Intent callListener = new Intent(MainActivity.this, CallListenerService.class);if (isChecked) {startService(callListener);Toast.makeText(this, "电话监听服务已开启", Toast.LENGTH_SHORT).show();} else {stopService(callListener);Toast.makeText(this, "电话监听服务已关闭", Toast.LENGTH_SHORT).show();}};switchListenCall.setOnCheckedChangeListener(switchCallCheckChangeListener);}private void askForDrawOverlay() {AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle("允许显示悬浮框").setMessage("为了使电话监听服务正常工作,请允许这项权限").setPositiveButton("去设置", (dialog, which) -> {openDrawOverlaySettings();dialog.dismiss();}).setNegativeButton("稍后再说", (dialog, which) -> dialog.dismiss()).create();//noinspection ConstantConditionsalertDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);alertDialog.show();}/*** 跳转悬浮窗管理设置界面*/private void openDrawOverlaySettings() {if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Android M 以上引导用户去系统设置中打开允许悬浮窗// 使用反射是为了用尽可能少的代码保证在大部分机型上都可用try {Context context = this;Class clazz = Settings.class;Field field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");Intent intent = new Intent(field.get(null).toString());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setData(Uri.parse("package:" + context.getPackageName()));context.startActivity(intent);} catch (Exception e) {Toast.makeText(this, "请在悬浮窗管理中打开权限", Toast.LENGTH_LONG).show();}}}@Overrideprotected void onResume() {super.onResume();switchPhoneCall.setChecked(isDefaultPhoneCallApp());switchListenCall.setChecked(isServiceRunning(CallListenerService.class));}/*** Android M 及以上检查是否是系统默认电话应用*/public boolean isDefaultPhoneCallApp() {if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE);if (manger != null && manger.getDefaultDialerPackage() != null) {return manger.getDefaultDialerPackage().equals(getPackageName());}}return false;}public boolean isServiceRunning(Class<?> serviceClass) {ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);if (manager == null) return false;for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {if (serviceClass.getName().equals(service.service.getClassName())) {return true;}}return false;}
三、增加一个来电展示的自定义UI页面PhoneCallActivity,可根据需求更改
public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener {private TextView tvCallNumberLabel;private TextView tvCallNumber;private TextView tvPickUp;private TextView tvCallingTime;private TextView tvHangUp;private PhoneCallManager phoneCallManager;private PhoneCallService.CallType callType;private String phoneNumber;private Timer onGoingCallTimer;private int callingTime;public static void actionStart(Context context, String phoneNumber,PhoneCallService.CallType callType) {Intent intent = new Intent(context, PhoneCallActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.putExtra(Intent.EXTRA_MIME_TYPES, callType);intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);context.startActivity(intent);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_phone_call);ActivityStack.getInstance().addActivity(this);initData();initView();}private void initData() {phoneCallManager = new PhoneCallManager(this);onGoingCallTimer = new Timer();if (getIntent() != null) {phoneNumber = getIntent().getStringExtra(Intent.EXTRA_PHONE_NUMBER);callType = (PhoneCallService.CallType) getIntent().getSerializableExtra(Intent.EXTRA_MIME_TYPES);}}private void initView() {int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;getWindow().getDecorView().setSystemUiVisibility(uiOptions);getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);tvCallNumberLabel = findViewById(R.id.tv_call_number_label);tvCallNumber = findViewById(R.id.tv_call_number);tvPickUp = findViewById(R.id.tv_phone_pick_up);tvCallingTime = findViewById(R.id.tv_phone_calling_time);tvHangUp = findViewById(R.id.tv_phone_hang_up);tvCallNumber.setText(formatPhoneNumber(phoneNumber));tvPickUp.setOnClickListener(this);tvHangUp.setOnClickListener(this);// 打进的电话if (callType == PhoneCallService.CallType.CALL_IN) {tvCallNumberLabel.setText("来电号码");tvPickUp.setVisibility(View.VISIBLE);}// 打出的电话else if (callType == PhoneCallService.CallType.CALL_OUT) {tvCallNumberLabel.setText("呼叫号码");tvPickUp.setVisibility(View.GONE);phoneCallManager.openSpeaker();}showOnLockScreen();}public void showOnLockScreen() {this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |WindowManager.LayoutParams.FLAG_FULLSCREEN |WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |WindowManager.LayoutParams.FLAG_FULLSCREEN |WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);}@Overridepublic void onClick(View v) {if (v.getId() == R.id.tv_phone_pick_up) {phoneCallManager.answer();tvPickUp.setVisibility(View.GONE);tvCallingTime.setVisibility(View.VISIBLE);onGoingCallTimer.schedule(new TimerTask() {@Overridepublic void run() {runOnUiThread(new Runnable() {@SuppressLint("SetTextI18n")@Overridepublic void run() {callingTime++;tvCallingTime.setText("通话中:" + getCallingTime());}});}}, 0, 1000);} else if (v.getId() == R.id.tv_phone_hang_up) {phoneCallManager.disconnect();stopTimer();}}private String getCallingTime() {int minute = callingTime / 60;int second = callingTime % 60;return (minute < 10 ? "0" + minute : minute) +":" +(second < 10 ? "0" + second : second);}private void stopTimer() {if (onGoingCallTimer != null) {onGoingCallTimer.cancel();}callingTime = 0;}@Overrideprotected void onDestroy() {super.onDestroy();phoneCallManager.destroy();}
四、来电页面UI中布局文件activity_phone_call
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@mipmap/sf_p1"tools:context=".phonecallui.PhoneCallActivity"><RelativeLayoutandroid:id="@+id/rl_user_info"android:layout_width="match_parent"android:layout_height="300dp"><TextViewandroid:id="@+id/tv_call_number_label"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_above="@+id/tv_call_number"android:layout_marginBottom="16dp"android:gravity="center"android:text="来电号码"android:textSize="18sp" /><TextViewandroid:id="@+id/tv_call_number"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:gravity="center"android:textAlignment="center"android:textColor="@android:color/white"android:textSize="28sp"android:textStyle="bold"tools:text="130-1111-1111" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/tv_phone_calling_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_marginTop="24dp"android:text="通话中:01:33"android:textColor="@android:color/white"android:textSize="18sp"android:visibility="gone"tools:visibility="visible" /><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"><TextViewandroid:id="@+id/tv_phone_hang_up"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawablePadding="16dp"android:drawableTop="@mipmap/phone_hang_up"android:foreground="?android:attr/selectableItemBackground"android:gravity="center"android:padding="8dp"android:text="挂 断"android:textColor="@android:color/black"tools:visibility="visible" /><TextViewandroid:id="@+id/tv_phone_pick_up"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="50dp"android:layout_toRightOf="@id/tv_phone_hang_up"android:drawablePadding="16dp"android:drawableTop="@mipmap/phone_pick_up"android:foreground="?android:attr/selectableItemBackground"android:gravity="center"android:padding="8dp"android:text="接 听"android:textColor="@android:color/black"android:visibility="gone"tools:visibility="visible" /></RelativeLayout></RelativeLayout>
</LinearLayout>
代码为完全贴出,其他代码可下载DEMO查看
-END