• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
init(PhoneGlobals app, CallLogger callLogger, CallGatewayManager callGatewayManager)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      */
CallController(PhoneGlobals app, CallLogger callLogger, CallGatewayManager callGatewayManager)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
handleMessage(Message msg)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      */
placeCall(Intent intent)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      */
placeCallInternal(Intent intent)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      */
checkIfOkToInitiateOutgoingCall(int state)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      */
handleOutgoingCallError(CallStatusCode status)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      */
checkForOtaspCall(Intent intent)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 
log(String msg)699     private static void log(String msg) {
700         Log.d(TAG, msg);
701     }
702 }
703