Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.phone;
     18 
     19 import com.android.internal.telephony.CallManager;
     20 import com.android.internal.telephony.Phone;
     21 import com.android.internal.telephony.PhoneConstants;
     22 import com.android.internal.telephony.TelephonyCapabilities;
     23 import com.android.phone.CallGatewayManager.RawGatewayInfo;
     24 import com.android.phone.Constants.CallStatusCode;
     25 
     26 import android.content.ComponentName;
     27 import android.content.Intent;
     28 import android.net.Uri;
     29 import android.os.Handler;
     30 import android.os.Message;
     31 import android.os.SystemProperties;
     32 import android.provider.CallLog.Calls;
     33 import android.telecom.PhoneAccount;
     34 import android.telephony.PhoneNumberUtils;
     35 import android.telephony.ServiceState;
     36 import android.util.Log;
     37 
     38 /**
     39  * Phone app module in charge of "call control".
     40  *
     41  * This is a singleton object which acts as the interface to the telephony layer
     42  * (and other parts of the Android framework) for all user-initiated telephony
     43  * functionality, like making outgoing calls.
     44  *
     45  * This functionality includes things like:
     46  *   - actually running the placeCall() method and handling errors or retries
     47  *   - running the whole "emergency call in airplane mode" sequence
     48  *   - running the state machine of MMI sequences
     49  *   - restoring/resetting mute and speaker state when a new call starts
     50  *   - updating the prox sensor wake lock state
     51  *   - resolving what the voicemail: intent should mean (and making the call)
     52  *
     53  * The single CallController instance stays around forever; it's not tied
     54  * to the lifecycle of any particular Activity (like the InCallScreen).
     55  * There's also no implementation of onscreen UI here (that's all in InCallScreen).
     56  *
     57  * Note that this class does not handle asynchronous events from the telephony
     58  * layer, like reacting to an incoming call; see CallNotifier for that.  This
     59  * class purely handles actions initiated by the user, like outgoing calls.
     60  */
     61 public class CallController extends Handler {
     62     private static final String TAG = "CallController";
     63     private static final boolean DBG =
     64             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     65     // Do not check in with VDBG = true, since that may write PII to the system log.
     66     private static final boolean VDBG = false;
     67 
     68     /** The singleton CallController instance. */
     69     private static CallController sInstance;
     70 
     71     final private PhoneGlobals mApp;
     72     final private CallManager mCM;
     73     final private CallLogger mCallLogger;
     74     final private CallGatewayManager mCallGatewayManager;
     75 
     76     /** Helper object for emergency calls in some rare use cases.  Created lazily. */
     77     private EmergencyCallHelper mEmergencyCallHelper;
     78 
     79 
     80     //
     81     // Message codes; see handleMessage().
     82     //
     83 
     84     private static final int THREEWAY_CALLERINFO_DISPLAY_DONE = 1;
     85 
     86 
     87     //
     88     // Misc constants.
     89     //
     90 
     91     // Amount of time the UI should display "Dialing" when initiating a CDMA
     92     // 3way call.  (See comments on the THRWAY_ACTIVE case in
     93     // placeCallInternal() for more info.)
     94     private static final int THREEWAY_CALLERINFO_DISPLAY_TIME = 3000; // msec
     95 
     96 
     97     /**
     98      * Initialize the singleton CallController instance.
     99      *
    100      * This is only done once, at startup, from PhoneApp.onCreate().
    101      * From then on, the CallController instance is available via the
    102      * PhoneApp's public "callController" field, which is why there's no
    103      * getInstance() method here.
    104      */
    105     /* package */ static CallController init(PhoneGlobals app, CallLogger callLogger,
    106             CallGatewayManager callGatewayManager) {
    107         synchronized (CallController.class) {
    108             if (sInstance == null) {
    109                 sInstance = new CallController(app, callLogger, callGatewayManager);
    110             } else {
    111                 Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
    112             }
    113             return sInstance;
    114         }
    115     }
    116 
    117     /**
    118      * Private constructor (this is a singleton).
    119      * @see init()
    120      */
    121     private CallController(PhoneGlobals app, CallLogger callLogger,
    122             CallGatewayManager callGatewayManager) {
    123         if (DBG) log("CallController constructor: app = " + app);
    124         mApp = app;
    125         mCM = app.mCM;
    126         mCallLogger = callLogger;
    127         mCallGatewayManager = callGatewayManager;
    128     }
    129 
    130     @Override
    131     public void handleMessage(Message msg) {
    132         if (VDBG) log("handleMessage: " + msg);
    133         switch (msg.what) {
    134 
    135             case THREEWAY_CALLERINFO_DISPLAY_DONE:
    136                 if (DBG) log("THREEWAY_CALLERINFO_DISPLAY_DONE...");
    137 
    138                 if (mApp.cdmaPhoneCallState.getCurrentCallState()
    139                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
    140                     // Reset the mThreeWayCallOrigStateDialing state
    141                     mApp.cdmaPhoneCallState.setThreeWayCallOrigState(false);
    142 
    143                     // TODO: Remove this code.
    144                     //mApp.getCallModeler().setCdmaOutgoing3WayCall(null);
    145                 }
    146                 break;
    147 
    148             default:
    149                 Log.wtf(TAG, "handleMessage: unexpected code: " + msg);
    150                 break;
    151         }
    152     }
    153 
    154     //
    155     // Outgoing call sequence
    156     //
    157 
    158     /**
    159      * Initiate an outgoing call.
    160      *
    161      * Here's the most typical outgoing call sequence:
    162      *
    163      *  (1) OutgoingCallBroadcaster receives a CALL intent and sends the
    164      *      NEW_OUTGOING_CALL broadcast
    165      *
    166      *  (2) The broadcast finally reaches OutgoingCallReceiver, which stashes
    167      *      away a copy of the original CALL intent and launches
    168      *      SipCallOptionHandler
    169      *
    170      *  (3) SipCallOptionHandler decides whether this is a PSTN or SIP call (and
    171      *      in some cases brings up a dialog to let the user choose), and
    172      *      ultimately calls CallController.placeCall() (from the
    173      *      setResultAndFinish() method) with the stashed-away intent from step
    174      *      (2) as the "intent" parameter.
    175      *
    176      *  (4) Here in CallController.placeCall() we read the phone number or SIP
    177      *      address out of the intent and actually initiate the call, and
    178      *      simultaneously launch the InCallScreen to display the in-call UI.
    179      *
    180      *  (5) We handle various errors by directing the InCallScreen to
    181      *      display error messages or dialogs (via the InCallUiState
    182      *      "pending call status code" flag), and in some cases we also
    183      *      sometimes continue working in the background to resolve the
    184      *      problem (like in the case of an emergency call while in
    185      *      airplane mode).  Any time that some onscreen indication to the
    186      *      user needs to change, we update the "status dialog" info in
    187      *      the inCallUiState and (re)launch the InCallScreen to make sure
    188      *      it's visible.
    189      */
    190     public void placeCall(Intent intent) {
    191         log("placeCall()...  intent = " + intent);
    192         if (VDBG) log("                extras = " + intent.getExtras());
    193 
    194         // TODO: Do we need to hold a wake lock while this method runs?
    195         //       Or did we already acquire one somewhere earlier
    196         //       in this sequence (like when we first received the CALL intent?)
    197 
    198         if (intent == null) {
    199             Log.wtf(TAG, "placeCall: called with null intent");
    200             throw new IllegalArgumentException("placeCall: called with null intent");
    201         }
    202 
    203         String action = intent.getAction();
    204         Uri uri = intent.getData();
    205         if (uri == null) {
    206             Log.wtf(TAG, "placeCall: intent had no data");
    207             throw new IllegalArgumentException("placeCall: intent had no data");
    208         }
    209 
    210         String scheme = uri.getScheme();
    211         String number = PhoneNumberUtils.getNumberFromIntent(intent, mApp);
    212         if (VDBG) {
    213             log("- action: " + action);
    214             log("- uri: " + uri);
    215             log("- scheme: " + scheme);
    216             log("- number: " + number);
    217         }
    218 
    219         // This method should only be used with the various flavors of CALL
    220         // intents.  (It doesn't make sense for any other action to trigger an
    221         // outgoing call!)
    222         if (!(Intent.ACTION_CALL.equals(action)
    223               || Intent.ACTION_CALL_EMERGENCY.equals(action)
    224               || Intent.ACTION_CALL_PRIVILEGED.equals(action))) {
    225             Log.wtf(TAG, "placeCall: unexpected intent action " + action);
    226             throw new IllegalArgumentException("Unexpected action: " + action);
    227         }
    228 
    229         // Check to see if this is an OTASP call (the "activation" call
    230         // used to provision CDMA devices), and if so, do some
    231         // OTASP-specific setup.
    232         Phone phone = mApp.mCM.getDefaultPhone();
    233         if (TelephonyCapabilities.supportsOtasp(phone)) {
    234             checkForOtaspCall(intent);
    235         }
    236 
    237         CallStatusCode status = placeCallInternal(intent);
    238 
    239         switch (status) {
    240             // Call was placed successfully:
    241             case SUCCESS:
    242             case EXITED_ECM:
    243                 if (DBG) log("==> placeCall(): success from placeCallInternal(): " + status);
    244                 break;
    245 
    246             default:
    247                 // Any other status code is a failure.
    248                 log("==> placeCall(): failure code from placeCallInternal(): " + status);
    249                 // Handle the various error conditions that can occur when
    250                 // initiating an outgoing call, typically by directing the
    251                 // InCallScreen to display a diagnostic message (via the
    252                 // "pending call status code" flag.)
    253                 handleOutgoingCallError(status);
    254                 break;
    255         }
    256 
    257         // Finally, regardless of whether we successfully initiated the
    258         // outgoing call or not, force the InCallScreen to come to the
    259         // foreground.
    260         //
    261         // (For successful calls the the user will just see the normal
    262         // in-call UI.  Or if there was an error, the InCallScreen will
    263         // notice the InCallUiState pending call status code flag and display an
    264         // error indication instead.)
    265     }
    266 
    267     /**
    268      * Actually make a call to whomever the intent tells us to.
    269      *
    270      * Note that there's no need to explicitly update (or refresh) the
    271      * in-call UI at any point in this method, since a fresh InCallScreen
    272      * instance will be launched automatically after we return (see
    273      * placeCall() above.)
    274      *
    275      * @param intent the CALL intent describing whom to call
    276      * @return CallStatusCode.SUCCESS if we successfully initiated an
    277      *    outgoing call.  If there was some kind of failure, return one of
    278      *    the other CallStatusCode codes indicating what went wrong.
    279      */
    280     private CallStatusCode placeCallInternal(Intent intent) {
    281         if (DBG) log("placeCallInternal()...  intent = " + intent);
    282 
    283         // TODO: This method is too long.  Break it down into more
    284         // manageable chunks.
    285 
    286         final Uri uri = intent.getData();
    287         final String scheme = (uri != null) ? uri.getScheme() : null;
    288         String number;
    289         Phone phone = null;
    290 
    291         // Check the current ServiceState to make sure it's OK
    292         // to even try making a call.
    293         CallStatusCode okToCallStatus = checkIfOkToInitiateOutgoingCall(
    294                 mCM.getServiceState());
    295 
    296         // TODO: Streamline the logic here.  Currently, the code is
    297         // unchanged from its original form in InCallScreen.java.  But we
    298         // should fix a couple of things:
    299         // - Don't call checkIfOkToInitiateOutgoingCall() more than once
    300         // - Wrap the try/catch for VoiceMailNumberMissingException
    301         //   around *only* the call that can throw that exception.
    302 
    303         try {
    304             number = PhoneUtils.getInitialNumber(intent);
    305             if (VDBG) log("- actual number to dial: '" + number + "'");
    306 
    307             // find the phone first
    308             // TODO Need a way to determine which phone to place the call
    309             // It could be determined by SIP setting, i.e. always,
    310             // or by number, i.e. for international,
    311             // or by user selection, i.e., dialog query,
    312             // or any of combinations
    313             String sipPhoneUri = intent.getStringExtra(
    314                     OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI);
    315             ComponentName thirdPartyCallComponent = (ComponentName) intent.getParcelableExtra(
    316                     OutgoingCallBroadcaster.EXTRA_THIRD_PARTY_CALL_COMPONENT);
    317             phone = PhoneUtils.pickPhoneBasedOnNumber(mCM, scheme, number, sipPhoneUri,
    318                     thirdPartyCallComponent);
    319             if (VDBG) log("- got Phone instance: " + phone + ", class = " + phone.getClass());
    320 
    321             // update okToCallStatus based on new phone
    322             okToCallStatus = checkIfOkToInitiateOutgoingCall(
    323                     phone.getServiceState().getState());
    324 
    325         } catch (PhoneUtils.VoiceMailNumberMissingException ex) {
    326             // If the call status is NOT in an acceptable state, it
    327             // may effect the way the voicemail number is being
    328             // retrieved.  Mask the VoiceMailNumberMissingException
    329             // with the underlying issue of the phone state.
    330             if (okToCallStatus != CallStatusCode.SUCCESS) {
    331                 if (DBG) log("Voicemail number not reachable in current SIM card state.");
    332                 return okToCallStatus;
    333             }
    334             if (DBG) log("VoiceMailNumberMissingException from getInitialNumber()");
    335             return CallStatusCode.VOICEMAIL_NUMBER_MISSING;
    336         }
    337 
    338         if (number == null) {
    339             Log.w(TAG, "placeCall: couldn't get a phone number from Intent " + intent);
    340             return CallStatusCode.NO_PHONE_NUMBER_SUPPLIED;
    341         }
    342 
    343 
    344         // Sanity-check that ACTION_CALL_EMERGENCY is used if and only if
    345         // this is a call to an emergency number
    346         // (This is just a sanity-check; this policy *should* really be
    347         // enforced in OutgoingCallBroadcaster.onCreate(), which is the
    348         // main entry point for the CALL and CALL_* intents.)
    349         boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(mApp, number);
    350         boolean isPotentialEmergencyNumber =
    351                 PhoneNumberUtils.isPotentialLocalEmergencyNumber(mApp, number);
    352         boolean isEmergencyIntent = Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction());
    353 
    354         if (isPotentialEmergencyNumber && !isEmergencyIntent) {
    355             Log.e(TAG, "Non-CALL_EMERGENCY Intent " + intent
    356                     + " attempted to call potential emergency number " + number
    357                     + ".");
    358             return CallStatusCode.CALL_FAILED;
    359         } else if (!isPotentialEmergencyNumber && isEmergencyIntent) {
    360             Log.e(TAG, "Received CALL_EMERGENCY Intent " + intent
    361                     + " with non-potential-emergency number " + number
    362                     + " -- failing call.");
    363             return CallStatusCode.CALL_FAILED;
    364         }
    365 
    366         // If we're trying to call an emergency number, then it's OK to
    367         // proceed in certain states where we'd otherwise bring up
    368         // an error dialog:
    369         // - If we're in EMERGENCY_ONLY mode, then (obviously) you're allowed
    370         //   to dial emergency numbers.
    371         // - If we're OUT_OF_SERVICE, we still attempt to make a call,
    372         //   since the radio will register to any available network.
    373 
    374         if (isEmergencyNumber
    375             && ((okToCallStatus == CallStatusCode.EMERGENCY_ONLY)
    376                 || (okToCallStatus == CallStatusCode.OUT_OF_SERVICE))) {
    377             if (DBG) log("placeCall: Emergency number detected with status = " + okToCallStatus);
    378             okToCallStatus = CallStatusCode.SUCCESS;
    379             if (DBG) log("==> UPDATING status to: " + okToCallStatus);
    380         }
    381 
    382         if (okToCallStatus != CallStatusCode.SUCCESS) {
    383             // If this is an emergency call, launch the EmergencyCallHelperService
    384             // to turn on the radio and retry the call.
    385             if (isEmergencyNumber && (okToCallStatus == CallStatusCode.POWER_OFF)) {
    386                 Log.i(TAG, "placeCall: Trying to make emergency call while POWER_OFF!");
    387 
    388                 // If needed, lazily instantiate an EmergencyCallHelper instance.
    389                 synchronized (this) {
    390                     if (mEmergencyCallHelper == null) {
    391                         mEmergencyCallHelper = new EmergencyCallHelper(this);
    392                     }
    393                 }
    394 
    395                 // ...and kick off the "emergency call from airplane mode" sequence.
    396                 mEmergencyCallHelper.startEmergencyCallFromAirplaneModeSequence(number);
    397 
    398                 // Finally, return CallStatusCode.SUCCESS right now so
    399                 // that the in-call UI will remain visible (in order to
    400                 // display the progress indication.)
    401                 // TODO: or maybe it would be more clear to return a whole
    402                 // new CallStatusCode called "TURNING_ON_RADIO" here.
    403                 // That way, we'd update inCallUiState.progressIndication from
    404                 // the handleOutgoingCallError() method, rather than here.
    405                 return CallStatusCode.SUCCESS;
    406             } else {
    407                 // Otherwise, just return the (non-SUCCESS) status code
    408                 // back to our caller.
    409                 if (DBG) log("==> placeCallInternal(): non-success status: " + okToCallStatus);
    410 
    411                 // Log failed call.
    412                 // Note: Normally, many of these values we gather from the Connection object but
    413                 // since no such object is created for unconnected calls, we have to build them
    414                 // manually.
    415                 // TODO: Try to restructure code so that we can handle failure-
    416                 // condition call logging in a single place (placeCall()) that also has access to
    417                 // the number we attempted to dial (not placeCall()).
    418                 mCallLogger.logCall(null /* callerInfo */, number, 0 /* presentation */,
    419                         Calls.OUTGOING_TYPE, System.currentTimeMillis(), 0 /* duration */);
    420 
    421                 return okToCallStatus;
    422             }
    423         }
    424 
    425         // We have a valid number, so try to actually place a call:
    426         // make sure we pass along the intent's URI which is a
    427         // reference to the contact. We may have a provider gateway
    428         // phone number to use for the outgoing call.
    429         Uri contactUri = intent.getData();
    430 
    431         // If a gateway is used, extract the data here and pass that into placeCall.
    432         final RawGatewayInfo rawGatewayInfo = mCallGatewayManager.getRawGatewayInfo(intent, number);
    433 
    434         // Watch out: PhoneUtils.placeCall() returns one of the
    435         // CALL_STATUS_* constants, not a CallStatusCode enum value.
    436         int callStatus = PhoneUtils.placeCall(mApp,
    437                                               phone,
    438                                               number,
    439                                               contactUri,
    440                                               (isEmergencyNumber || isEmergencyIntent),
    441                                               rawGatewayInfo,
    442                                               mCallGatewayManager);
    443 
    444         switch (callStatus) {
    445             case PhoneUtils.CALL_STATUS_DIALED:
    446                 if (VDBG) log("placeCall: PhoneUtils.placeCall() succeeded for regular call '"
    447                              + number + "'.");
    448 
    449 
    450                 // TODO(OTASP): still need more cleanup to simplify the mApp.cdma*State objects:
    451                 // - Rather than checking inCallUiState.inCallScreenMode, the
    452                 //   code here could also check for
    453                 //   app.getCdmaOtaInCallScreenUiState() returning NORMAL.
    454                 // - But overall, app.inCallUiState.inCallScreenMode and
    455                 //   app.cdmaOtaInCallScreenUiState.state are redundant.
    456                 //   Combine them.
    457 
    458                 boolean voicemailUriSpecified = scheme != null &&
    459                     scheme.equals(PhoneAccount.SCHEME_VOICEMAIL);
    460                 // Check for an obscure ECM-related scenario: If the phone
    461                 // is currently in ECM (Emergency callback mode) and we
    462                 // dial a non-emergency number, that automatically
    463                 // *cancels* ECM.  So warn the user about it.
    464                 // (See InCallScreen.showExitingECMDialog() for more info.)
    465                 boolean exitedEcm = false;
    466                 if (PhoneUtils.isPhoneInEcm(phone) && !isEmergencyNumber) {
    467                     Log.i(TAG, "About to exit ECM because of an outgoing non-emergency call");
    468                     exitedEcm = true;  // this will cause us to return EXITED_ECM from this method
    469                 }
    470 
    471                 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
    472                     // Start the timer for 3 Way CallerInfo
    473                     if (mApp.cdmaPhoneCallState.getCurrentCallState()
    474                             == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
    475 
    476                         // This is a "CDMA 3-way call", which means that you're dialing a
    477                         // 2nd outgoing call while a previous call is already in progress.
    478                         //
    479                         // Due to the limitations of CDMA this call doesn't actually go
    480                         // through the DIALING/ALERTING states, so we can't tell for sure
    481                         // when (or if) it's actually answered.  But we want to show
    482                         // *some* indication of what's going on in the UI, so we "fake it"
    483                         // by displaying the "Dialing" state for 3 seconds.
    484 
    485                         // Set the mThreeWayCallOrigStateDialing state to true
    486                         mApp.cdmaPhoneCallState.setThreeWayCallOrigState(true);
    487 
    488                         // Schedule the "Dialing" indication to be taken down in 3 seconds:
    489                         sendEmptyMessageDelayed(THREEWAY_CALLERINFO_DISPLAY_DONE,
    490                                                 THREEWAY_CALLERINFO_DISPLAY_TIME);
    491                     }
    492                 }
    493 
    494                 // Success!
    495                 if (exitedEcm) {
    496                     return CallStatusCode.EXITED_ECM;
    497                 } else {
    498                     return CallStatusCode.SUCCESS;
    499                 }
    500 
    501             case PhoneUtils.CALL_STATUS_DIALED_MMI:
    502                 if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");
    503                 // The passed-in number was an MMI code, not a regular phone number!
    504                 // This isn't really a failure; the Dialer may have deliberately
    505                 // fired an ACTION_CALL intent to dial an MMI code, like for a
    506                 // USSD call.
    507                 //
    508                 // Presumably an MMI_INITIATE message will come in shortly
    509                 // (and we'll bring up the "MMI Started" dialog), or else
    510                 // an MMI_COMPLETE will come in (which will take us to a
    511                 // different Activity; see PhoneUtils.displayMMIComplete()).
    512                 return CallStatusCode.DIALED_MMI;
    513 
    514             case PhoneUtils.CALL_STATUS_FAILED:
    515                 Log.w(TAG, "placeCall: PhoneUtils.placeCall() FAILED for number '"
    516                       + number + "'.");
    517                 // We couldn't successfully place the call; there was some
    518                 // failure in the telephony layer.
    519 
    520                 // Log failed call.
    521                 mCallLogger.logCall(null /* callerInfo */, number, 0 /* presentation */,
    522                         Calls.OUTGOING_TYPE, System.currentTimeMillis(), 0 /* duration */);
    523 
    524                 return CallStatusCode.CALL_FAILED;
    525 
    526             default:
    527                 Log.wtf(TAG, "placeCall: unknown callStatus " + callStatus
    528                         + " from PhoneUtils.placeCall() for number '" + number + "'.");
    529                 return CallStatusCode.SUCCESS;  // Try to continue anyway...
    530         }
    531     }
    532 
    533     /**
    534      * Checks the current ServiceState to make sure it's OK
    535      * to try making an outgoing call to the specified number.
    536      *
    537      * @return CallStatusCode.SUCCESS if it's OK to try calling the specified
    538      *    number.  If not, like if the radio is powered off or we have no
    539      *    signal, return one of the other CallStatusCode codes indicating what
    540      *    the problem is.
    541      */
    542     private CallStatusCode checkIfOkToInitiateOutgoingCall(int state) {
    543         if (VDBG) log("checkIfOkToInitiateOutgoingCall: ServiceState = " + state);
    544 
    545         switch (state) {
    546             case ServiceState.STATE_IN_SERVICE:
    547                 // Normal operation.  It's OK to make outgoing calls.
    548                 return CallStatusCode.SUCCESS;
    549 
    550             case ServiceState.STATE_POWER_OFF:
    551                 // Radio is explictly powered off.
    552                 return CallStatusCode.POWER_OFF;
    553 
    554             case ServiceState.STATE_EMERGENCY_ONLY:
    555                 // The phone is registered, but locked. Only emergency
    556                 // numbers are allowed.
    557                 // Note that as of Android 2.0 at least, the telephony layer
    558                 // does not actually use ServiceState.STATE_EMERGENCY_ONLY,
    559                 // mainly since there's no guarantee that the radio/RIL can
    560                 // make this distinction.  So in practice the
    561                 // CallStatusCode.EMERGENCY_ONLY state and the string
    562                 // "incall_error_emergency_only" are totally unused.
    563                 return CallStatusCode.EMERGENCY_ONLY;
    564 
    565             case ServiceState.STATE_OUT_OF_SERVICE:
    566                 // No network connection.
    567                 return CallStatusCode.OUT_OF_SERVICE;
    568 
    569             default:
    570                 throw new IllegalStateException("Unexpected ServiceState: " + state);
    571         }
    572     }
    573 
    574 
    575 
    576     /**
    577      * Handles the various error conditions that can occur when initiating
    578      * an outgoing call.
    579      *
    580      * Most error conditions are "handled" by simply displaying an error
    581      * message to the user.
    582      *
    583      * @param status one of the CallStatusCode error codes.
    584      */
    585     private void handleOutgoingCallError(CallStatusCode status) {
    586         if (DBG) log("handleOutgoingCallError(): status = " + status);
    587         final Intent intent = new Intent(mApp, ErrorDialogActivity.class);
    588         int errorMessageId = -1;
    589         switch (status) {
    590             case SUCCESS:
    591                 // This case shouldn't happen; you're only supposed to call
    592                 // handleOutgoingCallError() if there was actually an error!
    593                 Log.wtf(TAG, "handleOutgoingCallError: SUCCESS isn't an error");
    594                 break;
    595 
    596             case CALL_FAILED:
    597                 // We couldn't successfully place the call; there was some
    598                 // failure in the telephony layer.
    599                 // TODO: Need UI spec for this failure case; for now just
    600                 // show a generic error.
    601                 errorMessageId = R.string.incall_error_call_failed;
    602                 break;
    603             case POWER_OFF:
    604                 // Radio is explictly powered off, presumably because the
    605                 // device is in airplane mode.
    606                 //
    607                 // TODO: For now this UI is ultra-simple: we simply display
    608                 // a message telling the user to turn off airplane mode.
    609                 // But it might be nicer for the dialog to offer the option
    610                 // to turn the radio on right there (and automatically retry
    611                 // the call once network registration is complete.)
    612                 errorMessageId = R.string.incall_error_power_off;
    613                 break;
    614             case EMERGENCY_ONLY:
    615                 // Only emergency numbers are allowed, but we tried to dial
    616                 // a non-emergency number.
    617                 // (This state is currently unused; see comments above.)
    618                 errorMessageId = R.string.incall_error_emergency_only;
    619                 break;
    620             case OUT_OF_SERVICE:
    621                 // No network connection.
    622                 errorMessageId = R.string.incall_error_out_of_service;
    623                 break;
    624             case NO_PHONE_NUMBER_SUPPLIED:
    625                 // The supplied Intent didn't contain a valid phone number.
    626                 // (This is rare and should only ever happen with broken
    627                 // 3rd-party apps.) For now just show a generic error.
    628                 errorMessageId = R.string.incall_error_no_phone_number_supplied;
    629                 break;
    630 
    631             case VOICEMAIL_NUMBER_MISSING:
    632                 // Bring up the "Missing Voicemail Number" dialog, which
    633                 // will ultimately take us to some other Activity (or else
    634                 // just bail out of this activity.)
    635 
    636                 // Send a request to the InCallScreen to display the
    637                 // "voicemail missing" dialog when it (the InCallScreen)
    638                 // comes to the foreground.
    639                 intent.putExtra(ErrorDialogActivity.SHOW_MISSING_VOICEMAIL_NO_DIALOG_EXTRA, true);
    640                 break;
    641 
    642             case DIALED_MMI:
    643                 // Our initial phone number was actually an MMI sequence.
    644                 // There's no real "error" here, but we do bring up the
    645                 // a Toast (as requested of the New UI paradigm).
    646                 //
    647                 // In-call MMIs do not trigger the normal MMI Initiate
    648                 // Notifications, so we should notify the user here.
    649                 // Otherwise, the code in PhoneUtils.java should handle
    650                 // user notifications in the form of Toasts or Dialogs.
    651                 //
    652                 // TODO: Rather than launching a toast from here, it would
    653                 // be cleaner to just set a pending call status code here,
    654                 // and then let the InCallScreen display the toast...
    655                 final Intent mmiIntent = new Intent(mApp, MMIDialogActivity.class);
    656                 mmiIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    657                         Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    658                 mApp.startActivity(mmiIntent);
    659                 return;
    660             default:
    661                 Log.wtf(TAG, "handleOutgoingCallError: unexpected status code " + status);
    662                 // Show a generic "call failed" error.
    663                 errorMessageId = R.string.incall_error_call_failed;
    664                 break;
    665         }
    666         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    667         if (errorMessageId != -1) {
    668             intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId);
    669         }
    670         mApp.startActivity(intent);
    671     }
    672 
    673     /**
    674      * Checks the current outgoing call to see if it's an OTASP call (the
    675      * "activation" call used to provision CDMA devices).  If so, do any
    676      * necessary OTASP-specific setup before actually placing the call.
    677      */
    678     private void checkForOtaspCall(Intent intent) {
    679         if (OtaUtils.isOtaspCallIntent(intent)) {
    680             Log.i(TAG, "checkForOtaspCall: handling OTASP intent! " + intent);
    681 
    682             // ("OTASP-specific setup" basically means creating and initializing
    683             // the OtaUtils instance.  Note that this setup needs to be here in
    684             // the CallController.placeCall() sequence, *not* in
    685             // OtaUtils.startInteractiveOtasp(), since it's also possible to
    686             // start an OTASP call by manually dialing "*228" (in which case
    687             // OtaUtils.startInteractiveOtasp() never gets run at all.)
    688             OtaUtils.setupOtaspCall(intent);
    689         } else {
    690             if (DBG) log("checkForOtaspCall: not an OTASP call.");
    691         }
    692     }
    693 
    694 
    695     //
    696     // Debugging
    697     //
    698 
    699     private static void log(String msg) {
    700         Log.d(TAG, msg);
    701     }
    702 }
    703