Emergency Call 分析

outgoing call 有三类, 分别用三种intent 去标记 三种intent 定义如下

     * This method will handle three kinds of actions:
     *
     * - CALL (action for usual outgoing voice calls)
     * - CALL_PRIVILEGED (can come from built-in apps like contacts / voice dialer / bluetooth)
     * - CALL_EMERGENCY (from the EmergencyDialer that's reachable from the lockscreen.)

紧急电话的几种调用方式

  拨打Emergency call 是不需要卡的。应该走的是一种特殊的链路。

  Emergency call 被两种Intent所触发(Intent.ACTION_CALL_EMERGENCY 和 Intent.ACTION_CALL_PRIVILEGED).
  CALL_PRIVILEGED是被系统级的应用所发起的,而CALL_EMERGENCY Intent 是从紧急呼叫的拨号盘里发起的,就是手机在没有SIM卡的模式下显示的拨号盘。CALL_PRIVILEGED这个intent 接到后会检查号码是否是emergency number, 如果是紧急电话的号码就会把intent的action 替换为ACTION_CALL_EMERGENCY, 如果不是就替换成ACTION_CALL。由此可看,虽然我们有三种call的intent action, 但是我们实际有效果的就是上述两种。CALL_PRIVILEGED这种action的call的作用的是能把紧急电话的号码带给phone app去处理,而普通的call action的intent是不可以的。
processIntent实现里用一个flag变量callNow来控制是否拨打电话, callNow在满足拨打emengency call的时候被置为true,正常call会被在startSipCallOptionHandler 这里到类SipCallOptionHandler去处理。
  3rd App是没有权限拨打Emergency call的, 如果3rd App在发起一个Intent.ACTION_CALL里带有Emergency number,会被阻止.OutgoingCallBroadcaster 类里的processIntent函数承担这部分的主要工作。
  下面是google的代码
526        if (Intent.ACTION_CALL.equals(action)) {
527            if (isPotentialEmergencyNumber) {
528                Log.w(TAG, "Cannot call potential emergency number '" + number
529                        + "' with CALL Intent " + intent + ".");
530                Log.i(TAG, "Launching default dialer instead...");
531
532                Intent invokeFrameworkDialer = new Intent();
533
534                // TwelveKeyDialer is in a tab so we really want
535                // DialtactsActivity.  Build the intent 'manually' to
536                // use the java resolver to find the dialer class (as
537                // opposed to a Context which look up known android
538                // packages only)
539                final Resources resources = getResources();
540                invokeFrameworkDialer.setClassName(
541                        resources.getString(R.string.ui_default_package),
542                        resources.getString(R.string.dialer_default_class));
543                invokeFrameworkDialer.setAction(Intent.ACTION_DIAL);
544                invokeFrameworkDialer.setData(intent.getData());
545                if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: "
546                               + invokeFrameworkDialer);
547                startActivity(invokeFrameworkDialer);
548                finish();
549                return;
550            }
551            callNow = false;

拨打紧急电话与modem的互动关系

   我们所注意到的是在拨打紧急电话的时候,当我们发现是一个紧急电话并且modem 在power off的模式下,会开启一个消息队列去开启modem startEmergencyCallFromAirplaneModeSequence这里面会有个timeout 的操作, 如果modem超时没有被开启,还会等待modem开启中, 继续往这个队列里放入消息。
具体的实现关键点中发现timeout这个retry机制是发一个timeout的消息到队列里去处理 sendEmptyMessageDelayed(RETRY_TIMEOUTTIME_BETWEEN_RETRIES);
timeout 的时间是5s ,
TIME_BETWEEN_RETRIES = 5000; // msec
类EmergencyCallHelper承担这部分消息处理的逻辑以及收到消息后采取的措施,是继续开启modem还是拨打紧急电话。

第三方应用是不能影响,阻止拨打紧急电话的。

   不论是紧急电话还是正常的电话,第三方应用是在系统中拨出一个电话后是可以接到ACTION_NEW_OUTGOING_CALL 这样的一个intent的,SIP call除外; 系统中拨打的电话包括contacts / voice dialer / bluetooth / dialer
这里理解的系统中拨的电话就是上面所说的拨打电话中在code中调用到的CALL_PRIVILEGED 和 CALL_EMERGENCY这两种Intent。

   但不同的是紧急电话不能被阻止,常规的号码是可以被第三方阻止的,第三方应该可以会接到这样一个通知。紧急电话是直接播出去后再发这个intent. 
目前SIP call还不能被第三方拦截,也就是说上面提到的拨打一个SIP call之后直接return, 不会再去发一个ACTION_NEW_OUTGOING_CALL给外部,google在代码注释上写未来需要支持。 在processIntent的实现里用一个flag变量callNo来控制是否拨打电话, callNow在满足拨打emengency call的时候被置为true,
   正常call会被在startSipCallOptionHandler 这里到类SipCallOptionHandler去处理。
   具体实现参照packages/services/Telephony/src/com/android/phone/OutgoingCallBroadcaster.java
424    private void processIntent(Intent intent) {
425        if (DBG) {
426            Log.v(TAG, "processIntent() = " + intent + ", thread: " + Thread.currentThread());
427        }
428        final Configuration configuration = getResources().getConfiguration();
429
430        // Outgoing phone calls are only allowed on "voice-capable" devices.
431        if (!PhoneGlobals.sVoiceCapable) {
432            Log.i(TAG, "This device is detected as non-voice-capable device.");
433            handleNonVoiceCapable(intent);
434            return;
435        }
436
437        String action = intent.getAction();
438        String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
439        // Check the number, don't convert for sip uri
440        // TODO put uriNumber under PhoneNumberUtils
441        if (number != null) {
442            if (!PhoneNumberUtils.isUriNumber(number)) {
443                number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
444                number = PhoneNumberUtils.stripSeparators(number);
445            }
446        } else {
447            Log.w(TAG, "The number obtained from Intent is null.");
448        }
449
450        AppOpsManager appOps = (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE);
451        int launchedFromUid;
452        String launchedFromPackage;
453        try {
454            launchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
455                    getActivityToken());
456            launchedFromPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage(
457                    getActivityToken());
458        } catch (RemoteException e) {
459            launchedFromUid = -1;
460            launchedFromPackage = null;
461        }
462        if (appOps.noteOp(AppOpsManager.OP_CALL_PHONE, launchedFromUid, launchedFromPackage)
463                != AppOpsManager.MODE_ALLOWED) {
464            Log.w(TAG, "Rejecting call from uid " + launchedFromUid + " package "
465                    + launchedFromPackage);
466            finish();
467            return;
468        }
469
470        // If true, this flag will indicate that the current call is a special kind
471        // of call (most likely an emergency number) that 3rd parties aren't allowed
472        // to intercept or affect in any way.  (In that case, we start the call
473        // immediately rather than going through the NEW_OUTGOING_CALL sequence.)
474        boolean callNow;
475
476        if (getClass().getName().equals(intent.getComponent().getClassName())) {
477            // If we were launched directly from the OutgoingCallBroadcaster,
478            // not one of its more privileged aliases, then make sure that
479            // only the non-privileged actions are allowed.
480            if (!Intent.ACTION_CALL.equals(intent.getAction())) {
481                Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL");
482                intent.setAction(Intent.ACTION_CALL);
483            }
484        }
485
486        // Check whether or not this is an emergency number, in order to
487        // enforce the restriction that only the CALL_PRIVILEGED and
488        // CALL_EMERGENCY intents are allowed to make emergency calls.
489        //
490        // (Note that the ACTION_CALL check below depends on the result of
491        // isPotentialLocalEmergencyNumber() rather than just plain
492        // isLocalEmergencyNumber(), to be 100% certain that we *don't*
493        // allow 3rd party apps to make emergency calls by passing in an
494        // "invalid" number like "9111234" that isn't technically an
495        // emergency number but might still result in an emergency call
496        // with some networks.)
497        final boolean isExactEmergencyNumber =
498                (number != null) && PhoneNumberUtils.isLocalEmergencyNumber(number, this);
499        final boolean isPotentialEmergencyNumber =
500                (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, this);
501        if (VDBG) {
502            Log.v(TAG, " - Checking restrictions for number '" + number + "':");
503            Log.v(TAG, "     isExactEmergencyNumber     = " + isExactEmergencyNumber);
504            Log.v(TAG, "     isPotentialEmergencyNumber = " + isPotentialEmergencyNumber);
505        }
506
507        /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
508        // TODO: This code is redundant with some code in InCallScreen: refactor.
509        if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
510            // We're handling a CALL_PRIVILEGED intent, so we know this request came
511            // from a trusted source (like the built-in dialer.)  So even a number
512            // that's *potentially* an emergency number can safely be promoted to
513            // CALL_EMERGENCY (since we *should* allow you to dial "91112345" from
514            // the dialer if you really want to.)
515            if (isPotentialEmergencyNumber) {
516                Log.i(TAG, "ACTION_CALL_PRIVILEGED is used while the number is a potential"
517                        + " emergency number. Use ACTION_CALL_EMERGENCY as an action instead.");
518                action = Intent.ACTION_CALL_EMERGENCY;
519            } else {
520                action = Intent.ACTION_CALL;
521            }
522            if (DBG) Log.v(TAG, " - updating action from CALL_PRIVILEGED to " + action);
523            intent.setAction(action);
524        }
525 当第三方App去拨打紧急电话时,会launch一个缺省的dialer然后直接return.也不会再去发Intent.ACTION_NEW_OUTGOING_CALL intent给外部使用
526        if (Intent.ACTION_CALL.equals(action)) {
527            if (isPotentialEmergencyNumber) {
528                Log.w(TAG, "Cannot call potential emergency number '" + number
529                        + "' with CALL Intent " + intent + ".");
530                Log.i(TAG, "Launching default dialer instead...");
531
532                Intent invokeFrameworkDialer = new Intent();
533
534                // TwelveKeyDialer is in a tab so we really want
535                // DialtactsActivity.  Build the intent 'manually' to
536                // use the java resolver to find the dialer class (as
537                // opposed to a Context which look up known android
538                // packages only)
539                final Resources resources = getResources();
540                invokeFrameworkDialer.setClassName(
541                        resources.getString(R.string.ui_default_package),
542                        resources.getString(R.string.dialer_default_class));
543                invokeFrameworkDialer.setAction(Intent.ACTION_DIAL);
544                invokeFrameworkDialer.setData(intent.getData());
545                if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: "
546                               + invokeFrameworkDialer);
547                startActivity(invokeFrameworkDialer);
548                finish();
549                return;
550            }
551            callNow = false;
552        } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
553            // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED
554            // intent that we just turned into a CALL_EMERGENCY intent (see
555            // above), or else it really is an CALL_EMERGENCY intent that
556            // came directly from some other app (e.g. the EmergencyDialer
557            // activity built in to the Phone app.)
558            // Make sure it's at least *possible* that this is really an
559            // emergency number.
560            if (!isPotentialEmergencyNumber) {
561                Log.w(TAG, "Cannot call non-potential-emergency number " + number
562                        + " with EMERGENCY_CALL Intent " + intent + "."
563                        + " Finish the Activity immediately.");
564                finish();
565                return;
566            }
567            callNow = true;
568        } else {
569            Log.e(TAG, "Unhandled Intent " + intent + ". Finish the Activity immediately.");
570            finish();
571            return;
572        }
573
574        // Make sure the screen is turned on.  This is probably the right
575        // thing to do, and more importantly it works around an issue in the
576        // activity manager where we will not launch activities consistently
577        // when the screen is off (since it is trying to keep them paused
578        // and has...  issues).
579        //
580        // Also, this ensures the device stays awake while doing the following
581        // broadcast; technically we should be holding a wake lock here
582        // as well.
583        PhoneGlobals.getInstance().wakeUpScreen();
584
585        // If number is null, we're probably trying to call a non-existent voicemail number,
586        // send an empty flash or something else is fishy.  Whatever the problem, there's no
587        // number, so there's no point in allowing apps to modify the number.
588        if (TextUtils.isEmpty(number)) {
589            if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {
590                Log.i(TAG, "onCreate: SEND_EMPTY_FLASH...");
591                PhoneUtils.sendEmptyFlash(PhoneGlobals.getPhone());
592                finish();
593                return;
594            } else {
595                Log.i(TAG, "onCreate: null or empty number, setting callNow=true...");
596                callNow = true;
597            }
598        }
599
600        if (callNow) {
601            // This is a special kind of call (most likely an emergency number)
602            // that 3rd parties aren't allowed to intercept or affect in any way.
603            // So initiate the outgoing call immediately.
604
605            Log.i(TAG, "onCreate(): callNow case! Calling placeCall(): " + intent);
606
607            // Initiate the outgoing call, and simultaneously launch the
608            // InCallScreen to display the in-call UI:
609            PhoneGlobals.getInstance().callController.placeCall(intent);
610
611            // Note we do *not* "return" here, but instead continue and
612            // send the ACTION_NEW_OUTGOING_CALL broadcast like for any
613            // other outgoing call.  (But when the broadcast finally
614            // reaches the OutgoingCallReceiver, we'll know not to
615            // initiate the call again because of the presence of the
616            // EXTRA_ALREADY_CALLED extra.)
617        }
618
619        // For now, SIP calls will be processed directly without a
620        // NEW_OUTGOING_CALL broadcast.
621        //
622        // TODO: In the future, though, 3rd party apps *should* be allowed to
623        // intercept outgoing calls to SIP addresses as well.  To do this, we should
624        // (1) update the NEW_OUTGOING_CALL intent documentation to explain this
625        // case, and (2) pass the outgoing SIP address by *not* overloading the
626        // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold
627        // the outgoing SIP address.  (Be sure to document whether it's a URI or just
628        // a plain address, whether it could be a tel: URI, etc.)
629        Uri uri = intent.getData();
630        String scheme = uri.getScheme();
631        if (Constants.SCHEME_SIP.equals(scheme) || PhoneNumberUtils.isUriNumber(number)) {
632            Log.i(TAG, "The requested number was detected as SIP call.");
633            startSipCallOptionHandler(this, intent, uri, number);
634            finish();
635            return;
636
637            // TODO: if there's ever a way for SIP calls to trigger a
638            // "callNow=true" case (see above), we'll need to handle that
639            // case here too (most likely by just doing nothing at all.)
640        }
641 当去拨打一个电话(紧急电话或常规号码,不包括SIP call)后,暴露给外部一个ACTION_NEW_OUTGOING_CALL的intent
642        Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
643        if (number != null) {
644            broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
645        }
646        CallGatewayManager.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);
647        broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);
648        broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());
649        // Need to raise foreground in-call UI as soon as possible while allowing 3rd party app
650        // to intercept the outgoing call.
651        broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
652        if (DBG) Log.v(TAG, " - Broadcasting intent: " + broadcastIntent + ".");
653
654        // Set a timer so that we can prepare for unexpected delay introduced by the broadcast.
655        // If it takes too much time, the timer will show "waiting" spinner.
656        // This message will be removed when OutgoingCallReceiver#onReceive() is called before the
657        // timeout.
658        mHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT,
659                OUTGOING_CALL_TIMEOUT_THRESHOLD);
660        sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.OWNER,
661                PERMISSION, new OutgoingCallReceiver(),
662                null,  // scheduler
663                Activity.RESULT_OK,  // initialCode
664                number,  // initialData: initial value for the result data
665                null);  // initialExtras
666    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值