OS: Android 8.1
需求分析
1、禁止系统来电铃声,提供接口给客户自己播放铃声
2、禁止系统拉起来去电页面(InCallActivity),消息通知客户拉起自己的来去电页面
3、禁止来电消息 Notification 显示(包括未接来电),点击跳转至 InCallActivity(未接来电消息可通知客户或者将 PendingIntent 改成客户的)
上代码
1、系统来电铃声播放在 Telecomm 应用中,我们发现一般都是先听到铃声才看到 UI 被拉起,那是因为铃声在 Telecomm 中,UI 需要通过 InCallService 通知到 Dialer 中。
vendor\mediatek\proprietary\packages\services\Telecomm\src\com\android\server\telecom\Ringer.java
public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) { //cczhegn add return for customer self control ringing, system ignore if (true) { return ; } if (foregroundCall == null) { /// M: ALPS03787956 Fix wtf log warning. @{ /// Hand up the call immediately when ringing. Then the foreground call will /// change to null, but call audio is start ringing at the same time. /// Log.wtf will occur in this case. /// Solution: /// Call audio can handle this case, so change Log.wtf to Log.i here. Log.i(this, "startRinging called with null foreground call."); /// @} return false; } AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0; boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri()); boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null); boolean isSelfManaged = foregroundCall.isSelfManaged(); .... } public void stopRinging() { //cczheng add return for customer self control ringing, system ignore if (true) { return ; } if (mRingingCall != null) { Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER); mRingingCall = null; } if (mIsVibrating) { Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR); mVibrator.cancel(); mIsVibrating = false; mVibratingCall = null; } }
只需要将 startRinging() 和 stopRinging() 直接 return 即可,系统就不会响铃了。
2、提供播放铃声工具类 RingUtil 给客户
public class RingUtil { private static final String TAG = "RingUtil"; private static Ringtone ringtone; private static Ringtone getRing(Context context){ if (null == ringtone){ Uri defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE); ringtone = RingtoneManager.getRingtone(context, defaultRingtoneUri); } return ringtone; } public static void startRing(Context context){ getRing(context); if (!ringtone.isPlaying()){ ringtone.play(); } } public static void stopRing(Context context){ getRing(context); if (ringtone.isPlaying()){ ringtone.stop(); } } }
3、禁止系统拉起来去电页面
刚刚上面说到 Telecom 和 Dialer 主要通过 InCallService 通信,重要的两个方法 onCallAdded、onCallRemoved,从字面意思很容易理解,当 call 加入和移除时回调。
InCallPresenter 可以说是 Call 的管理中心,来电去电都经过这处理,所以我们在此处修改比较容易
vendor\mediatek\proprietary\packages\apps\Dialer\java\com\android\incallui\InCallPresenter.java
//来电拉起 InCallActivity 的地方 public void showInCall(boolean showDialpad, boolean newOutgoingCall) { LogUtil.d("InCallPresenter.showInCall", "Showing InCallActivity"); //cczheng annotaion don't show sysytem InCallActivity //mContext.startActivity( //InCallActivity.getIntent( //mContext, showDialpad, newOutgoingCall, false /* forFullScreen */)); } //去电拉起 InCallActivity 的地方 public void maybeStartRevealAnimation(Intent intent) { LogUtil.e("InCallPresenter.OutgoingCall", "maybeStartRevealAnimation"); if (intent == null || mInCallActivity != null) { return; } final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); if (extras == null) { // Incoming call, just show the in-call UI directly. return; } if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) { // Account selection dialog will show up so don't show the animation. return; } final PhoneAccountHandle accountHandle = intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT); InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle); //cczheng annotaion don't show sysytem InCallActivity LogUtil.e("InCallPresenter.OutgoingCall", "OutgoingCall is preper start inCallActivity."); //final Intent activityIntent = //InCallActivity.getIntent(mContext, false, true, false /* forFullScreen */); //activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint); //mContext.startActivity(activityIntent); }
4、消息通知客户来去电消息
为了简单我们采用广播的方式,由于 8.1 中静态注册广播有限制,为了突破限制,我们需要在发送广播时加上 intent.addFlags(0x01000000);
vendor\mediatek\proprietary\packages\apps\Dialer\java\com\android\incallui\InCallPresenter.java
String number; public void onCallAdded(final android.telecom.Call call) { LatencyReport latencyReport = new LatencyReport(call); if (shouldAttemptBlocking(call)) { maybeBlockCall(call, latencyReport); } else { if (call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) { mExternalCallList.onCallAdded(call); } else { latencyReport.onCallBlockingDone(); mCallList.onCallAdded(mContext, call, latencyReport); } } // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); //cczheng add when incall or outcall added notify user can start there incallui number = TelecomCallUtil.getNumber(call); if (isOutGoingCall) { LogUtil.e("InCallPresenter.OutgoingCall", "OutgoingCall number=="+number); Intent intent = new Intent("com.android.outgoingcall.wireless"); intent.putExtra("outgoingNumber", number); intent.addFlags(0x01000000); mContext.sendBroadcast(intent); }else{ LogUtil.i("InCallPresenter.IncomingCall", "IncomingCall number=="+number); Intent intentcc = new Intent("com.android.incomingcall.wireless"); intentcc.putExtra("incomingNumber", number); intentcc.addFlags(0x01000000); mContext.sendBroadcast(intentcc); } }
在 onCallAdded() 方法中,可以看到我们添加了 isOutGoingCall 变量判读是来电还是去电,分别对应不用的 action,通过 TelecomCallUtil 获取当前 call 的 number 顺带发送
5、来去电类型 isOutGoingCall 区分
回到刚刚的屏蔽去电拉起页面的方法 maybeStartRevealAnimation() 中,其中有获取 EXTRA_OUTGOING_CALL_EXTRAS 参数,经过验证确实来电不带此参数,去电带参数
vendor\mediatek\proprietary\packages\apps\Dialer\java\com\android\incallui\InCallPresenter.java
//cczheng add for distinguish incall or outgogingcall private boolean isOutGoingCall; public void maybeStartRevealAnimation(Intent intent) { LogUtil.e("InCallPresenter.OutgoingCall", "maybeStartRevealAnimation"); if (intent == null || mInCallActivity != null) { return; } final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); if (extras == null) { // Incoming call, just show the in-call UI directly. isOutGoingCall = false;//cczheng add for incomingcall return; } if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) { // Account selection dialog will show up so don't show the animation. return; } final PhoneAccountHandle accountHandle = intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT); InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle); LogUtil.e("InCallPresenter.OutgoingCall", "OutgoingCall is preper start inCallActivity."); isOutGoingCall = true;//cczhegn add for outgongcall //final Intent activityIntent = //InCallActivity.getIntent(mContext, false, true, false /* forFullScreen */); //activityIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint); //mContext.startActivity(activityIntent); }
6、禁止来电消息 Notification 显示
vendor\mediatek\proprietary\packages\apps\Dialer\java\com\android\incallui\StatusBarNotifier.java
@RequiresPermission(Manifest.permission.READ_PHONE_STATE) public void updateNotification(CallList callList) { ///cczheng annotaion for don't show incallnotification when pstnoverlayactivity in font //updateInCallNotification(callList); }
直接注释 updateInCallNotification()
7、未接来电消息处理
回到 Telecomm 中,MissedCallNotifierImpl 负责管理未接来电消息,如果将 android 自己的 Notification 屏蔽,通知客户自己去显示,略微麻烦,用户需要自己建数据库保存通知已读未读状态,
因为重启需要查询判断是否需要再次显示。这样你可以在 showMissedCallNotification() 中直接发送通知后 return。我们此处采用第二种方式,修改系统 Notification 的 PendingIntent 为客户
vendor\mediatek\proprietary\packages\services\Telecomm\src\com\android\server\telecom\ui\MissedCallNotifierImpl.java
private PendingIntent createCallLogPendingIntent(UserHandle userHandle) { //cczheng add for jump to customer app try { Intent intentc = mContext.getPackageManager().getLaunchIntentForPackage("your customer app packageName"); return PendingIntent.getActivity(mContext, 11, intentc, 0); }catch (Exception e){ e.printStackTrace(); Intent intent = new Intent(Intent.ACTION_VIEW, null); intent.setType(Calls.CONTENT_TYPE); TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext); taskStackBuilder.addNextIntent(intent); return taskStackBuilder.getPendingIntent(0, 0, null, userHandle); } }
8、按下音量键和电源键静音
系统播放来电铃声时,此时按下音量键或电源键,默认都会停止播放铃声。但这是系统播放铃声的情况下,现在我们已经开发给
客户自己去播放铃声了,所以原来的逻辑就会失效,只能客户自己去停止铃声,但是来电页面响铃中,客户是监听不到音量键或
电源键事件的,只能在系统通知了。
vendor\mediatek\proprietary\packages\services\Telecomm\src\com\android\server\telecom\TelecomServiceImpl.java
@Override public void silenceRinger(String callingPackage) { try { Log.startSession("TSI.sR"); synchronized (mLock) { enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); long token = Binder.clearCallingIdentity(); try { Log.i(this, "Silence Ringer requested by %s", callingPackage); //cczheng add for notify custmer press valume/power need silence ringer mContext.sendBroadcast(new Intent("com.android.key.silence.ringer")); mCallsManager.getCallAudioManager().silenceRingers(); mCallsManager.getInCallController().silenceRinger(); } finally { Binder.restoreCallingIdentity(token); } } } finally { Log.endSession(); } }