判断系统拨号的每个状态,根据状态不同弹窗提示。
1、无卡开机要求提示用户:Emergency calls only. Please enter Emergency Number;
2、无网络覆盖情况,当户用拨打所有号码,界面上立即给出提示“No network coverage!";
3、飞行模式打开的情况下,用户拨打紧急号码时,弹出提示,提示用户要先关闭飞行模式;
4、当两张卡都处于有限服务状态的时候 拨打号码, 手机提示"Emergency call only, Please enter an Emergency number!"
通过需求了解去电的基本流程。
以下有关流程图转自:http://blog.csdn.net/yihongyuelan
流程图分析的太过详细,重点为了解决需求问题,从而简要分析一下代码结构。
这一篇笔记只能分析流程图上的从Dialer到InCallUI进程的启动流程(第一部分),找出对sim卡判断的代码。感谢大神的图。
从系统拨号盘按下拨号按钮开始分析:
1.按下拨号按钮开始 (DialpadFragment.java)
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.dialpad_floating_action_button:
mHaptic.vibrate(); // 震动一下
handleDialButtonPressed(); //进入去电流程
break;
case R.id.deleteButton: {
keyPressed(KeyEvent.KEYCODE_DEL);
break;
}
case R.id.digits: {
if (!isDigitsEmpty()) {
mDigits.setCursorVisible(true);
}
break;
}
case R.id.dialpad_overflow: {
mOverflowPopupMenu.show();
break;
}
default: {
Log.wtf(TAG, "Unexpected onClick() event from: " + view);
return;
}
}
}
首先会调用一下震动器接口,接着进入handleDialButtonPressed()方法,中间比较繁琐的代码省略。
handleDialButtonPressed(int type) {
if (isDigitsEmpty()) { // 没有输入电话号码,号码为空.
// 如果上次输入记录不为空,自动补全上次输入的号码。。。
handleDialButtonClickWithEmptyDigits();
} else {
final String number = mDigits.getText().toString();
// "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
// test equipment.
// TODO: clean it up.
// mProhibitedPhoneNumberRegexp 为空,默认不走这段代码
if (number != null
&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
&& number.matches(mProhibitedPhoneNumberRegexp)) {
.....
} else {
final Intent intent;
/** M: [Ip Dial] check the type of call @{ */
// 从拨号盘呼出,默认为type = Constants.DIAL_NUMBER_INTENT_NORMAL
if (type != Constants.DIAL_NUMBER_INTENT_NORMAL) {
intent = CallUtil.getCallIntent(CallUtil.getCallUri(number),
(getActivity() instanceof DialtactsActivity ?
((DialtactsActivity) getActivity()).getCallOrigin() : null),
type);
} else {
intent = CallUtil.getCallIntent(number,
(getActivity() instanceof DialtactsActivity ?
((DialtactsActivity) getActivity()).getCallOrigin() : null));
}
/** @} */
DialerUtils.startActivityWithErrorToast(getActivity(), intent);
hideAndClearDialpad(false);
}
}
}
HandleDialButtonPressed方法主要为了处理号码是否为空的情况,号码为空就试着去调用上次输入的记录,不为空就通过CallUtil.getCallIntent()方法创建一个意图,getCallIntent()方法对callOrigin和accountHandle也有非空判断,callOrigin表示的应该是是否使用系统拨号程序,accountHandle默认为空。
意图包含了Action 和 Uri,一般拨号 Uri 的形式如”tel : 123456“,”tel“ 为uri中scheme的属性 ,String类型; ”123456“为 ssp 属性,Part类型。后面对uri有各种判断。
拨号 Action 有几种,比如Intent.ACTION_CALL_PRIVILEGED 和 Intent.ACTION_CALL 等等。由于是系统拨号程序,此处CallUtil类将Aciton设置成Intent.ACTION_CALL_PRIVILEGED。Intent.ACTION_CALL一般为第三方拨号程序启用的Aciton。
接下来通过DialerUtils.startActivityWithErrorToast()方法进入具体拨号流程。此方法处理比较简单,如果Action 为 Intent.ACTION_CALL_PRIVILEGED 直接跳转,如果是 Intent.ACTION_CALL,就封装进一个bundle(bundle 存入的是最后一次点击屏幕的x/y点的坐标,具体干什么不知道,TouchPointManager 类上面注释说明 ”Used to pass on to the InCallUI for animation“),接着再跳转。
5.0上已经将同样拥有Aciton: Intent.ACTION_CALL_PRIVILEGED 的类OutgoingCallBroadcaster的enabled属性设置成false。所以直接启动CallActivity。
private void processIntent(Intent intent) {
// Ensure call intents are not processed on devices that are not capable of calling.
if (!isVoiceCapable()) {
return;
}
//如果不是通过ACTION_CALL_PRIVILEGED Action启动的,强制将intent.Acion设为ACTION_CALL
verifyCallAction(intent);
String action = intent.getAction();
if (Intent.ACTION_CALL.equals(action) ||
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
Log.d(this, "ACTION CALL");
processOutgoingCallIntent(intent);
} else if (Intent.ACTION_PICK.equals(action)) {
// M: add for select account for MMI
Log.d(this, "ACTION_PICK");
processSelectSubIntent(intent);
}
}
从onCreate()直接进入processIntent()方法;
经过verifyCallAction()过滤后,根据Aciton类型来选择执行相应的方法。由于目前的Aciton是属于拨号类型,因此直接走processOutgoingCallIntent()方法下的代码。
private void processOutgoingCallIntent(Intent intent) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
// 如果拨号时不加类似@和**,一般scheme = "tel";
if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
// PhoneAccount.SCHEME_VOICEMAIL 为 "voicemail"
handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
}
Log.d(this, "phone number = "+ handle.getSchemeSpecificPart());
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
....
intent.putExtra(CallReceiver.KEY_IS_DEFAULT_DIALER, isDefaultDialer());
if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
Log.d(this, "process outgoing call intent...");
CallReceiver.processOutgoingCallIntent(getApplicationContext(), intent);
} else {
sendBroadcastToReceiver(intent, false /* isIncoming */);
}
}
接着判断当前用户组类型,如果不是所谓的“设备所有者”,就发广播;否则直接调用CallReceiver.processOutgoingCallIntent方法;最终走的都是这个方法。只是用广播的形式可能需要接受者有相应的权限。
static void processOutgoingCallIntent(Context context, Intent intent) {
/// M: call control start. @{
// Check outgoing call condition, if exist ring call or 1A2H, or 1A1H in a same phone
// account, could not start a new outgoing call. Here need to stop dialing out.
if (!CallsManager.getInstance().canStartOutgoingCall(intent.getData())) {
Toast.makeText(context,
context.getResources().getString(R.string.outgoing_call_error_limit_exceeded),
Toast.LENGTH_SHORT).show();
Log.d(TAG, "Rejecting out going call due to LIMIT EXCEEDED");
return;
}
// 绑定 IncallUI 进程的InCallService服务类。
CallsManager.getInstance().getInCallController().bind();
/// @}
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
}
// phoneAccountHandle 为空,之前并没有对此处理过。
PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
....
Bundle clientExtras = null;
if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
}
if (clientExtras == null) {
clientExtras = Bundle.EMPTY;
}
final boolean isDefaultDialer = intent.getBooleanExtra(KEY_IS_DEFAULT_DIALER, false);
clientExtras.putBoolean(Constants.EXTRA_IS_IP_DIAL, intent.getBooleanExtra(Constants.EXTRA_IS_IP_DIAL, false));
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
Call call = getCallsManager().startOutgoingCall(handle, phoneAccountHandle, clientExtras);
if (call != null) {
/// M: ip dial. ip prefix already add, here need to change intent @{
if (call.isIpCall()) {
intent.setData(call.getHandle());
}
/// @}
// 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, getCallsManager(), call, intent, isDefaultDialer);
final int result = broadcaster.processIntent();
final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
if (!success && call != null) {
disconnectCallAndShowErrorDialog(context, call, result);
}
}
}
首先判断当前电话状态,一般电话有空闲状态、接听状态、Ringing状态(有电话)和HOLD状态(保持当前通话,接入别的电话)等等。如果当前状态是Ringing状态,或者当前已经有至少一个电话正在接通,但是不具备HOLD这个功能,没办法只能在这里打住。
具备以上条件后,通过InCallController这个类,首次绑定InCallService这个服务类,绑定成功后立即调用了inCallService.setInCallAdapter、addCall和onAudioStateChanged三个方法,setInCallAdapter方法初始化Phone对象,流程图上执行的OnPhoneCreate()方法,在代码里是个空方法。可能换成其他处理方法了。
final void internalAddCall(ParcelableCall parcelableCall) {
Call call = new Call(this, parcelableCall.getId(), mInCallAdapter);
mCallByTelecomCallId.put(parcelableCall.getId(), call);
mCalls.add(call);
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
fireCallAdded(call);
}
Phone.internalAddCall()方法,即调用call.internalUpdate方法通知InCallUI进程当前Call的状态。
同时执行了fireCallAdded(call)方法,调用回调,通过CallList.Phone.Listener.onCallAdded方法实例化一个Call对象,设置当前Call的状态属性mState。最后走到InCallPresenter.startOrFinishUi()开始了InCallActivity的显示:
private InCallState startOrFinishUi(InCallState newState) {
Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState);
// TODO: Consider a proper state machine implementation
// If the state isn't changing or if we're transitioning from pending outgoing to actual
// outgoing, we have already done any starting/stopping of activities in a previous pass
// ...so lets cut out early
boolean alreadyOutgoing = mInCallState == InCallState.PENDING_OUTGOING &&
newState == InCallState.OUTGOING;
if (newState == mInCallState || alreadyOutgoing) {
return newState;
}
....
final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
final boolean mainUiNotVisible = !isShowingInCallUi() || !getCallCardFragmentVisible();
final boolean showCallUi = ((InCallState.PENDING_OUTGOING == newState ||
InCallState.OUTGOING == newState) && mainUiNotVisible);
boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted();
if (activityIsFinishing) {
Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState);
return mInCallState;
}
/** * 首次启动showCallUi 应该为true,showAccountPicker取决于 * CallManager.startOutgoingCall方法中对phoneAccountHandle和Accounts的判断。 /** if (showCallUi || showAccountPicker) { Log.i(this, "[startOrFinishUi]Start in call UI, showAccountPicker: " + showAccountPicker); showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
} else if (startStartupSequence) {
......
} else if (newState == InCallState.NO_CALLS) {
// The new state is the no calls state. Tear everything down.
attemptFinishActivity();
attemptCleanup();
}
return newState;
}
showInCall()方法启动了InCallActivity,在InCallActivity.OnCreate().internalResolveIntent()方法中终于有对SIM卡状态进行了处理:
private void internalResolveIntent(Intent intent) {
final String action = intent.getAction();
if (action.equals(intent.ACTION_MAIN)) {
//一开始对系统拨号盘的Aciton进行判断的时候,对手指按下的坐标进行了封装,在这里终于用上了。
Point touchPoint = null;
if (TouchPointManager.getInstance().hasValidPoint()) {
// Use the most immediate touch point in the InCallUi if available
touchPoint = TouchPointManager.getInstance().getPoint();
} else {
// Otherwise retrieve the touch point from the call intent
if (call != null) {
touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT);
}
}
mCallCardFragment.animateForNewOutgoingCall(touchPoint);
//这里应该就是对无卡状态,并且拨打的不是紧急号码的情况作出的判断吧
if (call != null && !isEmergencyCall(call)) {
final List<PhoneAccountHandle> phoneAccountHandles = extras
.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
if (call.getAccountHandle() == null &&
(phoneAccountHandles == null || phoneAccountHandles.isEmpty())) {
TelecomAdapter.getInstance().disconnectCall(call.getId());
}
}
}
}
TelecomAdapter.getInstance().disconnectCall()最终会走到InCallUI进程下的Call.update():
private void update() {
233 InCallTrace.begin("callUpdate");
234 int oldState = getState();
235 updateFromTelecommCall();
236 if (oldState != getState() && getState() == Call.State.DISCONNECTED) {
237 CallList.getInstance().onDisconnect(this);
238 } else {
239 CallList.getInstance().onUpdate(this);
240 }
241 InCallTrace.end("callUpdate");
242 }
经过Callsmanager时Call.state已经被修改成DISCONNECTED状态,这里就会直接执行CallList.getInstance().onDisconnect(this)方法,最终走到InCallPresenter.onDisconnect方法:
@Override
426 public void onDisconnect(Call call) {
427 hideDialpadForDisconnect();
428 maybeShowErrorDialogOnDisconnect(call);
429
430 // We need to do the run the same code as onCallListChange.
431 onCallListChange(CallList.getInstance());
432
433 if (isActivityStarted()) {
434 mInCallActivity.dismissKeyguard(false);
435 }
436 }
private void maybeShowErrorDialogOnDisconnect(Call call) {
844 // For newly disconnected calls, we may want to show a dialog on specific error conditions
845 if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) {
846 if (call.getAccountHandle() == null && !call.isConferenceCall()) {
847 setDisconnectCauseForMissingAccounts(call);
848 }
849 mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause());
850 /// M: For ALPS01798961. @{
851 } else if (!isActivityStarted() && call.getState() == Call.State.DISCONNECTED) {
852 mDisconnectCause = call.getDisconnectCause();
853 }
854 /// @}
855 }
public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
Log.d(this, "maybeShowErrorDialogOnDisconnect");
Log.d(this, "disconnectCause.getDescription() :"+disconnectCause.getDescription());
if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription())
&& (disconnectCause.getCode() == DisconnectCause.ERROR ||
disconnectCause.getCode() == DisconnectCause.RESTRICTED)) {
showErrorDialog(disconnectCause.getDescription());
/// M: Fix ALPS01790208. @{
// Dimiss activity if needed.
} else {
dismissActivityIfNeeded();
/// @}
}
}
private void showErrorDialog(CharSequence msg) {
Log.i(this, "Show Dialog: " + msg);
dismissPendingDialogs();
mDialog = new AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton(R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onDialogDismissed();
}})
.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
onDialogDismissed();
}})
.create();
mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
mDialog.show();
}
以上几个log打印结果如下:
01-01 10:18:43.527 1913 1913 D Telecom : InCallController: call handle tel:111, is ip dial= false
01-01 10:18:43.527 1913 1913 V Telecom : InCallController: updateCall [50196459, DISCONNECTED...
01-01 10:18:43.610 8571 8571 D InCall : Call - updateFromTelecommCall: ...
01-01 10:18:43.610 8571 8571 I InCall : CallList - onDisconnect: ...
01-01 10:18:43.610 8571 8571 D InCall : InCallActivity - maybeShowErrorDialogOnDisconnect
01-01 10:18:43.610 8571 8571 I InCall : InCallActivity - Show Dialog: 没有SIM卡/SIM卡错误
以上仅仅分析到CallManager.startOutgoingCall方法,方法中首次与IncallUI绑定,telecom模块与InCallUI进程间的处理流程。这个过程中对需求中的无卡情况进行了非常详细的处理,所以得出结果:只要改一下字串就行了。
下个笔记需要对接下来的几个状态进行分析,相对而言稍微复杂一点。