(学习笔记)android 5.0 系统去电流程状态判断(上)

Posted 王_健

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(学习笔记)android 5.0 系统去电流程状态判断(上)相关的知识,希望对你有一定的参考价值。

(学习笔记)android 5.0 系统去电流程状态判断


需求:

    判断系统拨号的每个状态,根据状态不同弹窗提示。

需求分析:

    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卡判断的代码。感谢大神的图。

从系统拨号盘按下拨号按钮开始分析:

一、从Dialer到InCallUI的流程分析

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进程间的处理流程。这个过程中对需求中的无卡情况进行了非常详细的处理,所以得出结果:只要改一下字串就行了。

下个笔记需要对接下来的几个状态进行分析,相对而言稍微复杂一点。


以上是关于(学习笔记)android 5.0 系统去电流程状态判断(上)的主要内容,如果未能解决你的问题,请参考以下文章

(学习笔记)android 5.0 系统去电流程状态判断(下)

(学习笔记)android 5.0 系统去电流程状态判断(下)

Tiny4412 Android 5.0 编译系统学习笔记

Android 5.0 Camera系统源码分析:Camera预览3A流程

5.0以上机器XPOSED框架安装流程

无论如何要为Android中的来电和去电制作我的自定义用户界面吗?