RK3399 Android7.1 VoLTE视频通话
一.通话记录 视频拨号按钮
1.1.packages\apps\Dialer\res\values-zh-rCN\strings.xml
1 2 3 | <string name= "call_log_action_video_call" msgid= "7724301709041128296" > "视频通话" </string> <string name= "call_log_action_send_message" msgid= "5679719296905285131" > "发送短信" </string> <string name= "call_log_action_details" msgid= "701345508704970622" > "通话详情" </string> |
1.1.2.packages\apps\Dialer\res\layout\call_log_list_item_actions.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 | <LinearLayout android:id= "@+id/video_call_action" style= "@style/CallLogActionStyle" > <ImageView style= "@style/CallLogActionIconStyle" android:src= "@drawable/ic_videocam_24dp" /> <TextView style= "@style/CallLogActionTextStyle" android:text= "@string/call_log_action_video_call" /> </LinearLayout> |
1.2. 加载控件
packages\apps\Dialer\src\com\android\dialer\calllog\CallLogListItemViewHolder.java
1 2 3 4 | public void inflateActionViewStub() { …… videoCallButtonView = actionsView.findViewById(R.id.video_call_action); videoCallButtonView.setOnClickListener( this ); |
1.3.
// If one of the calls had video capabilities, show the video call button.
//如果其中一个通话具有视频功能,则显示视频通话按钮。
mCallLogCache.isVideoEnabled()->false
canPlaceCallToNumber->true
phoneCallDetailsViews.callTypeIcons.isVideoShown()->false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | private void bindActionButtons() { boolean canPlaceCallToNumber = PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation); if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) { callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); ((TextView) callButtonView.findViewById(R.id.call_action_text)) .setText(TextUtils.expandTemplate( mContext.getString(R.string.call_log_action_call), nameOrNumber)); TextView callTypeOrLocationView = ((TextView) callButtonView.findViewById( R.id.call_type_or_location_text)); if (callType == Calls.VOICEMAIL_TYPE && !TextUtils.isEmpty(callTypeOrLocation)) { callTypeOrLocationView.setText(callTypeOrLocation); callTypeOrLocationView.setVisibility(View.VISIBLE); } else { callTypeOrLocationView.setVisibility(View.GONE); } callButtonView.setVisibility(View.VISIBLE); } else { callButtonView.setVisibility(View.GONE); } // If one of the calls had video capabilities, show the video call button. if (mCallLogCache.isVideoEnabled() && canPlaceCallToNumber && phoneCallDetailsViews.callTypeIcons.isVideoShown()) { videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); videoCallButtonView.setVisibility(View.VISIBLE); } else { videoCallButtonView.setVisibility(View.GONE); } …… |
A.通话是否具有视频功能 mCallLogCache.isVideoEnabled()
packages\apps\Dialer\src\com\android\dialer\calllog\calllogcache\CallLogCache.java
mIsVideoEnabled = CallUtil.isVideoEnabled(mContext); import com.android.contacts.common.CallUtil;
1 2 3 4 5 6 7 | public boolean isVideoEnabled() { if (!mHasCheckedForVideoEnabled) { mIsVideoEnabled = CallUtil.isVideoEnabled(mContext); mHasCheckedForVideoEnabled = true ; } return mIsVideoEnabled; } |
packages\apps\ContactsCommon\src\com\android\contacts\common\CallUtil.java
/**
* Indicates that the video calling is not available.
*/
public static final int VIDEO_CALLING_DISABLED = 0;
/**
* Indicates that video calling is enabled, regardless of presence status.
*/
public static final int VIDEO_CALLING_ENABLED = 1;
getVideoCallingAvailability 看支不支持 video_calling
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public static boolean isVideoEnabled(Context context) { return (getVideoCallingAvailability(context) & VIDEO_CALLING_ENABLED) != 0 ; } public static int getVideoCallingAvailability(Context context) { if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE) || !CompatUtils.isVideoCompatible()) { return VIDEO_CALLING_DISABLED; } TelecomManager telecommMgr = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); if (telecommMgr == null ) { return VIDEO_CALLING_DISABLED; } List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts(); for (PhoneAccountHandle accountHandle : accountHandles) { PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle); if (account != null ) { if (account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) { //能够视频通话的标志 // Builds prior to N do not have presence support. if (!CompatUtils.isVideoPresenceCompatible()) { return VIDEO_CALLING_ENABLED; } int videoCapabilities = VIDEO_CALLING_ENABLED; if (account.hasCapabilities( PhoneAccountSdkCompat.CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)) { //列表示是否联系人的电话号码支持视频通话 videoCapabilities |= VIDEO_CALLING_PRESENCE; } return videoCapabilities; } } } return VIDEO_CALLING_DISABLED; //以上条件均不满足 最后就会返回VIDEO_CALLING_DISABLED 0 } |
frameworks\base\telecomm\java\android\telecom\PhoneAccount.java
Android 8.0 PhoneAccount全面解析 PhoneAccount这个类是关于SIM卡信息的
PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)
account.hasCapabilities(PhoneAccountSdkCompat.CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)
B.添加拨号的intent videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
b.1. packages\apps\Dialer\src\com\android\dialer\calllog\IntentProvider.java
主要看 new IntentProvider() 类中 getIntent 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public static IntentProvider getReturnVideoCallIntentProvider( final String number) { return getReturnVideoCallIntentProvider(number, null ); } public static IntentProvider getReturnVideoCallIntentProvider( final String number, final PhoneAccountHandle accountHandle) { return new IntentProvider() { @Override public Intent getIntent(Context context) { return new CallIntentBuilder(number) .setPhoneAccountHandle(accountHandle) .setCallInitiationType(LogState.INITIATION_CALL_LOG) .setIsVideoCall( true ) .build(); } }; } |
public abstract Intent getIntent(Context context);
没想到IntentProvider 也是个抽象类 public abstract class IntentProvider {
b.2. packages\apps\Dialer\src\com\android\dialer\util\IntentUtil.java 构建intent // 普通电话为VideoProfile.STATE_AUDIO_ONLY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static class CallIntentBuilder { private Uri mUri; private int mCallInitiationType; private PhoneAccountHandle mPhoneAccountHandle; private boolean mIsVideoCall = false ; …… public Intent build() { return getCallIntent( mUri, mPhoneAccountHandle, mIsVideoCall ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY, mCallInitiationType); } } |
1.4.按钮监听事件 走 else startActivityWithErrorToast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @Override public void onClick(View view) { if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) { mVoicemailPrimaryActionButtonClicked = true ; mExpandCollapseListener.onClick(primaryActionView); } else if (view.getId() == R.id.call_with_note_action) { CallSubjectDialog.start( (Activity) mContext, info.photoId, info.photoUri, info.lookupUri, (String) nameOrNumber /* top line of contact view in call subject dialog */ , isBusiness, number, TextUtils.isEmpty(info.name) ? null : displayNumber, /* second line of contact view in dialog. */ numberType, /* phone number type (e.g. mobile) in second line of contact view */ accountHandle); } else { final IntentProvider intentProvider = (IntentProvider) view.getTag(); if (intentProvider != null ) { final Intent intent = intentProvider.getIntent(mContext); // See IntentProvider.getCallDetailIntentProvider() for why this may be null. if (intent != null ) { DialerUtils.startActivityWithErrorToast(mContext, intent); } } } } |
1.5.packages\apps\Dialer\src\com\android\dialer\util\DialerUtils.java
intent.getAction()->android.intent.action.CALL 处理拨号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) { try { if ((IntentUtil.CALL_ACTION.equals(intent.getAction()) && context instanceof Activity)) { // All dialer-initiated calls should pass the touch point to the InCallUI Point touchPoint = TouchPointManager.getInstance().getPoint(); if (touchPoint.x != 0 || touchPoint.y != 0 ) { Bundle extras; // Make sure to not accidentally clobber any existing extras if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) { extras = intent.getParcelableExtra( TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); } else { extras = new Bundle(); } extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint); intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); } final boolean hasCallPermission = TelecomUtil.placeCall((Activity) context, intent); if (!hasCallPermission) { // TODO: Make calling activity show request permission dialog and handle // callback results appropriately. Toast.makeText(context, "Cannot place call without Phone permission" , Toast.LENGTH_SHORT); } } else { context.startActivity(intent); } } catch (ActivityNotFoundException e) { Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show(); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2020-08-05 android:theme
2020-08-05 android:excludeFromRecents="true"