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

    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();
          }
      }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272