--2017.12.08--csdn不知咋搞的,文中有些内容看不见了(尤其是我加了说明的部分),得重新“上色”,增加维护成本。。。
写在前面的话:
现在时间是2016.12.16,下面所有涉及到的代码都是基于最新CM 14.1。
代码比较部分比较的是今年5月分的CM 13.0。
开始写这篇博客的时候我还没有正式的跑过一次CM 14.1的IMS MO流程,所以后面可能会不定时更新/纠正下面的内容。
正式开始之前也许你会想看上两个版本的比较
Android M MO流程 并与Android L MO对比
----我是分割线---
android N IMS MO整体流程:
以及 比较的图片
变化:1.startCall()去掉了(可忽略);2.调用TelecomManager.placeCall()之前增加了几步。
packages/apps/Dialer/src/com/android/dialer/dialpad/DialpadFragment.java
1. handleDialButtonPressed()
这里没啥说的哈,就是在拨号盘拨号,与CM 13.0 稍有不同的是那个在cm 13.0上拆分出来的startCall()又合并进去了。真是任性啊。
读者也可以打开我之前的博客比较着看(分别占左右半屏方便比较)。
private void handleDialButtonPressed() {
if (isDigitsEmpty() && (mRecipients == null || !mRecipients.isShown())) {
// No number entered.
handleDialButtonClickWithEmptyDigits();
} else {
boolean isDigitsShown = mDigits.isShown();
final String number = isDigitsShown ? mDigits.getText().toString() :
mRecipients.getText().toString().trim();
if (isDigitsShown && isDigitsEmpty()) {
handleDialButtonClickWithEmptyDigits();
} else if (mAddParticipant && isPhoneInUse() && isDigitsEmpty()
&& mRecipients.isShown() && isRecipientEmpty()) {
// mRecipients must be empty
// TODO add support for conference URI in last number dialed
// use ErrorDialogFragment instead? also see
// android.app.AlertDialog
android.widget.Toast.makeText(getActivity(),
"Error: Cannot dial. Please provide conference recipients.",
android.widget.Toast.LENGTH_SHORT).show();
} else {
// "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
// test equipment.
// TODO: clean it up.
if (number != null
&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
&& number.matches(mProhibitedPhoneNumberRegexp)) {
Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
if (getActivity() != null) {
DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
R.string.dialog_phone_call_prohibited_message);
dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
}
// Clear the digits just in case.
clearDialpad();
} else {
final Intent intent = CallUtil.getCallIntent(number);//到这里我又想吐槽了,还是算了。。。
if (!isDigitsShown) {
// must be dial conference add extra
intent.putExtra(EXTRA_DIAL_CONFERENCE_URI, true);
}
intent.putExtra(ADD_PARTICIPANT_KEY, mAddParticipant && isPhoneInUse());
DialerUtils.startActivityWithErrorToast(getActivity(), intent);
hideAndClearDialpad(false);
}
}
}
}
3. startActivityWithErrorToast
/**
* Attempts to start an activity and displays a toast with a provided error message if the
* activity is not found, instead of throwing an exception.
*
* @param context to start the activity with.
* @param intent to start the activity with.
* @param msgId Resource ID of the string to display in an error message if the activity is
* not found.
*/
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
// 记录坐标,后面启动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) {//没权限的话弹个toast
// 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();
}
}
packages/apps/Dialer/src/com/android/dialer/util/TelecomUtil.java
4. TelecomUtil.placeCall()
/**
* Tries to place a call using the {@link TelecomManager}.
*
* @param activity a valid activity.
* @param intent the call intent.
*
* @return {@code true} if we successfully attempted to place the call, {@code false} if it
* failed due to a permission check.
*/
public static boolean placeCall(Activity activity, Intent intent) {
if (hasCallPhonePermission(activity)) {//检查权限
TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent);
return true;
}
return false;
}
packages/apps/ContactsCommon/src/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
对的这里跑到ContactsCommon里面来了,前面我想吐槽的CallUtil.getCallIntent(number),也是ContactsCommon里面的。。当然我想吐槽的不止这些
说好的move to Dialer呢?
5. TelecomManagerCompat.placeCall()
这里还不是api,下一步才是
/**
* Places a new outgoing call to the provided address using the system telecom service with
* the specified intent.
*
* @param activity {@link Activity} used to start another activity for the given intent
* @param telecomManager the {@link TelecomManager} used to place a call, if possible
* @param intent the intent for the call
*/
public static void placeCall(@Nullable Activity activity,
@Nullable TelecomManager telecomManager, @Nullable Intent intent) {
if (activity == null || telecomManager == null || intent == null) {
return;
}
if (CompatUtils.isMarshmallowCompatible()) {
telecomManager.placeCall(intent.getData(), intent.getExtras());
return;
}
activity.startActivityForResult(intent, 0);
}
frameworks/base/telecomm/java/android/telecom/TelecomManager.java
6. telecomManager.placeCall()
这个api建议细看一下,获得电话拨打权限后调用这个api可以直接不经过dialer直接拨打电话,我写过demo测试,已传github。
/**
* Places a new outgoing call to the provided address using the system telecom service with
* the specified extras.
*
* This method is equivalent to placing an outgoing call using {@link Intent#ACTION_CALL},
* except that the outgoing call will always be sent via the system telecom service. If
* method-caller is either the user selected default dialer app or preloaded system dialer
* app, then emergency calls will also be allowed.
*
* Requires permission: {@link android.Manifest.permission#CALL_PHONE}
*
* Usage example://假如你对这个不熟,建议看一下这段注释
* <pre>
* Uri uri = Uri.fromParts("tel", "12345", null);
* Bundle extras = new Bundle();
* extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
* telecomManager.placeCall(uri, extras);
* </pre>
*
* The following keys are supported in the supplied extras.
* <ul>
* <li>{@link #EXTRA_OUTGOING_CALL_EXTRAS}</li>
* <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
* <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>//默认打开Speaker(亲测)
* <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>//视频通话
* </ul>
*
* @param address The address to make the call to.
* @param extras Bundle of extras to use with the call.
*/
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
public void placeCall(Uri address, Bundle extras) {
ITelecomService service = getTelecomService();
if (service != null) {
if (address == null) {
Log.w(TAG, "Cannot place call to empty address.");
}
try {
service.placeCall(address, extras == null ? new Bundle() : extras,
mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#placeCall", e);
}
}
}
packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
7.service.placeCall
@Override
public void placeCall(Uri handle, Bundle extras, String callingPackage) {
try {
Log.startSession("TSI.pC");
enforceCallingPackage(callingPackage);
if (!canCallPhone(callingPackage, "placeCall")) {
throw new SecurityException("Package " + callingPackage
+ " is not allowed to place phone calls");
}
// Note: we can still get here for the default/system dialer, even if the Phone
// permission is turned off. This is because the default/system dialer is always
// allowed to attempt to place a call (regardless of permission state), in case
// it turns out to be an emergency call. If the permission is denied and the
// call is being made to a non-emergency number, the call will be denied later on
// by {@link UserCallIntentProcessor}.
// 默认/系统拨号应用即使没有权限也可以执行到这里
// 避免紧急号码因权限问题被阻止
// 如果是非紧急号码,后面会被UserCallIntentProcessor拒绝
final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
PackageManager.PERMISSION_GRANTED;
synchronized (mLock) {
final UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
try {
final Intent intent = new Intent(Intent.ACTION_CALL, handle);
if (extras != null) {
extras.setDefusable(true);
intent.putExtras(extras);
}
//此处有变化,M上是new UserCallIntentProcessor
mUserCallIntentProcessorFactory.create(mContext, userHandle)
.processIntent(
intent, callingPackage, hasCallAppOp && hasCallPermission);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
Log.endSession();
}
}
packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java
8. mUserCallIntentProcessorFactory.create.processIntent
public void processIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
// Ensure call intents are not processed on devices that are not capable of calling.
if (!isVoiceCapable()) {
return;
}
String action = intent.getAction();
// 只允许这三种action拨打电话
if (Intent.ACTION_CALL.equals(action) ||//判断三种ACTION_CALL(反正只会出现两种)
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency);
}
}
9. processOutgoingCallIntent()
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
//略过一部分然后下面的代码相较于M有删减
int videoState = intent.getIntExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
isDefaultOrSystemDialer(callingPackageName));
// Save the user handle of current user before forwarding the intent to primary user.
intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
sendBroadcastToReceiver(intent);
}
相同的部分我们就略过
packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
13. processOutgoingCallIntent()
/**
* Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
*
* @param intent Call intent containing data about the handle to call.
*/
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
Bundle clientExtras = null;
//略去一部分代码
Log.i(CallIntentProcessor.class, " processOutgoingCallIntent handle = " + handle
+ ",scheme = " + scheme + ", uriString = " + uriString
+ ", isSkipSchemaParsing = " + isSkipSchemaParsing
+ ", isAddParticipant = " + isAddParticipant);
final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
// Show the toast to warn user that it is a personal call though initiated in work profile.//这段是新增
if (fixedInitiatingUser) {
// string的值 Using the personal dialer to make the call
Toast.makeText(context, R.string.toast_personal_call_msg, Toast.LENGTH_LONG).show();
}
UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
// 这里看来是不等广播完成就先启动InCallUI,视觉上给人一种立刻拨打和响应速度很快的感觉
Call call = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);
if (call != null) {
// Asynchronous calls should not usually be made inside a BroadcastReceiver because once
// onReceive is complete, the BroadcastReceiver's process runs the risk of getting
// killed if memory is scarce. However, this is OK here because the entire Telecom
// process will be running throughout the duration of the phone call and should never
// be killed.
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, call, intent, new PhoneNumberUtilsAdapterImpl(),
isPrivilegedDialer);
final int result = broadcaster.processIntent();
final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
if (!success && call != null) {
callsManager.clearPendingMOEmergencyCall();
disconnectCallAndShowErrorDialog(context, call, result);
}
}
}
结合InCallUI里面的一些提交,cm 14.1上似乎是对电话做了personal和work的区分,看到实际效果后我考虑要不要专门总结一下那个。
packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
14. broadcaster.processIntent
15. rewriteCallIntentAction
16. broadcastIntent
/**
* Processes the supplied intent and starts the outgoing call broadcast process relevant to the
* intent.
*
* This method will handle three kinds of actions:
*//加粗加黑划重点
* - CALL (intent launched by all third party dialers)
* - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)//这个几乎不会出现了
* - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
*
* @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
* {@link DisconnectCause} if the call did not, describing why it failed.
*/
@VisibleForTesting
public int processIntent() {
Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
Intent intent = mIntent;
String action = intent.getAction();
final Uri handle = intent.getData();
- //略过一些代码
- final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
// 重写Call Intent action。关于这部分的详解看M和L版本的比较
rewriteCallIntentAction(intent, isPotentialEmergencyNumber);
action = intent.getAction();
// True for certain types of numbers that are not intended to be intercepted or modified
// by third parties (e.g. emergency numbers).
boolean callImmediately = false;
if (Intent.ACTION_CALL.equals(action)) {
if (isPotentialEmergencyNumber) {
if (!mIsDefaultOrSystemPhoneApp) {
Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
+ "unless caller is system or default dialer.", number, intent);
launchSystemDialer(intent.getData());
return DisconnectCause.OUTGOING_CANCELED;
} else {
// “可能是”紧急号码 并且 默认/系统拨号应用为 true
callImmediately = true;
}
}
} else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
if (!isPotentialEmergencyNumber) {
Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
+ "Intent %s.", number, intent);
return DisconnectCause.OUTGOING_CANCELED;
}
// 不是紧急号码,但是 action 标记为emergency 为 treu,我记得这个actino是在锁屏界面拨打紧急号码使用的
callImmediately = true;
} else {
Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
return DisconnectCause.INVALID_NUMBER;
}
if (callImmediately) {
Log.i(this, "Placing call immediately instead of waiting for "
+ " OutgoingCallBroadcastReceiver: %s", intent);
String scheme = isUriNumber ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
boolean speakerphoneOn = mIntent.getBooleanExtra(
TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
int videoState = mIntent.getIntExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
// Since we will not start NewOutgoingCallBroadcastIntentReceiver in case of
// callImmediately is true, make sure to mark it as ready, so that when user
// selects account, call can go ahead in case of numbers which are potential emergency
// but not actual emergeny.
// 这里其实蛮重要的,注意看上面英文注释,
// 有个bug跟这个有关,好像是双卡设定拨打前询问后,拨打紧急号码开的号码会直接拨出去
// 需要标记的地方不止一个,可以从git log中查看相关提交,为了防止你的代码不是最新的,建议去LineageOS的review上查看
mCall.setNewOutgoingCallIntentBroadcastIsDone();
mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, number, null), null,
speakerphoneOn, videoState);
// Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
// so that third parties can still inspect (but not intercept) the outgoing call. When
// the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to
// initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
}
UserHandle targetUser = mCall.getInitiatingUser();
Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
if (isSkipSchemaParsing) {
broadcastIntent(intent, handle.toString(), !callImmediately, targetUser);
} else {
broadcastIntent(intent, number, !callImmediately, targetUser);
}
return DisconnectCause.NOT_DISCONNECTED;
}
前面部分略微小结一下,由于代码“历代更改”,现如今在新建intent的时候,默认的action已经成为ACTION_CALL,而不是ACTION_CALL_PRIVILEGED,也因此rewriteCallIntentAction成了一个无用的方法。
packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
19. mCallsManager.placeOutgoingCall
- @VisibleForTesting
public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
boolean speakerphoneOn, int videoState) {
- final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
if (gatewayInfo == null) {
Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
} else {
Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
Log.pii(uriHandle), Log.pii(handle));
}
call.setHandle(uriHandle);
call.setGatewayInfo(gatewayInfo);
final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
R.bool.use_speaker_when_docked);
final boolean isDocked = mDockManager.isDocked();
final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabled(videoState);
// 建议大家注意一下这部分代码,这里影响(不是决定)了通话是否要自动开speaker,就我个人来看,关于Spekaer开关这一块的
// 代码逻辑还不够清晰准确,或者说Spekaer这部分(尤其是视频通话中)没有一个统一的期望结果,尤其再结合低电的情况,
// 所以总会挑出一些小猫病。
算了这次说MO的,Speaker的不多说了。- // Auto-enable speakerphone if the originating intent specified to do so, if the call
// is a video call, of if using speaker when docked
call.setStartWithSpeakerphoneOn(speakerphoneOn || useSpeakerForVideoCall
|| (useSpeakerWhenDocked && isDocked));
call.setVideoState(videoState);
if (speakerphoneOn) {
Log.i(this, "%s Starting with speakerphone as requested", call);
} else if (useSpeakerWhenDocked && useSpeakerWhenDocked) {
Log.i(this, "%s Starting with speakerphone because car is docked.", call);
} else if (useSpeakerForVideoCall) {
Log.i(this, "%s Starting with speakerphone because its a video call.", call);
}
if (call.isEmergencyCall()) {
// Emergency -- CreateConnectionProcessor will choose accounts automatically
call.setTargetPhoneAccount(null);
new AsyncEmergencyContactNotifier(mContext).execute();
}
final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
if (!call.isEmergencyCall()) {
updateLchStatus(call.getTargetPhoneAccount().getId());
}
//Block to initiate Emregency call now as the current active call
//is not yet disconnected.
if (mPendingMOEmerCall == null) {
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
call.startCreateConnection(mPhoneAccountRegistrar);
}
} else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false,
call.getInitiatingUser()).isEmpty()) {
// If there are no call capable accounts, disconnect the call.
markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
"No registered PhoneAccounts"));
markCallAsRemoved(call);
}
}
packages/services/Telecomm/src/com/android/server/telecom/CreateConnectionProcessor.java
21. mCreateConnectionProcessor.process
22. adjustAttemptsForEmergency
@VisibleForTesting
public void process() {
Log.v(this, "process");
clearTimeout();
mAttemptRecords = new ArrayList<>();
if (mCall.getTargetPhoneAccount() != null) {
mAttemptRecords.add(new CallAttemptRecord(
mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
}
adjustAttemptsForConnectionManager();
adjustAttemptsForEmergency();
mAttemptRecordIterator = mAttemptRecords.iterator();
attemptNextPhoneAccount();
}
31. crateConnection
/**
* This can be used by telecom to either create a new outgoing call or attach to an existing
* incoming call. In either case, telecom will cycle through a set of services and call
* createConnection util a connection service cancels the process or completes it successfully.
*/
private void createConnection(
final PhoneAccountHandle callManagerAccount,
final String callId,
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
"isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
isIncoming,
isUnknown);
Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
: isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
:onCreateOutgoingConnection(callManagerAccount, request);//根据isIncoming创建不同connection
Log.d(this, "createConnection, connection: %s", connection);
if (connection == null) {
connection = Connection.createFailedConnection(
new DisconnectCause(DisconnectCause.ERROR));
}
connection.setTelecomCallId(callId);
if (connection.getState() != Connection.STATE_DISCONNECTED) {
addConnection(callId, connection);
}
Uri address = connection.getAddress();
String number = address == null ? "null" : address.getSchemeSpecificPart();
Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
Connection.toLogSafePhoneNumber(number),
Connection.stateToString(connection.getState()),
Connection.capabilitiesToString(connection.getConnectionCapabilities()),
Connection.propertiesToString(connection.getConnectionProperties()));
Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
mAdapter.handleCreateConnectionComplete(
callId,
request,
new ParcelableConnection( // 瞧一下这里面的参数,想看什么可以log打印出来
request.getAccountHandle(),
connection.getState(),
connection.getConnectionCapabilities(),
connection.getConnectionProperties(),
connection.getAddress(),
connection.getAddressPresentation(),
connection.getCallerDisplayName(),
connection.getCallerDisplayNamePresentation(),
connection.getVideoProvider() == null ?
null : connection.getVideoProvider().getInterface(),
connection.getVideoState(),
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
createIdList(connection.getConferenceables()),
connection.getExtras()));
if (isUnknown) {
triggerConferenceRecalculate();
}
}
TelephonyConnectionService.java
32. onCreateOutgoingConnection
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
final ConnectionRequest request) {
Log.i(this, "onCreateOutgoingConnection, request: " + request);
Bundle bundle = request.getExtras();
boolean isSkipSchemaOrConfUri = (bundle != null) && (bundle.getBoolean(
TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false) ||
bundle.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false));
Uri handle = request.getAddress();
if (!isSkipSchemaOrConfUri && handle == null) {
Log.d(this, "onCreateOutgoingConnection, handle is null");
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
"No phone number supplied"));
}
if (handle == null) handle = Uri.EMPTY;
String scheme = handle.getScheme();
String number;
...
final String numberToDial = number;
final boolean isEmergencyNumber =
PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
//isLocalEmergencyNumber比isPotentialEmergencyNumber靠谱多了
if (isEmergencyNumber && !isRadioOn()) {
//emergency的也略过
// Return the still unconnected GsmConnection and wait for the Radios to boot before
// connecting it to the underlying Phone.
return emergencyConnection;
} else {
if (!canAddCall() && !isEmergencyNumber) {
Log.d(this, "onCreateOutgoingConnection, cannot add call .");
return Connection.createFailedConnection(
new DisconnectCause(DisconnectCause.ERROR,
getApplicationContext().getText(
R.string.incall_error_cannot_add_call),
getApplicationContext().getText(
R.string.incall_error_cannot_add_call),
"Add call restricted due to ongoing video call"));
}
// Get the right phone object from the account data passed in.
//返回的是GSMCDMAPhone
final Phone phone =getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
Connection resultConnection = getTelephonyConnection(request, numberToDial,
isEmergencyNumber, handle, phone);
// If there was a failure, the resulting connection will not be a TelephonyConnection,
// so don't place the call!
if(resultConnection instanceof TelephonyConnection) {
placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
}
return resultConnection;
}
}
33. placeOutgoingConnection
private void placeOutgoingConnection(
TelephonyConnection connection, Phone phone, ConnectionRequest request) {
placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
}
private void placeOutgoingConnection(
TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
String number = connection.getAddress().getSchemeSpecificPart();
boolean isAddParticipant = (extras != null) && extras
.getBoolean(TelephonyProperties.ADD_PARTICIPANT_KEY, false);
Log.d(this, "placeOutgoingConnection isAddParticipant = " + isAddParticipant);
com.android.internal.telephony.Connection originalConnection = null;
try {
if (phone != null) {
if (isAddParticipant) {
phone.addParticipant(number);
return;
} else {
// 这里phone的类型是GSMCDMAPhone, android N的新变化
originalConnection = phone.dial(number, null, videoState, extras);
}
}
} catch (CallStateException e) {
...
}
if (originalConnection == null) {
int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
// 没有connection的话意味着是拨了一个MMIcode
// On GSM phones, null connection means that we dialed an MMI code
if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
Log.d(this, "dialed MMI code");
int subId = phone.getSubId();
Log.d(this, "subId: " + subId);
telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
final Intent intent = new Intent(this, MMIDialogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
}
startActivity(intent);
}
Log.d(this, "placeOutgoingConnection, phone.dial returned null");
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
telephonyDisconnectCause, "Connection is null"));
} else {
connection.setOriginalConnection(originalConnection);
}
}
我们知道originalConnection = phone.dial(number, null, videoState, extras)是到了拨号的阶段了。
那么当前这个phone是怎么来的?是什么类型的呢?
这里这个phone是作为一个参数传进来的,在32可以看到这两行代码
// Get the right phone object from the account data passed in.
final Phone phone =getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
for (int i = 0; i < numPhones; i++) {
Phone phone = null;
int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
phone = telephonyComponentFactory.makePhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i,
PhoneConstants.PHONE_TYPE_GSM,
telephonyComponentFactory);
} else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
phone = telephonyComponentFactory.makePhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i,
PhoneConstants.PHONE_TYPE_CDMA,
telephonyComponentFactory);
}
Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
sPhones[i] = phone;
}
for (int i = 0; i < numPhones; i++) {
PhoneBase phone = null;
int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
phone = TelephonyPluginDelegate.getInstance().makeGSMPhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i);
} else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
phone = TelephonyPluginDelegate.getInstance().makeCDMALTEPhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i);
}
Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
sProxyPhones[i] = TelephonyPluginDelegate.getInstance().makePhoneProxy(phone);
}
上图N的GSMCDMAPhone对应M的GSMPhone
GsmCdmaPhone.java
34. phone.dial
@Override
public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
throws CallStateException {
if (!isPhoneTypeGsm() && uusInfo != null) {
throw new CallStateException("Sending UUS information NOT supported in CDMA!");
}
// 前面判断了那么多次emergency,这里还在判断,emergency相关方法很多
boolean isEmergency = PhoneNumberUtils.isEmergencyNumber(dialString);
Phone imsPhone = mImsPhone;
CarrierConfigManager configManager =
(CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
.getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
boolean imsUseEnabled = isImsUseEnabled()// 瞧这众多条件
&& imsPhone != null
&& (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
(imsPhone.isVideoEnabled() && VideoProfile.isVideo(videoState)))
&& (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
&& !shallDialOnCircuitSwitch(intentExtras);
boolean useImsForEmergency = imsPhone != null
&& isEmergency
&& alwaysTryImsForEmergencyCarrierConfig
&& ImsManager.isNonTtyOrTtyOnVolteEnabled(mContext)
&& (imsPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF);
String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(PhoneNumberUtils.
stripSeparators(dialString));
boolean isUt = (dialPart.startsWith("*") || dialPart.startsWith("#"))
&& dialPart.endsWith("#");
boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
if (DBG) {// log很详细
logd("imsUseEnabled=" + imsUseEnabled
+ ", useImsForEmergency=" + useImsForEmergency
+ ", useImsForUt=" + useImsForUt
+ ", isUt=" + isUt
+ ", imsPhone=" + imsPhone
+ ", imsPhone.isVolteEnabled()="
+ ((imsPhone != null) ? imsPhone.isVolteEnabled() : "N/A")
+ ", imsPhone.isVowifiEnabled()="
+ ((imsPhone != null) ? imsPhone.isWifiCallingEnabled() : "N/A")
+ ", imsPhone.isVideoEnabled()="
+ ((imsPhone != null) ? imsPhone.isVideoEnabled() : "N/A")
+ ", imsPhone.getServiceState().getState()="
+ ((imsPhone != null) ? imsPhone.getServiceState().getState() : "N/A"));
}
Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mContext);// VoWiFi//检查wifi call、、
// 这里判断ims可用的话,会通过imsPhone dial
if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
try {
if (DBG) logd("Trying IMS PS call");
return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
} catch (CallStateException e) {
if (DBG) logd("IMS PS call exception " + e +
"imsUseEnabled =" + imsUseEnabled + ", imsPhone =" + imsPhone);
if (!Phone.CS_FALLBACK.equals(e.getMessage())) {
CallStateException ce = new CallStateException(e.getMessage());
ce.setStackTrace(e.getStackTrace());
throw ce;
}
}
}
- //...
}
我们继续看imsPhone这条路
ImsPhone.java
37. imsPhone.dial
38. dialInternal
@Override
public Connection
dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
throws CallStateException {
// ignore UUSInfo
return dialInternal (dialString, videoState, intentExtras);
}
private Connection dialInternal(String dialString, int videoState, Bundle intentExtras)
throws CallStateException {
boolean isConferenceUri = false;
boolean isSkipSchemaParsing = false;
//略
- // Only look at the Network portion for mmi
String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
ImsPhoneMmiCode mmi =
ImsPhoneMmiCode.newFromDialString(networkPortion, this);
if (DBG) Rlog.d(LOG_TAG,
"dialing w/ mmi '" + mmi + "'...");
if (mmi == null) {
return mCT.dial(dialString, videoState, intentExtras);
} else if (mmi.isTemporaryModeCLIR()) {
return mCT.dial(mmi.getDialingNumber(), mmi.getCLIRMode(), videoState, intentExtras);
} else if (!mmi.isSupportedOverImsPhone()) {
// If the mmi is not supported by IMS service,
// try to initiate dialing with default phone
throw new CallStateException(CS_FALLBACK);
} else {
mPendingMMIs.add(mmi);
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
mmi.processCode();
return null;
}
}
ImsPhoneCallTracker
39. mCT.dial
public Connection dial(String dialString, int videoState, Bundle intentExtras) throws
CallStateException {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
int oirMode = sp.getInt(Phone.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
return dial(dialString, oirMode, videoState, intentExtras);
}
40. dial
/**
* oirMode is one of the CLIR_ constants
*/
synchronized Connection
dial(String dialString, int clirMode, int videoState, Bundle intentExtras)
throws CallStateException {
boolean isPhoneInEcmMode = isPhoneInEcbMode();
boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
- //...
- boolean holdBeforeDial = false;
// The new call must be assigned to the foreground call.
// That call must be idle, so place anything that's
// there on hold
if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
}
// foreground call is empty for the newly dialed connection
holdBeforeDial = true;
// Cache the video state for pending MO call.
mPendingCallVideoState = videoState;
mPendingIntentExtras = intentExtras;
switchWaitingOrHoldingAndActive();
}
ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
mClirMode = clirMode;
synchronized (mSyncHold) {
if (holdBeforeDial) {
fgState = mForegroundCall.getState();
bgState = mBackgroundCall.getState();
//holding foreground call failed
if (fgState == ImsPhoneCall.State.ACTIVE) {
throw new CallStateException("cannot dial in current state");
}
//holding foreground call succeeded
if (bgState == ImsPhoneCall.State.HOLDING) {
holdBeforeDial = false;
}
}
mPendingMO = new ImsPhoneConnection(mPhone,//创建ImsConnection,作为dialInternal的参数
checkForTestEmergencyNumber(dialString), this, mForegroundCall,
isEmergencyNumber, intentExtras);
mPendingMO.setVideoState(videoState);
}
addConnection(mPendingMO);
if (!holdBeforeDial) {
if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
dialInternal(mPendingMO, clirMode, videoState, intentExtras);
} else {
try {
getEcbmInterface().exitEmergencyCallbackMode();
} catch (ImsException e) {
e.printStackTrace();
throw new CallStateException("service not available");
}
mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
pendingCallClirMode = clirMode;
mPendingCallVideoState = videoState;
mPendingIntentExtras = intentExtras;
pendingCallInEcm = true;
}
}
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return mPendingMO;
}
42. dialInternal
private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState,
Bundle intentExtras) {
if (conn == null) {
return;
}
boolean isConferenceUri = false;
boolean isSkipSchemaParsing = false;
boolean isCallPull = false;
- //略
// Always unmute when initiating a new call
setMute(false);
int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
//TODO(vt): Is this sufficient? At what point do we know the video state of the call?
conn.setVideoState(videoState);
try {
String[] callees = new String[] { conn.getAddress() };
ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
serviceType, callType);
profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
profile.setCallExtraBoolean(TelephonyProperties.EXTRAS_IS_CONFERENCE_URI,
isConferenceUri);
//略
// Translate call subject intent-extra from Telecom-specific extra key to the
// ImsCallProfile key.
- ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
callees, mImsCallListener);
conn.setImsCall(imsCall);
mMetrics.writeOnImsCallStart(mPhone.getPhoneId(),
imsCall.getSession());
setVideoCallProvider(conn, imsCall);
conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
} catch (ImsException e) {
loge("dialInternal : " + e);
conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
} catch (RemoteException e) {
}
}
/**
* Creates a {@link ImsCall} to make a call.
*
* @param serviceId a service id which is obtained from {@link ImsManager#open}
* @param profile a call profile to make the call
* (it contains service type, call type, media information, etc.)
* @param participants participants to invite the conference call
* @param listener listen to the call events from {@link ImsCall}
* @return a {@link ImsCall} object
* @throws ImsException if calling the IMS service results in an error
*/
public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
ImsCall.Listener listener) throws ImsException {
if (DBG) {
log("makeCall :: serviceId=" + serviceId
+ ", profile=" + profile);
}
checkAndThrowExceptionIfServiceUnavailable();
ImsCall call = new ImsCall(mContext, profile);//new ImsCall
call.setListener(listener);
ImsCallSession session = createCallSession(serviceId, profile);
boolean isConferenceUri = profile.getCallExtraBoolean(
TelephonyProperties.EXTRAS_IS_CONFERENCE_URI, false);
if (!isConferenceUri && (callees != null) && (callees.length == 1)) {
call.start(session, callees[0]);
} else {
call.start(session, callees);
}
return call;
}
44. createCallSession
/**
* Creates a {@link ImsCallSession} with the specified call profile.
* Use other methods, if applicable, instead of interacting with
* {@link ImsCallSession} directly.
*
* @param serviceId a service id which is obtained from {@link ImsManager#open}
* @param profile a call profile to make the call
*/
private ImsCallSession createCallSession(int serviceId,
ImsCallProfile profile) throws ImsException {
try {
return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
} catch (RemoteException e) {
return null;
}
}
---
小结:
从现在的内容看android N 上的MO 变化不大,placeCall()这个在M上新增的API,在N上的MO流程中细化了一下,估计三方app调用应该没影响;GSMPhone和CDMAPhone和并成GSMCDMAPhone;在上层似乎区分了personal和work(待验证)。