1. 程式人生 > >Android MTK平臺 客製化系統來電介面(遮蔽 InCallUI 提供介面給客戶自行展示來電去電頁面)

Android MTK平臺 客製化系統來電介面(遮蔽 InCallUI 提供介面給客戶自行展示來電去電頁面)

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** ```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 給客戶 ```java 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** ```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** ```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** ```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** ```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** ```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** ```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(); }