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