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

  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();
          }
      }


目录
相关文章
|
10月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
12月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
492 65
Android自定义view之网易云推荐歌单界面
|
12月前
|
Android开发 开发者
Android企业级实战-界面篇-3
本文是《Android企业级实战-界面篇》系列的第三篇,主要介绍分割线和条形跳转框的实现方法,二者常用于设置和个人中心界面。文章通过具体代码示例展示了如何实现这两种UI组件,并提供了效果图。实现前需准备`dimens.xml`、`ids.xml`、`colors.xml`等文件,部分资源可参考系列第一、二篇文章。代码中详细说明了布局文件的配置,如分割线的样式定义和条形跳转框的组件组合,帮助开发者快速上手并应用于实际项目中。
145 1
|
12月前
|
XML Android开发 数据格式
Android企业级实战-界面篇-2
本文为《Android企业级实战-界面篇》系列第二篇,主要介绍三个UI模块的实现:用户资料模块、关注与粉丝统计模块以及喜欢和收藏功能模块。通过详细的XML代码展示布局设计,包括dimens、ids、colors配置文件的使用,帮助开发者快速构建美观且功能齐全的界面。文章结合实际效果图,便于理解和应用。建议配合第一篇文章内容学习,以获取完整工具类支持。
181 0
|
12月前
|
算法 Java Android开发
Android企业级实战-界面篇-1
本文详细介绍了Android企业级开发中界面实现的过程,涵盖效果展示、实现前准备及代码实现。作者通过自身经历分享了Android开发经验,并提供了`dimens.xml`、`ids.xml`、`colors.xml`和`strings.xml`等配置文件内容,帮助开发者快速构建规范化的UI布局。文章以一个具体的用户消息界面为例,展示了如何使用线性布局(LinearLayout)和相对布局(RelativeLayout)实现功能模块排列,并附带注意事项及使用方法,适合初学者和进阶开发者参考学习。
261 0
|
12月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
249 0
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
886 13
|
7月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1001 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
7月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
375 0