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
17package com.android.phone;
18
19import com.android.internal.telephony.CallManager;
20import com.android.internal.telephony.Phone;
21import com.android.internal.telephony.PhoneConstants;
22import com.android.internal.telephony.TelephonyCapabilities;
23import com.android.phone.CallGatewayManager.RawGatewayInfo;
24import com.android.phone.Constants.CallStatusCode;
25
26import android.content.ComponentName;
27import android.content.Intent;
28import android.net.Uri;
29import android.os.Handler;
30import android.os.Message;
31import android.os.SystemProperties;
32import android.provider.CallLog.Calls;
33import android.telecom.PhoneAccount;
34import android.telephony.PhoneNumberUtils;
35import android.telephony.ServiceState;
36import 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 */
61public 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