1/*
2 * Copyright (C) 2006 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 android.app.Activity;
20import android.app.ActivityOptions;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.app.ProgressDialog;
24import android.bluetooth.BluetoothAdapter;
25import android.bluetooth.BluetoothDevice;
26import android.bluetooth.BluetoothHeadset;
27import android.bluetooth.BluetoothProfile;
28import android.bluetooth.IBluetoothHeadsetPhone;
29import android.content.ActivityNotFoundException;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.DialogInterface;
33import android.content.DialogInterface.OnCancelListener;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.res.Configuration;
37import android.content.res.Resources;
38import android.graphics.Typeface;
39import android.media.AudioManager;
40import android.os.AsyncResult;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.os.PowerManager;
45import android.os.RemoteException;
46import android.os.SystemClock;
47import android.os.SystemProperties;
48import android.telephony.ServiceState;
49import android.text.TextUtils;
50import android.text.method.DialerKeyListener;
51import android.util.EventLog;
52import android.util.Log;
53import android.view.KeyEvent;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.ViewStub;
57import android.view.Window;
58import android.view.WindowManager;
59import android.view.accessibility.AccessibilityEvent;
60import android.widget.EditText;
61import android.widget.ImageView;
62import android.widget.LinearLayout;
63import android.widget.TextView;
64import android.widget.Toast;
65
66import com.android.internal.telephony.Call;
67import com.android.internal.telephony.CallManager;
68import com.android.internal.telephony.Connection;
69import com.android.internal.telephony.MmiCode;
70import com.android.internal.telephony.Phone;
71import com.android.internal.telephony.PhoneConstants;
72import com.android.internal.telephony.TelephonyCapabilities;
73import com.android.phone.Constants.CallStatusCode;
74import com.android.phone.InCallUiState.InCallScreenMode;
75import com.android.phone.OtaUtils.CdmaOtaScreenState;
76
77import java.util.List;
78
79
80/**
81 * Phone app "in call" screen.
82 */
83public class InCallScreen extends Activity
84        implements View.OnClickListener {
85    private static final String LOG_TAG = "InCallScreen";
86
87    private static final boolean DBG =
88            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
89    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
90
91    /**
92     * Intent extra used to specify whether the DTMF dialpad should be
93     * initially visible when bringing up the InCallScreen.  (If this
94     * extra is present, the dialpad will be initially shown if the extra
95     * has the boolean value true, and initially hidden otherwise.)
96     */
97    // TODO: Should be EXTRA_SHOW_DIALPAD for consistency.
98    static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
99
100    /**
101     * Intent extra to specify the package name of the gateway
102     * provider.  Used to get the name displayed in the in-call screen
103     * during the call setup. The value is a string.
104     */
105    // TODO: This extra is currently set by the gateway application as
106    // a temporary measure. Ultimately, the framework will securely
107    // set it.
108    /* package */ static final String EXTRA_GATEWAY_PROVIDER_PACKAGE =
109            "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
110
111    /**
112     * Intent extra to specify the URI of the provider to place the
113     * call. The value is a string. It holds the gateway address
114     * (phone gateway URL should start with the 'tel:' scheme) that
115     * will actually be contacted to call the number passed in the
116     * intent URL or in the EXTRA_PHONE_NUMBER extra.
117     */
118    // TODO: Should the value be a Uri (Parcelable)? Need to make sure
119    // MMI code '#' don't get confused as URI fragments.
120    /* package */ static final String EXTRA_GATEWAY_URI =
121            "com.android.phone.extra.GATEWAY_URI";
122
123    // Amount of time (in msec) that we display the "Call ended" state.
124    // The "short" value is for calls ended by the local user, and the
125    // "long" value is for calls ended by the remote caller.
126    private static final int CALL_ENDED_SHORT_DELAY =  200;  // msec
127    private static final int CALL_ENDED_LONG_DELAY = 2000;  // msec
128    private static final int CALL_ENDED_EXTRA_LONG_DELAY = 5000;  // msec
129
130    // Amount of time that we display the PAUSE alert Dialog showing the
131    // post dial string yet to be send out to the n/w
132    private static final int PAUSE_PROMPT_DIALOG_TIMEOUT = 2000;  //msec
133
134    // Amount of time that we display the provider info if applicable.
135    private static final int PROVIDER_INFO_TIMEOUT = 5000;  // msec
136
137    // These are values for the settings of the auto retry mode:
138    // 0 = disabled
139    // 1 = enabled
140    // TODO (Moto):These constants don't really belong here,
141    // they should be moved to Settings where the value is being looked up in the first place
142    static final int AUTO_RETRY_OFF = 0;
143    static final int AUTO_RETRY_ON = 1;
144
145    // Message codes; see mHandler below.
146    // Note message codes < 100 are reserved for the PhoneApp.
147    private static final int PHONE_STATE_CHANGED = 101;
148    private static final int PHONE_DISCONNECT = 102;
149    private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
150    private static final int POST_ON_DIAL_CHARS = 104;
151    private static final int WILD_PROMPT_CHAR_ENTERED = 105;
152    private static final int ADD_VOICEMAIL_NUMBER = 106;
153    private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
154    private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
155    private static final int SUPP_SERVICE_FAILED = 110;
156    private static final int REQUEST_UPDATE_BLUETOOTH_INDICATION = 114;
157    private static final int PHONE_CDMA_CALL_WAITING = 115;
158    private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
159    private static final int REQUEST_CLOSE_OTA_FAILURE_NOTICE = 119;
160    private static final int EVENT_PAUSE_DIALOG_COMPLETE = 120;
161    private static final int EVENT_HIDE_PROVIDER_INFO = 121;  // Time to remove the info.
162    private static final int REQUEST_UPDATE_SCREEN = 122;
163    private static final int PHONE_INCOMING_RING = 123;
164    private static final int PHONE_NEW_RINGING_CONNECTION = 124;
165
166    // When InCallScreenMode is UNDEFINED set the default action
167    // to ACTION_UNDEFINED so if we are resumed the activity will
168    // know its undefined. In particular checkIsOtaCall will return
169    // false.
170    public static final String ACTION_UNDEFINED = "com.android.phone.InCallScreen.UNDEFINED";
171
172    /** Status codes returned from syncWithPhoneState(). */
173    private enum SyncWithPhoneStateStatus {
174        /**
175         * Successfully updated our internal state based on the telephony state.
176         */
177        SUCCESS,
178
179        /**
180         * There was no phone state to sync with (i.e. the phone was
181         * completely idle).  In most cases this means that the
182         * in-call UI shouldn't be visible in the first place, unless
183         * we need to remain in the foreground while displaying an
184         * error message.
185         */
186        PHONE_NOT_IN_USE
187    }
188
189    private boolean mRegisteredForPhoneStates;
190
191    private PhoneGlobals mApp;
192    private CallManager mCM;
193
194    // TODO: need to clean up all remaining uses of mPhone.
195    // (There may be more than one Phone instance on the device, so it's wrong
196    // to just keep a single mPhone field.  Instead, any time we need a Phone
197    // reference we should get it dynamically from the CallManager, probably
198    // based on the current foreground Call.)
199    private Phone mPhone;
200
201    private BluetoothHeadset mBluetoothHeadset;
202    private BluetoothAdapter mBluetoothAdapter;
203    private boolean mBluetoothConnectionPending;
204    private long mBluetoothConnectionRequestTime;
205
206    /** Main in-call UI elements. */
207    private CallCard mCallCard;
208
209    // UI controls:
210    private InCallControlState mInCallControlState;
211    private InCallTouchUi mInCallTouchUi;
212    private RespondViaSmsManager mRespondViaSmsManager;  // see internalRespondViaSms()
213    private ManageConferenceUtils mManageConferenceUtils;
214
215    // DTMF Dialer controller and its view:
216    private DTMFTwelveKeyDialer mDialer;
217
218    private EditText mWildPromptText;
219
220    // Various dialogs we bring up (see dismissAllDialogs()).
221    // TODO: convert these all to use the "managed dialogs" framework.
222    //
223    // The MMI started dialog can actually be one of 2 items:
224    //   1. An alert dialog if the MMI code is a normal MMI
225    //   2. A progress dialog if the user requested a USSD
226    private Dialog mMmiStartedDialog;
227    private AlertDialog mMissingVoicemailDialog;
228    private AlertDialog mGenericErrorDialog;
229    private AlertDialog mSuppServiceFailureDialog;
230    private AlertDialog mWaitPromptDialog;
231    private AlertDialog mWildPromptDialog;
232    private AlertDialog mCallLostDialog;
233    private AlertDialog mPausePromptDialog;
234    private AlertDialog mExitingECMDialog;
235    // NOTE: if you add a new dialog here, be sure to add it to dismissAllDialogs() also.
236
237    // ProgressDialog created by showProgressIndication()
238    private ProgressDialog mProgressDialog;
239
240    // TODO: If the Activity class ever provides an easy way to get the
241    // current "activity lifecycle" state, we can remove these flags.
242    private boolean mIsDestroyed = false;
243    private boolean mIsForegroundActivity = false;
244    private boolean mIsForegroundActivityForProximity = false;
245    private PowerManager mPowerManager;
246
247    // For use with Pause/Wait dialogs
248    private String mPostDialStrAfterPause;
249    private boolean mPauseInProgress = false;
250
251    // Info about the most-recently-disconnected Connection, which is used
252    // to determine what should happen when exiting the InCallScreen after a
253    // call.  (This info is set by onDisconnect(), and used by
254    // delayedCleanupAfterDisconnect().)
255    private Connection.DisconnectCause mLastDisconnectCause;
256
257    /** In-call audio routing options; see switchInCallAudio(). */
258    public enum InCallAudioMode {
259        SPEAKER,    // Speakerphone
260        BLUETOOTH,  // Bluetooth headset (if available)
261        EARPIECE,   // Handset earpiece (or wired headset, if connected)
262    }
263
264
265    private Handler mHandler = new Handler() {
266        @Override
267        public void handleMessage(Message msg) {
268            if (mIsDestroyed) {
269                if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
270                return;
271            }
272            if (!mIsForegroundActivity) {
273                if (DBG) log("Handler: handling message " + msg + " while not in foreground");
274                // Continue anyway; some of the messages below *want* to
275                // be handled even if we're not the foreground activity
276                // (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
277                // should at least be safe to handle if we're not in the
278                // foreground...
279            }
280
281            switch (msg.what) {
282                case SUPP_SERVICE_FAILED:
283                    onSuppServiceFailed((AsyncResult) msg.obj);
284                    break;
285
286                case PHONE_STATE_CHANGED:
287                    onPhoneStateChanged((AsyncResult) msg.obj);
288                    break;
289
290                case PHONE_DISCONNECT:
291                    onDisconnect((AsyncResult) msg.obj);
292                    break;
293
294                case EVENT_HEADSET_PLUG_STATE_CHANGED:
295                    // Update the in-call UI, since some UI elements (such
296                    // as the "Speaker" button) may change state depending on
297                    // whether a headset is plugged in.
298                    // TODO: A full updateScreen() is overkill here, since
299                    // the value of PhoneApp.isHeadsetPlugged() only affects a
300                    // single onscreen UI element.  (But even a full updateScreen()
301                    // is still pretty cheap, so let's keep this simple
302                    // for now.)
303                    updateScreen();
304
305                    // Also, force the "audio mode" popup to refresh itself if
306                    // it's visible, since one of its items is either "Wired
307                    // headset" or "Handset earpiece" depending on whether the
308                    // headset is plugged in or not.
309                    mInCallTouchUi.refreshAudioModePopup();  // safe even if the popup's not active
310
311                    break;
312
313                // TODO: sort out MMI code (probably we should remove this method entirely).
314                // See also MMI handling code in onResume()
315                // case PhoneApp.MMI_INITIATE:
316                // onMMIInitiate((AsyncResult) msg.obj);
317                //    break;
318
319                case PhoneGlobals.MMI_CANCEL:
320                    onMMICancel();
321                    break;
322
323                // handle the mmi complete message.
324                // since the message display class has been replaced with
325                // a system dialog in PhoneUtils.displayMMIComplete(), we
326                // should finish the activity here to close the window.
327                case PhoneGlobals.MMI_COMPLETE:
328                    onMMIComplete((MmiCode) ((AsyncResult) msg.obj).result);
329                    break;
330
331                case POST_ON_DIAL_CHARS:
332                    handlePostOnDialChars((AsyncResult) msg.obj, (char) msg.arg1);
333                    break;
334
335                case ADD_VOICEMAIL_NUMBER:
336                    addVoiceMailNumberPanel();
337                    break;
338
339                case DONT_ADD_VOICEMAIL_NUMBER:
340                    dontAddVoiceMailNumber();
341                    break;
342
343                case DELAYED_CLEANUP_AFTER_DISCONNECT:
344                    delayedCleanupAfterDisconnect();
345                    break;
346
347                case REQUEST_UPDATE_BLUETOOTH_INDICATION:
348                    if (VDBG) log("REQUEST_UPDATE_BLUETOOTH_INDICATION...");
349                    // The bluetooth headset state changed, so some UI
350                    // elements may need to update.  (There's no need to
351                    // look up the current state here, since any UI
352                    // elements that care about the bluetooth state get it
353                    // directly from PhoneApp.showBluetoothIndication().)
354                    updateScreen();
355                    break;
356
357                case PHONE_CDMA_CALL_WAITING:
358                    if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
359                    Connection cn = mCM.getFirstActiveRingingCall().getLatestConnection();
360
361                    // Only proceed if we get a valid connection object
362                    if (cn != null) {
363                        // Finally update screen with Call waiting info and request
364                        // screen to wake up
365                        updateScreen();
366                        mApp.updateWakeState();
367                    }
368                    break;
369
370                case REQUEST_CLOSE_SPC_ERROR_NOTICE:
371                    if (mApp.otaUtils != null) {
372                        mApp.otaUtils.onOtaCloseSpcNotice();
373                    }
374                    break;
375
376                case REQUEST_CLOSE_OTA_FAILURE_NOTICE:
377                    if (mApp.otaUtils != null) {
378                        mApp.otaUtils.onOtaCloseFailureNotice();
379                    }
380                    break;
381
382                case EVENT_PAUSE_DIALOG_COMPLETE:
383                    if (mPausePromptDialog != null) {
384                        if (DBG) log("- DISMISSING mPausePromptDialog.");
385                        mPausePromptDialog.dismiss();  // safe even if already dismissed
386                        mPausePromptDialog = null;
387                    }
388                    break;
389
390                case EVENT_HIDE_PROVIDER_INFO:
391                    mApp.inCallUiState.providerInfoVisible = false;
392                    if (mCallCard != null) {
393                        mCallCard.updateState(mCM);
394                    }
395                    break;
396                case REQUEST_UPDATE_SCREEN:
397                    updateScreen();
398                    break;
399
400                case PHONE_INCOMING_RING:
401                    onIncomingRing();
402                    break;
403
404                case PHONE_NEW_RINGING_CONNECTION:
405                    onNewRingingConnection();
406                    break;
407
408                default:
409                    Log.wtf(LOG_TAG, "mHandler: unexpected message: " + msg);
410                    break;
411            }
412        }
413    };
414
415    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
416            @Override
417            public void onReceive(Context context, Intent intent) {
418                String action = intent.getAction();
419                if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
420                    // Listen for ACTION_HEADSET_PLUG broadcasts so that we
421                    // can update the onscreen UI when the headset state changes.
422                    // if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
423                    // if (DBG) log("==> intent: " + intent);
424                    // if (DBG) log("    state: " + intent.getIntExtra("state", 0));
425                    // if (DBG) log("    name: " + intent.getStringExtra("name"));
426                    // send the event and add the state as an argument.
427                    Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
428                            intent.getIntExtra("state", 0), 0);
429                    mHandler.sendMessage(message);
430                }
431            }
432        };
433
434
435    @Override
436    protected void onCreate(Bundle icicle) {
437        Log.i(LOG_TAG, "onCreate()...  this = " + this);
438        Profiler.callScreenOnCreate();
439        super.onCreate(icicle);
440
441        // Make sure this is a voice-capable device.
442        if (!PhoneGlobals.sVoiceCapable) {
443            // There should be no way to ever reach the InCallScreen on a
444            // non-voice-capable device, since this activity is not exported by
445            // our manifest, and we explicitly disable any other external APIs
446            // like the CALL intent and ITelephony.showCallScreen().
447            // So the fact that we got here indicates a phone app bug.
448            Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");
449            finish();
450            return;
451        }
452
453        mApp = PhoneGlobals.getInstance();
454        mApp.setInCallScreenInstance(this);
455
456        // set this flag so this activity will stay in front of the keyguard
457        int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
458                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
459        if (mApp.getPhoneState() == PhoneConstants.State.OFFHOOK) {
460            // While we are in call, the in-call screen should dismiss the keyguard.
461            // This allows the user to press Home to go directly home without going through
462            // an insecure lock screen.
463            // But we do not want to do this if there is no active call so we do not
464            // bypass the keyguard if the call is not answered or declined.
465            flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
466        }
467
468        WindowManager.LayoutParams lp = getWindow().getAttributes();
469        lp.flags |= flags;
470        if (!mApp.proximitySensorModeEnabled()) {
471            // If we don't have a proximity sensor, then the in-call screen explicitly
472            // controls user activity.  This is to prevent spurious touches from waking
473            // the display.
474            lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
475        }
476        getWindow().setAttributes(lp);
477
478        setPhone(mApp.phone);  // Sets mPhone
479
480        mCM =  mApp.mCM;
481        log("- onCreate: phone state = " + mCM.getState());
482
483        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
484        if (mBluetoothAdapter != null) {
485            mBluetoothAdapter.getProfileProxy(getApplicationContext(), mBluetoothProfileServiceListener,
486                                    BluetoothProfile.HEADSET);
487        }
488
489        requestWindowFeature(Window.FEATURE_NO_TITLE);
490
491        // Inflate everything in incall_screen.xml and add it to the screen.
492        setContentView(R.layout.incall_screen);
493
494        // If in landscape, then one of the ViewStubs (instead of <include>) is used for the
495        // incall_touch_ui, because CDMA and GSM button layouts are noticeably different.
496        final ViewStub touchUiStub = (ViewStub) findViewById(
497                mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
498                ? R.id.inCallTouchUiCdmaStub : R.id.inCallTouchUiStub);
499        if (touchUiStub != null) touchUiStub.inflate();
500
501        initInCallScreen();
502
503        registerForPhoneStates();
504
505        // No need to change wake state here; that happens in onResume() when we
506        // are actually displayed.
507
508        // Handle the Intent we were launched with, but only if this is the
509        // the very first time we're being launched (ie. NOT if we're being
510        // re-initialized after previously being shut down.)
511        // Once we're up and running, any future Intents we need
512        // to handle will come in via the onNewIntent() method.
513        if (icicle == null) {
514            if (DBG) log("onCreate(): this is our very first launch, checking intent...");
515            internalResolveIntent(getIntent());
516        }
517
518        Profiler.callScreenCreated();
519        if (DBG) log("onCreate(): exit");
520    }
521
522     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
523             new BluetoothProfile.ServiceListener() {
524         @Override
525         public void onServiceConnected(int profile, BluetoothProfile proxy) {
526             mBluetoothHeadset = (BluetoothHeadset) proxy;
527             if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
528         }
529
530         @Override
531         public void onServiceDisconnected(int profile) {
532             mBluetoothHeadset = null;
533         }
534    };
535
536    /**
537     * Sets the Phone object used internally by the InCallScreen.
538     *
539     * In normal operation this is called from onCreate(), and the
540     * passed-in Phone object comes from the PhoneApp.
541     * For testing, test classes can use this method to
542     * inject a test Phone instance.
543     */
544    /* package */ void setPhone(Phone phone) {
545        mPhone = phone;
546    }
547
548    @Override
549    protected void onResume() {
550        if (DBG) log("onResume()...");
551        super.onResume();
552
553        mIsForegroundActivity = true;
554        mIsForegroundActivityForProximity = true;
555
556        // The flag shouldn't be turned on when there are actual phone calls.
557        if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()) {
558            mApp.inCallUiState.showAlreadyDisconnectedState = false;
559        }
560
561        final InCallUiState inCallUiState = mApp.inCallUiState;
562        if (VDBG) inCallUiState.dumpState();
563
564        updateExpandedViewState();
565
566        // ...and update the in-call notification too, since the status bar
567        // icon needs to be hidden while we're the foreground activity:
568        mApp.notificationMgr.updateInCallNotification();
569
570        // Listen for broadcast intents that might affect the onscreen UI.
571        registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
572
573        // Keep a "dialer session" active when we're in the foreground.
574        // (This is needed to play DTMF tones.)
575        mDialer.startDialerSession();
576
577        // Restore various other state from the InCallUiState object:
578
579        // Update the onscreen dialpad state to match the InCallUiState.
580        if (inCallUiState.showDialpad) {
581            openDialpadInternal(false);  // no "opening" animation
582        } else {
583            closeDialpadInternal(false);  // no "closing" animation
584        }
585
586        // Reset the dialpad context
587        // TODO: Dialpad digits should be set here as well (once they are saved)
588        mDialer.setDialpadContext(inCallUiState.dialpadContextText);
589
590        // If there's a "Respond via SMS" popup still around since the
591        // last time we were the foreground activity, make sure it's not
592        // still active!
593        // (The popup should *never* be visible initially when we first
594        // come to the foreground; it only ever comes up in response to
595        // the user selecting the "SMS" option from the incoming call
596        // widget.)
597        mRespondViaSmsManager.dismissPopup();  // safe even if already dismissed
598
599        // Display an error / diagnostic indication if necessary.
600        //
601        // When the InCallScreen comes to the foreground, we normally we
602        // display the in-call UI in whatever state is appropriate based on
603        // the state of the telephony framework (e.g. an outgoing call in
604        // DIALING state, an incoming call, etc.)
605        //
606        // But if the InCallUiState has a "pending call status code" set,
607        // that means we need to display some kind of status or error
608        // indication to the user instead of the regular in-call UI.  (The
609        // most common example of this is when there's some kind of
610        // failure while initiating an outgoing call; see
611        // CallController.placeCall().)
612        //
613        boolean handledStartupError = false;
614        if (inCallUiState.hasPendingCallStatusCode()) {
615            if (DBG) log("- onResume: need to show status indication!");
616            showStatusIndication(inCallUiState.getPendingCallStatusCode());
617
618            // Set handledStartupError to ensure that we won't bail out below.
619            // (We need to stay here in the InCallScreen so that the user
620            // is able to see the error dialog!)
621            handledStartupError = true;
622        }
623
624        // Set the volume control handler while we are in the foreground.
625        final boolean bluetoothConnected = isBluetoothAudioConnected();
626
627        if (bluetoothConnected) {
628            setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
629        } else {
630            setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
631        }
632
633        takeKeyEvents(true);
634
635        // If an OTASP call is in progress, use the special OTASP-specific UI.
636        boolean inOtaCall = false;
637        if (TelephonyCapabilities.supportsOtasp(mPhone)) {
638            inOtaCall = checkOtaspStateOnResume();
639        }
640        if (!inOtaCall) {
641            // Always start off in NORMAL mode
642            setInCallScreenMode(InCallScreenMode.NORMAL);
643        }
644
645        // Before checking the state of the CallManager, clean up any
646        // connections in the DISCONNECTED state.
647        // (The DISCONNECTED state is used only to drive the "call ended"
648        // UI; it's totally useless when *entering* the InCallScreen.)
649        mCM.clearDisconnected();
650
651        // Update the onscreen UI to reflect the current telephony state.
652        SyncWithPhoneStateStatus status = syncWithPhoneState();
653
654        // Note there's no need to call updateScreen() here;
655        // syncWithPhoneState() already did that if necessary.
656
657        if (status != SyncWithPhoneStateStatus.SUCCESS) {
658            if (DBG) log("- onResume: syncWithPhoneState failed! status = " + status);
659            // Couldn't update the UI, presumably because the phone is totally
660            // idle.
661
662            // Even though the phone is idle, though, we do still need to
663            // stay here on the InCallScreen if we're displaying an
664            // error dialog (see "showStatusIndication()" above).
665
666            if (handledStartupError) {
667                // Stay here for now.  We'll eventually leave the
668                // InCallScreen when the user presses the dialog's OK
669                // button (see bailOutAfterErrorDialog()), or when the
670                // progress indicator goes away.
671                Log.i(LOG_TAG, "  ==> syncWithPhoneState failed, but staying here anyway.");
672            } else {
673                // The phone is idle, and we did NOT handle a
674                // startup error during this pass thru onResume.
675                //
676                // This basically means that we're being resumed because of
677                // some action *other* than a new intent.  (For example,
678                // the user pressing POWER to wake up the device, causing
679                // the InCallScreen to come back to the foreground.)
680                //
681                // In this scenario we do NOT want to stay here on the
682                // InCallScreen: we're not showing any useful info to the
683                // user (like a dialog), and the in-call UI itself is
684                // useless if there's no active call.  So bail out.
685
686                Log.i(LOG_TAG, "  ==> syncWithPhoneState failed; bailing out!");
687                dismissAllDialogs();
688
689                // Force the InCallScreen to truly finish(), rather than just
690                // moving it to the back of the activity stack (which is what
691                // our finish() method usually does.)
692                // This is necessary to avoid an obscure scenario where the
693                // InCallScreen can get stuck in an inconsistent state, somehow
694                // causing a *subsequent* outgoing call to fail (bug 4172599).
695                endInCallScreenSession(true /* force a real finish() call */);
696                return;
697            }
698        } else if (TelephonyCapabilities.supportsOtasp(mPhone)) {
699            if (inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL ||
700                    inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED) {
701                if (mCallCard != null) mCallCard.setVisibility(View.GONE);
702                updateScreen();
703                return;
704            }
705        }
706
707        // InCallScreen is now active.
708        EventLog.writeEvent(EventLogTags.PHONE_UI_ENTER);
709
710        // Update the poke lock and wake lock when we move to the foreground.
711        // This will be no-op when prox sensor is effective.
712        mApp.updateWakeState();
713
714        // Restore the mute state if the last mute state change was NOT
715        // done by the user.
716        if (mApp.getRestoreMuteOnInCallResume()) {
717            // Mute state is based on the foreground call
718            PhoneUtils.restoreMuteState();
719            mApp.setRestoreMuteOnInCallResume(false);
720        }
721
722        Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
723
724        // If there's a pending MMI code, we'll show a dialog here.
725        //
726        // Note: previously we had shown the dialog when MMI_INITIATE event's coming
727        // from telephony layer, while right now we don't because the event comes
728        // too early (before in-call screen is prepared).
729        // Now we instead check pending MMI code and show the dialog here.
730        //
731        // This *may* cause some problem, e.g. when the user really quickly starts
732        // MMI sequence and calls an actual phone number before the MMI request
733        // being completed, which is rather rare.
734        //
735        // TODO: streamline this logic and have a UX in a better manner.
736        // Right now syncWithPhoneState() above will return SUCCESS based on
737        // mPhone.getPendingMmiCodes().isEmpty(), while we check it again here.
738        // Also we show pre-populated in-call UI under the dialog, which looks
739        // not great. (issue 5210375, 5545506)
740        // After cleaning them, remove commented-out MMI handling code elsewhere.
741        if (!mPhone.getPendingMmiCodes().isEmpty()) {
742            if (mMmiStartedDialog == null) {
743                MmiCode mmiCode = mPhone.getPendingMmiCodes().get(0);
744                Message message = Message.obtain(mHandler, PhoneGlobals.MMI_CANCEL);
745                mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
746                        message, mMmiStartedDialog);
747                // mInCallScreen needs to receive MMI_COMPLETE/MMI_CANCEL event from telephony,
748                // which will dismiss the entire screen.
749            }
750        }
751
752        // This means the screen is shown even though there's no connection, which only happens
753        // when the phone call has hung up while the screen is turned off at that moment.
754        // We want to show "disconnected" state with photos with appropriate elapsed time for
755        // the finished phone call.
756        if (mApp.inCallUiState.showAlreadyDisconnectedState) {
757            // if (DBG) {
758            log("onResume(): detected \"show already disconnected state\" situation."
759                    + " set up DELAYED_CLEANUP_AFTER_DISCONNECT message with "
760                    + CALL_ENDED_LONG_DELAY + " msec delay.");
761            //}
762            mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
763            mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
764                    CALL_ENDED_LONG_DELAY);
765        }
766
767        if (VDBG) log("onResume() done.");
768    }
769
770    // onPause is guaranteed to be called when the InCallScreen goes
771    // in the background.
772    @Override
773    protected void onPause() {
774        if (DBG) log("onPause()...");
775        super.onPause();
776
777        if (mPowerManager.isScreenOn()) {
778            // Set to false when the screen went background *not* by screen turned off. Probably
779            // the user bailed out of the in-call screen (by pressing BACK, HOME, etc.)
780            mIsForegroundActivityForProximity = false;
781        }
782        mIsForegroundActivity = false;
783
784        // Force a clear of the provider info frame. Since the
785        // frame is removed using a timed message, it is
786        // possible we missed it if the prev call was interrupted.
787        mApp.inCallUiState.providerInfoVisible = false;
788
789        // "show-already-disconnected-state" should be effective just during the first wake-up.
790        // We should never allow it to stay true after that.
791        mApp.inCallUiState.showAlreadyDisconnectedState = false;
792
793        // A safety measure to disable proximity sensor in case call failed
794        // and the telephony state did not change.
795        mApp.setBeginningCall(false);
796
797        // Make sure the "Manage conference" chronometer is stopped when
798        // we move away from the foreground.
799        mManageConferenceUtils.stopConferenceTime();
800
801        // as a catch-all, make sure that any dtmf tones are stopped
802        // when the UI is no longer in the foreground.
803        mDialer.onDialerKeyUp(null);
804
805        // Release any "dialer session" resources, now that we're no
806        // longer in the foreground.
807        mDialer.stopDialerSession();
808
809        // If the device is put to sleep as the phone call is ending,
810        // we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
811        // event gets handled AFTER the device goes to sleep and wakes
812        // up again.
813
814        // This is because it is possible for a sleep command
815        // (executed with the End Call key) to come during the 2
816        // seconds that the "Call Ended" screen is up.  Sleep then
817        // pauses the device (including the cleanup event) and
818        // resumes the event when it wakes up.
819
820        // To fix this, we introduce a bit of code that pushes the UI
821        // to the background if we pause and see a request to
822        // DELAYED_CLEANUP_AFTER_DISCONNECT.
823
824        // Note: We can try to finish directly, by:
825        //  1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
826        //  2. Calling delayedCleanupAfterDisconnect directly
827
828        // However, doing so can cause problems between the phone
829        // app and the keyguard - the keyguard is trying to sleep at
830        // the same time that the phone state is changing.  This can
831        // end up causing the sleep request to be ignored.
832        if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)
833                && mCM.getState() != PhoneConstants.State.RINGING) {
834            if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
835            endInCallScreenSession();
836        }
837
838        EventLog.writeEvent(EventLogTags.PHONE_UI_EXIT);
839
840        // Dismiss any dialogs we may have brought up, just to be 100%
841        // sure they won't still be around when we get back here.
842        dismissAllDialogs();
843
844        updateExpandedViewState();
845
846        // ...and the in-call notification too:
847        mApp.notificationMgr.updateInCallNotification();
848        // ...and *always* reset the system bar back to its normal state
849        // when leaving the in-call UI.
850        // (While we're the foreground activity, we disable navigation in
851        // some call states; see InCallTouchUi.updateState().)
852        mApp.notificationMgr.statusBarHelper.enableSystemBarNavigation(true);
853
854        // Unregister for broadcast intents.  (These affect the visible UI
855        // of the InCallScreen, so we only care about them while we're in the
856        // foreground.)
857        unregisterReceiver(mReceiver);
858
859        // Make sure we revert the poke lock and wake lock when we move to
860        // the background.
861        mApp.updateWakeState();
862
863        // clear the dismiss keyguard flag so we are back to the default state
864        // when we next resume
865        updateKeyguardPolicy(false);
866
867        // See also PhoneApp#updatePhoneState(), which takes care of all the other release() calls.
868        if (mApp.getUpdateLock().isHeld() && mApp.getPhoneState() == PhoneConstants.State.IDLE) {
869            if (DBG) {
870                log("Release UpdateLock on onPause() because there's no active phone call.");
871            }
872            mApp.getUpdateLock().release();
873        }
874    }
875
876    @Override
877    protected void onStop() {
878        if (DBG) log("onStop()...");
879        super.onStop();
880
881        stopTimer();
882
883        PhoneConstants.State state = mCM.getState();
884        if (DBG) log("onStop: state = " + state);
885
886        if (state == PhoneConstants.State.IDLE) {
887            if (mRespondViaSmsManager.isShowingPopup()) {
888                // This means that the user has been opening the "Respond via SMS" dialog even
889                // after the incoming call hanging up, and the screen finally went background.
890                // In that case we just close the dialog and exit the whole in-call screen.
891                mRespondViaSmsManager.dismissPopup();
892            }
893
894            // when OTA Activation, OTA Success/Failure dialog or OTA SPC
895            // failure dialog is running, do not destroy inCallScreen. Because call
896            // is already ended and dialog will not get redrawn on slider event.
897            if ((mApp.cdmaOtaProvisionData != null) && (mApp.cdmaOtaScreenState != null)
898                    && ((mApp.cdmaOtaScreenState.otaScreenState !=
899                            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION)
900                        && (mApp.cdmaOtaScreenState.otaScreenState !=
901                            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG)
902                        && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
903                // we don't want the call screen to remain in the activity history
904                // if there are not active or ringing calls.
905                if (DBG) log("- onStop: calling finish() to clear activity history...");
906                moveTaskToBack(true);
907                if (mApp.otaUtils != null) {
908                    mApp.otaUtils.cleanOtaScreen(true);
909                }
910            }
911        }
912    }
913
914    @Override
915    protected void onDestroy() {
916        Log.i(LOG_TAG, "onDestroy()...  this = " + this);
917        super.onDestroy();
918
919        // Set the magic flag that tells us NOT to handle any handler
920        // messages that come in asynchronously after we get destroyed.
921        mIsDestroyed = true;
922
923        mApp.setInCallScreenInstance(null);
924
925        // Clear out the InCallScreen references in various helper objects
926        // (to let them know we've been destroyed).
927        if (mCallCard != null) {
928            mCallCard.setInCallScreenInstance(null);
929        }
930        if (mInCallTouchUi != null) {
931            mInCallTouchUi.setInCallScreenInstance(null);
932        }
933        mRespondViaSmsManager.setInCallScreenInstance(null);
934
935        mDialer.clearInCallScreenReference();
936        mDialer = null;
937
938        unregisterForPhoneStates();
939        // No need to change wake state here; that happens in onPause() when we
940        // are moving out of the foreground.
941
942        if (mBluetoothHeadset != null) {
943            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
944            mBluetoothHeadset = null;
945        }
946
947        // Dismiss all dialogs, to be absolutely sure we won't leak any of
948        // them while changing orientation.
949        dismissAllDialogs();
950
951        // If there's an OtaUtils instance around, clear out its
952        // references to our internal widgets.
953        if (mApp.otaUtils != null) {
954            mApp.otaUtils.clearUiWidgets();
955        }
956    }
957
958    /**
959     * Dismisses the in-call screen.
960     *
961     * We never *really* finish() the InCallScreen, since we don't want to
962     * get destroyed and then have to be re-created from scratch for the
963     * next call.  Instead, we just move ourselves to the back of the
964     * activity stack.
965     *
966     * This also means that we'll no longer be reachable via the BACK
967     * button (since moveTaskToBack() puts us behind the Home app, but the
968     * home app doesn't allow the BACK key to move you any farther down in
969     * the history stack.)
970     *
971     * (Since the Phone app itself is never killed, this basically means
972     * that we'll keep a single InCallScreen instance around for the
973     * entire uptime of the device.  This noticeably improves the UI
974     * responsiveness for incoming calls.)
975     */
976    @Override
977    public void finish() {
978        if (DBG) log("finish()...");
979        moveTaskToBack(true);
980    }
981
982    /**
983     * End the current in call screen session.
984     *
985     * This must be called when an InCallScreen session has
986     * complete so that the next invocation via an onResume will
987     * not be in an old state.
988     */
989    public void endInCallScreenSession() {
990        if (DBG) log("endInCallScreenSession()... phone state = " + mCM.getState());
991        endInCallScreenSession(false);
992    }
993
994    /**
995     * Internal version of endInCallScreenSession().
996     *
997     * @param forceFinish If true, force the InCallScreen to
998     *        truly finish() rather than just calling moveTaskToBack().
999     *        @see finish()
1000     */
1001    private void endInCallScreenSession(boolean forceFinish) {
1002        if (DBG) {
1003            log("endInCallScreenSession(" + forceFinish + ")...  phone state = " + mCM.getState());
1004        }
1005        if (forceFinish) {
1006            Log.i(LOG_TAG, "endInCallScreenSession(): FORCING a call to super.finish()!");
1007            super.finish();  // Call super.finish() rather than our own finish() method,
1008                             // which actually just calls moveTaskToBack().
1009        } else {
1010            moveTaskToBack(true);
1011        }
1012        setInCallScreenMode(InCallScreenMode.UNDEFINED);
1013    }
1014
1015    /**
1016     * True when this Activity is in foreground (between onResume() and onPause()).
1017     */
1018    /* package */ boolean isForegroundActivity() {
1019        return mIsForegroundActivity;
1020    }
1021
1022    /**
1023     * Returns true when the Activity is in foreground (between onResume() and onPause()),
1024     * or, is in background due to user's bailing out of the screen, not by screen turning off.
1025     *
1026     * @see #isForegroundActivity()
1027     */
1028    /* package */ boolean isForegroundActivityForProximity() {
1029        return mIsForegroundActivityForProximity;
1030    }
1031
1032    /* package */ void updateKeyguardPolicy(boolean dismissKeyguard) {
1033        if (dismissKeyguard) {
1034            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
1035        } else {
1036            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
1037        }
1038    }
1039
1040    private void registerForPhoneStates() {
1041        if (!mRegisteredForPhoneStates) {
1042            mCM.registerForPreciseCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
1043            mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
1044            // TODO: sort out MMI code (probably we should remove this method entirely).
1045            // See also MMI handling code in onResume()
1046            // mCM.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
1047
1048            // register for the MMI complete message.  Upon completion,
1049            // PhoneUtils will bring up a system dialog instead of the
1050            // message display class in PhoneUtils.displayMMIComplete().
1051            // We'll listen for that message too, so that we can finish
1052            // the activity at the same time.
1053            mCM.registerForMmiComplete(mHandler, PhoneGlobals.MMI_COMPLETE, null);
1054            mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
1055            mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
1056            mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
1057            mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);
1058            mCM.registerForNewRingingConnection(mHandler, PHONE_NEW_RINGING_CONNECTION, null);
1059            mRegisteredForPhoneStates = true;
1060        }
1061    }
1062
1063    private void unregisterForPhoneStates() {
1064        mCM.unregisterForPreciseCallStateChanged(mHandler);
1065        mCM.unregisterForDisconnect(mHandler);
1066        mCM.unregisterForMmiInitiate(mHandler);
1067        mCM.unregisterForMmiComplete(mHandler);
1068        mCM.unregisterForCallWaiting(mHandler);
1069        mCM.unregisterForPostDialCharacter(mHandler);
1070        mCM.unregisterForSuppServiceFailed(mHandler);
1071        mCM.unregisterForIncomingRing(mHandler);
1072        mCM.unregisterForNewRingingConnection(mHandler);
1073        mRegisteredForPhoneStates = false;
1074    }
1075
1076    /* package */ void updateAfterRadioTechnologyChange() {
1077        if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
1078
1079        // Reset the call screen since the calls cannot be transferred
1080        // across radio technologies.
1081        resetInCallScreenMode();
1082
1083        // Unregister for all events from the old obsolete phone
1084        unregisterForPhoneStates();
1085
1086        // (Re)register for all events relevant to the new active phone
1087        registerForPhoneStates();
1088
1089        // And finally, refresh the onscreen UI.  (Note that it's safe
1090        // to call requestUpdateScreen() even if the radio change ended up
1091        // causing us to exit the InCallScreen.)
1092        requestUpdateScreen();
1093    }
1094
1095    @Override
1096    protected void onNewIntent(Intent intent) {
1097        log("onNewIntent: intent = " + intent + ", phone state = " + mCM.getState());
1098
1099        // We're being re-launched with a new Intent.  Since it's possible for a
1100        // single InCallScreen instance to persist indefinitely (even if we
1101        // finish() ourselves), this sequence can potentially happen any time
1102        // the InCallScreen needs to be displayed.
1103
1104        // Stash away the new intent so that we can get it in the future
1105        // by calling getIntent().  (Otherwise getIntent() will return the
1106        // original Intent from when we first got created!)
1107        setIntent(intent);
1108
1109        // Activities are always paused before receiving a new intent, so
1110        // we can count on our onResume() method being called next.
1111
1112        // Just like in onCreate(), handle the intent.
1113        internalResolveIntent(intent);
1114    }
1115
1116    private void internalResolveIntent(Intent intent) {
1117        if (intent == null || intent.getAction() == null) {
1118            return;
1119        }
1120        String action = intent.getAction();
1121        if (DBG) log("internalResolveIntent: action=" + action);
1122
1123        // In gingerbread and earlier releases, the InCallScreen used to
1124        // directly handle certain intent actions that could initiate phone
1125        // calls, namely ACTION_CALL and ACTION_CALL_EMERGENCY, and also
1126        // OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING.
1127        //
1128        // But it doesn't make sense to tie those actions to the InCallScreen
1129        // (or especially to the *activity lifecycle* of the InCallScreen).
1130        // Instead, the InCallScreen should only be concerned with running the
1131        // onscreen UI while in a call.  So we've now offloaded the call-control
1132        // functionality to a new module called CallController, and OTASP calls
1133        // are now launched from the OtaUtils startInteractiveOtasp() or
1134        // startNonInteractiveOtasp() methods.
1135        //
1136        // So now, the InCallScreen is only ever launched using the ACTION_MAIN
1137        // action, and (upon launch) performs no functionality other than
1138        // displaying the UI in a state that matches the current telephony
1139        // state.
1140
1141        if (action.equals(intent.ACTION_MAIN)) {
1142            // This action is the normal way to bring up the in-call UI.
1143            //
1144            // Most of the interesting work of updating the onscreen UI (to
1145            // match the current telephony state) happens in the
1146            // syncWithPhoneState() => updateScreen() sequence that happens in
1147            // onResume().
1148            //
1149            // But we do check here for one extra that can come along with the
1150            // ACTION_MAIN intent:
1151
1152            if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
1153                // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
1154                // dialpad should be initially visible.  If the extra isn't
1155                // present at all, we just leave the dialpad in its previous state.
1156
1157                boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
1158                if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
1159
1160                // If SHOW_DIALPAD_EXTRA is specified, that overrides whatever
1161                // the previous state of inCallUiState.showDialpad was.
1162                mApp.inCallUiState.showDialpad = showDialpad;
1163
1164                final boolean hasActiveCall = mCM.hasActiveFgCall();
1165                final boolean hasHoldingCall = mCM.hasActiveBgCall();
1166
1167                // There's only one line in use, AND it's on hold, at which we're sure the user
1168                // wants to use the dialpad toward the exact line, so un-hold the holding line.
1169                if (showDialpad && !hasActiveCall && hasHoldingCall) {
1170                    PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
1171                }
1172            }
1173            // ...and in onResume() we'll update the onscreen dialpad state to
1174            // match the InCallUiState.
1175
1176            return;
1177        }
1178
1179        if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {
1180            // Bring up the in-call UI in the OTASP-specific "activate" state;
1181            // see OtaUtils.startInteractiveOtasp().  Note that at this point
1182            // the OTASP call has not been started yet; we won't actually make
1183            // the call until the user presses the "Activate" button.
1184
1185            if (!TelephonyCapabilities.supportsOtasp(mPhone)) {
1186                throw new IllegalStateException(
1187                    "Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "
1188                    + intent);
1189            }
1190
1191            setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
1192            if ((mApp.cdmaOtaProvisionData != null)
1193                && (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
1194                mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
1195                mApp.cdmaOtaScreenState.otaScreenState =
1196                        CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
1197            }
1198            return;
1199        }
1200
1201        // Various intent actions that should no longer come here directly:
1202        if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
1203            // This intent is now handled by the InCallScreenShowActivation
1204            // activity, which translates it into a call to
1205            // OtaUtils.startInteractiveOtasp().
1206            throw new IllegalStateException(
1207                "Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "
1208                + intent);
1209        } else if (action.equals(Intent.ACTION_CALL)
1210                   || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
1211            // ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now
1212            // translates them into CallController.placeCall() calls rather than
1213            // launching the InCallScreen directly.
1214            throw new IllegalStateException("Unexpected CALL action received by InCallScreen: "
1215                                            + intent);
1216        } else if (action.equals(ACTION_UNDEFINED)) {
1217            // This action is only used for internal bookkeeping; we should
1218            // never actually get launched with it.
1219            Log.wtf(LOG_TAG, "internalResolveIntent: got launched with ACTION_UNDEFINED");
1220            return;
1221        } else {
1222            Log.wtf(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
1223            // But continue the best we can (basically treating this case
1224            // like ACTION_MAIN...)
1225            return;
1226        }
1227    }
1228
1229    private void stopTimer() {
1230        if (mCallCard != null) mCallCard.stopTimer();
1231    }
1232
1233    private void initInCallScreen() {
1234        if (VDBG) log("initInCallScreen()...");
1235
1236        // Have the WindowManager filter out touch events that are "too fat".
1237        getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
1238
1239        // Initialize the CallCard.
1240        mCallCard = (CallCard) findViewById(R.id.callCard);
1241        if (VDBG) log("  - mCallCard = " + mCallCard);
1242        mCallCard.setInCallScreenInstance(this);
1243
1244        // Initialize the onscreen UI elements.
1245        initInCallTouchUi();
1246
1247        // Helper class to keep track of enabledness/state of UI controls
1248        mInCallControlState = new InCallControlState(this, mCM);
1249
1250        // Helper class to run the "Manage conference" UI
1251        mManageConferenceUtils = new ManageConferenceUtils(this, mCM);
1252
1253        // The DTMF Dialpad.
1254        ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
1255        mDialer = new DTMFTwelveKeyDialer(this, stub);
1256        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
1257    }
1258
1259    /**
1260     * Returns true if the phone is "in use", meaning that at least one line
1261     * is active (ie. off hook or ringing or dialing).  Conversely, a return
1262     * value of false means there's currently no phone activity at all.
1263     */
1264    private boolean phoneIsInUse() {
1265        return mCM.getState() != PhoneConstants.State.IDLE;
1266    }
1267
1268    private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
1269        if (VDBG) log("handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
1270
1271        // As soon as the user starts typing valid dialable keys on the
1272        // keyboard (presumably to type DTMF tones) we start passing the
1273        // key events to the DTMFDialer's onDialerKeyDown.  We do so
1274        // only if the okToDialDTMFTones() conditions pass.
1275        if (okToDialDTMFTones()) {
1276            return mDialer.onDialerKeyDown(event);
1277
1278            // TODO: If the dialpad isn't currently visible, maybe
1279            // consider automatically bringing it up right now?
1280            // (Just to make sure the user sees the digits widget...)
1281            // But this probably isn't too critical since it's awkward to
1282            // use the hard keyboard while in-call in the first place,
1283            // especially now that the in-call UI is portrait-only...
1284        }
1285
1286        return false;
1287    }
1288
1289    @Override
1290    public void onBackPressed() {
1291        if (DBG) log("onBackPressed()...");
1292
1293        // To consume this BACK press, the code here should just do
1294        // something and return.  Otherwise, call super.onBackPressed() to
1295        // get the default implementation (which simply finishes the
1296        // current activity.)
1297
1298        if (mCM.hasActiveRingingCall()) {
1299            // The Back key, just like the Home key, is always disabled
1300            // while an incoming call is ringing.  (The user *must* either
1301            // answer or reject the call before leaving the incoming-call
1302            // screen.)
1303            if (DBG) log("BACK key while ringing: ignored");
1304
1305            // And consume this event; *don't* call super.onBackPressed().
1306            return;
1307        }
1308
1309        // BACK is also used to exit out of any "special modes" of the
1310        // in-call UI:
1311
1312        if (mDialer.isOpened()) {
1313            closeDialpadInternal(true);  // do the "closing" animation
1314            return;
1315        }
1316
1317        if (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
1318            // Hide the Manage Conference panel, return to NORMAL mode.
1319            setInCallScreenMode(InCallScreenMode.NORMAL);
1320            requestUpdateScreen();
1321            return;
1322        }
1323
1324        // Nothing special to do.  Fall back to the default behavior.
1325        super.onBackPressed();
1326    }
1327
1328    /**
1329     * Handles the green CALL key while in-call.
1330     * @return true if we consumed the event.
1331     */
1332    private boolean handleCallKey() {
1333        // The green CALL button means either "Answer", "Unhold", or
1334        // "Swap calls", or can be a no-op, depending on the current state
1335        // of the Phone.
1336
1337        final boolean hasRingingCall = mCM.hasActiveRingingCall();
1338        final boolean hasActiveCall = mCM.hasActiveFgCall();
1339        final boolean hasHoldingCall = mCM.hasActiveBgCall();
1340
1341        int phoneType = mPhone.getPhoneType();
1342        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1343            // The green CALL button means either "Answer", "Swap calls/On Hold", or
1344            // "Add to 3WC", depending on the current state of the Phone.
1345
1346            CdmaPhoneCallState.PhoneCallState currCallState =
1347                mApp.cdmaPhoneCallState.getCurrentCallState();
1348            if (hasRingingCall) {
1349                //Scenario 1: Accepting the First Incoming and Call Waiting call
1350                if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
1351                internalAnswerCall();  // Automatically holds the current active call,
1352                                       // if there is one
1353            } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1354                    && (hasActiveCall)) {
1355                //Scenario 2: Merging 3Way calls
1356                if (DBG) log("answerCall: Merge 3-way call scenario");
1357                // Merge calls
1358                PhoneUtils.mergeCalls(mCM);
1359            } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1360                //Scenario 3: Switching between two Call waiting calls or drop the latest
1361                // connection if in a 3Way merge scenario
1362                if (DBG) log("answerCall: Switch btwn 2 calls scenario");
1363                internalSwapCalls();
1364            }
1365        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1366                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
1367            if (hasRingingCall) {
1368                // If an incoming call is ringing, the CALL button is actually
1369                // handled by the PhoneWindowManager.  (We do this to make
1370                // sure that we'll respond to the key even if the InCallScreen
1371                // hasn't come to the foreground yet.)
1372                //
1373                // We'd only ever get here in the extremely rare case that the
1374                // incoming call started ringing *after*
1375                // PhoneWindowManager.interceptKeyTq() but before the event
1376                // got here, or else if the PhoneWindowManager had some
1377                // problem connecting to the ITelephony service.
1378                Log.w(LOG_TAG, "handleCallKey: incoming call is ringing!"
1379                      + " (PhoneWindowManager should have handled this key.)");
1380                // But go ahead and handle the key as normal, since the
1381                // PhoneWindowManager presumably did NOT handle it:
1382
1383                // There's an incoming ringing call: CALL means "Answer".
1384                internalAnswerCall();
1385            } else if (hasActiveCall && hasHoldingCall) {
1386                // Two lines are in use: CALL means "Swap calls".
1387                if (DBG) log("handleCallKey: both lines in use ==> swap calls.");
1388                internalSwapCalls();
1389            } else if (hasHoldingCall) {
1390                // There's only one line in use, AND it's on hold.
1391                // In this case CALL is a shortcut for "unhold".
1392                if (DBG) log("handleCallKey: call on hold ==> unhold.");
1393                PhoneUtils.switchHoldingAndActive(
1394                    mCM.getFirstActiveBgCall());  // Really means "unhold" in this state
1395            } else {
1396                // The most common case: there's only one line in use, and
1397                // it's an active call (i.e. it's not on hold.)
1398                // In this case CALL is a no-op.
1399                // (This used to be a shortcut for "add call", but that was a
1400                // bad idea because "Add call" is so infrequently-used, and
1401                // because the user experience is pretty confusing if you
1402                // inadvertently trigger it.)
1403                if (VDBG) log("handleCallKey: call in foregound ==> ignoring.");
1404                // But note we still consume this key event; see below.
1405            }
1406        } else {
1407            throw new IllegalStateException("Unexpected phone type: " + phoneType);
1408        }
1409
1410        // We *always* consume the CALL key, since the system-wide default
1411        // action ("go to the in-call screen") is useless here.
1412        return true;
1413    }
1414
1415    boolean isKeyEventAcceptableDTMF (KeyEvent event) {
1416        return (mDialer != null && mDialer.isKeyEventAcceptable(event));
1417    }
1418
1419    /**
1420     * Overriden to track relevant focus changes.
1421     *
1422     * If a key is down and some time later the focus changes, we may
1423     * NOT recieve the keyup event; logically the keyup event has not
1424     * occured in this window.  This issue is fixed by treating a focus
1425     * changed event as an interruption to the keydown, making sure
1426     * that any code that needs to be run in onKeyUp is ALSO run here.
1427     */
1428    @Override
1429    public void onWindowFocusChanged(boolean hasFocus) {
1430        // the dtmf tones should no longer be played
1431        if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
1432        if (!hasFocus && mDialer != null) {
1433            if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
1434            mDialer.onDialerKeyUp(null);
1435        }
1436    }
1437
1438    @Override
1439    public boolean onKeyUp(int keyCode, KeyEvent event) {
1440        // if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
1441
1442        // push input to the dialer.
1443        if ((mDialer != null) && (mDialer.onDialerKeyUp(event))){
1444            return true;
1445        } else if (keyCode == KeyEvent.KEYCODE_CALL) {
1446            // Always consume CALL to be sure the PhoneWindow won't do anything with it
1447            return true;
1448        }
1449        return super.onKeyUp(keyCode, event);
1450    }
1451
1452    @Override
1453    public boolean onKeyDown(int keyCode, KeyEvent event) {
1454        // if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
1455
1456        switch (keyCode) {
1457            case KeyEvent.KEYCODE_CALL:
1458                boolean handled = handleCallKey();
1459                if (!handled) {
1460                    Log.w(LOG_TAG, "InCallScreen should always handle KEYCODE_CALL in onKeyDown");
1461                }
1462                // Always consume CALL to be sure the PhoneWindow won't do anything with it
1463                return true;
1464
1465            // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
1466            // The standard system-wide handling of the ENDCALL key
1467            // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
1468            // already implements exactly what the UI spec wants,
1469            // namely (1) "hang up" if there's a current active call,
1470            // or (2) "don't answer" if there's a current ringing call.
1471
1472            case KeyEvent.KEYCODE_CAMERA:
1473                // Disable the CAMERA button while in-call since it's too
1474                // easy to press accidentally.
1475                return true;
1476
1477            case KeyEvent.KEYCODE_VOLUME_UP:
1478            case KeyEvent.KEYCODE_VOLUME_DOWN:
1479            case KeyEvent.KEYCODE_VOLUME_MUTE:
1480                if (mCM.getState() == PhoneConstants.State.RINGING) {
1481                    // If an incoming call is ringing, the VOLUME buttons are
1482                    // actually handled by the PhoneWindowManager.  (We do
1483                    // this to make sure that we'll respond to them even if
1484                    // the InCallScreen hasn't come to the foreground yet.)
1485                    //
1486                    // We'd only ever get here in the extremely rare case that the
1487                    // incoming call started ringing *after*
1488                    // PhoneWindowManager.interceptKeyTq() but before the event
1489                    // got here, or else if the PhoneWindowManager had some
1490                    // problem connecting to the ITelephony service.
1491                    Log.w(LOG_TAG, "VOLUME key: incoming call is ringing!"
1492                          + " (PhoneWindowManager should have handled this key.)");
1493                    // But go ahead and handle the key as normal, since the
1494                    // PhoneWindowManager presumably did NOT handle it:
1495                    internalSilenceRinger();
1496
1497                    // As long as an incoming call is ringing, we always
1498                    // consume the VOLUME keys.
1499                    return true;
1500                }
1501                break;
1502
1503            case KeyEvent.KEYCODE_MUTE:
1504                onMuteClick();
1505                return true;
1506
1507            // Various testing/debugging features, enabled ONLY when VDBG == true.
1508            case KeyEvent.KEYCODE_SLASH:
1509                if (VDBG) {
1510                    log("----------- InCallScreen View dump --------------");
1511                    // Dump starting from the top-level view of the entire activity:
1512                    Window w = this.getWindow();
1513                    View decorView = w.getDecorView();
1514                    decorView.debug();
1515                    return true;
1516                }
1517                break;
1518            case KeyEvent.KEYCODE_EQUALS:
1519                if (VDBG) {
1520                    log("----------- InCallScreen call state dump --------------");
1521                    PhoneUtils.dumpCallState(mPhone);
1522                    PhoneUtils.dumpCallManager();
1523                    return true;
1524                }
1525                break;
1526            case KeyEvent.KEYCODE_GRAVE:
1527                if (VDBG) {
1528                    // Placeholder for other misc temp testing
1529                    log("------------ Temp testing -----------------");
1530                    return true;
1531                }
1532                break;
1533        }
1534
1535        if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
1536            return true;
1537        }
1538
1539        return super.onKeyDown(keyCode, event);
1540    }
1541
1542    /**
1543     * Handle a failure notification for a supplementary service
1544     * (i.e. conference, switch, separate, transfer, etc.).
1545     */
1546    void onSuppServiceFailed(AsyncResult r) {
1547        Phone.SuppService service = (Phone.SuppService) r.result;
1548        if (DBG) log("onSuppServiceFailed: " + service);
1549
1550        int errorMessageResId;
1551        switch (service) {
1552            case SWITCH:
1553                // Attempt to switch foreground and background/incoming calls failed
1554                // ("Failed to switch calls")
1555                errorMessageResId = R.string.incall_error_supp_service_switch;
1556                break;
1557
1558            case SEPARATE:
1559                // Attempt to separate a call from a conference call
1560                // failed ("Failed to separate out call")
1561                errorMessageResId = R.string.incall_error_supp_service_separate;
1562                break;
1563
1564            case TRANSFER:
1565                // Attempt to connect foreground and background calls to
1566                // each other (and hanging up user's line) failed ("Call
1567                // transfer failed")
1568                errorMessageResId = R.string.incall_error_supp_service_transfer;
1569                break;
1570
1571            case CONFERENCE:
1572                // Attempt to add a call to conference call failed
1573                // ("Conference call failed")
1574                errorMessageResId = R.string.incall_error_supp_service_conference;
1575                break;
1576
1577            case REJECT:
1578                // Attempt to reject an incoming call failed
1579                // ("Call rejection failed")
1580                errorMessageResId = R.string.incall_error_supp_service_reject;
1581                break;
1582
1583            case HANGUP:
1584                // Attempt to release a call failed ("Failed to release call(s)")
1585                errorMessageResId = R.string.incall_error_supp_service_hangup;
1586                break;
1587
1588            case UNKNOWN:
1589            default:
1590                // Attempt to use a service we don't recognize or support
1591                // ("Unsupported service" or "Selected service failed")
1592                errorMessageResId = R.string.incall_error_supp_service_unknown;
1593                break;
1594        }
1595
1596        // mSuppServiceFailureDialog is a generic dialog used for any
1597        // supp service failure, and there's only ever have one
1598        // instance at a time.  So just in case a previous dialog is
1599        // still around, dismiss it.
1600        if (mSuppServiceFailureDialog != null) {
1601            if (DBG) log("- DISMISSING mSuppServiceFailureDialog.");
1602            mSuppServiceFailureDialog.dismiss();  // It's safe to dismiss() a dialog
1603                                                  // that's already dismissed.
1604            mSuppServiceFailureDialog = null;
1605        }
1606
1607        mSuppServiceFailureDialog = new AlertDialog.Builder(this)
1608                .setMessage(errorMessageResId)
1609                .setPositiveButton(R.string.ok, null)
1610                .create();
1611        mSuppServiceFailureDialog.getWindow().addFlags(
1612                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1613        mSuppServiceFailureDialog.show();
1614    }
1615
1616    /**
1617     * Something has changed in the phone's state.  Update the UI.
1618     */
1619    private void onPhoneStateChanged(AsyncResult r) {
1620        PhoneConstants.State state = mCM.getState();
1621        if (DBG) log("onPhoneStateChanged: current state = " + state);
1622
1623        // There's nothing to do here if we're not the foreground activity.
1624        // (When we *do* eventually come to the foreground, we'll do a
1625        // full update then.)
1626        if (!mIsForegroundActivity) {
1627            if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
1628            return;
1629        }
1630
1631        updateExpandedViewState();
1632
1633        // Update the onscreen UI.
1634        // We use requestUpdateScreen() here (which posts a handler message)
1635        // instead of calling updateScreen() directly, which allows us to avoid
1636        // unnecessary work if multiple onPhoneStateChanged() events come in all
1637        // at the same time.
1638
1639        requestUpdateScreen();
1640
1641        // Make sure we update the poke lock and wake lock when certain
1642        // phone state changes occur.
1643        mApp.updateWakeState();
1644    }
1645
1646    /**
1647     * Updates the UI after a phone connection is disconnected, as follows:
1648     *
1649     * - If this was a missed or rejected incoming call, and no other
1650     *   calls are active, dismiss the in-call UI immediately.  (The
1651     *   CallNotifier will still create a "missed call" notification if
1652     *   necessary.)
1653     *
1654     * - With any other disconnect cause, if the phone is now totally
1655     *   idle, display the "Call ended" state for a couple of seconds.
1656     *
1657     * - Or, if the phone is still in use, stay on the in-call screen
1658     *   (and update the UI to reflect the current state of the Phone.)
1659     *
1660     * @param r r.result contains the connection that just ended
1661     */
1662    private void onDisconnect(AsyncResult r) {
1663        Connection c = (Connection) r.result;
1664        Connection.DisconnectCause cause = c.getDisconnectCause();
1665        if (DBG) log("onDisconnect: connection '" + c + "', cause = " + cause
1666                + ", showing screen: " + mApp.isShowingCallScreen());
1667
1668        boolean currentlyIdle = !phoneIsInUse();
1669        int autoretrySetting = AUTO_RETRY_OFF;
1670        boolean phoneIsCdma = (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
1671        if (phoneIsCdma) {
1672            // Get the Auto-retry setting only if Phone State is IDLE,
1673            // else let it stay as AUTO_RETRY_OFF
1674            if (currentlyIdle) {
1675                autoretrySetting = android.provider.Settings.Global.getInt(mPhone.getContext().
1676                        getContentResolver(), android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
1677            }
1678        }
1679
1680        // for OTA Call, only if in OTA NORMAL mode, handle OTA END scenario
1681        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
1682                && ((mApp.cdmaOtaProvisionData != null)
1683                && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
1684            setInCallScreenMode(InCallScreenMode.OTA_ENDED);
1685            updateScreen();
1686            return;
1687        } else if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
1688                   || ((mApp.cdmaOtaProvisionData != null)
1689                       && mApp.cdmaOtaProvisionData.inOtaSpcState)) {
1690           if (DBG) log("onDisconnect: OTA Call end already handled");
1691           return;
1692        }
1693
1694        // Any time a call disconnects, clear out the "history" of DTMF
1695        // digits you typed (to make sure it doesn't persist from one call
1696        // to the next.)
1697        mDialer.clearDigits();
1698
1699        // Under certain call disconnected states, we want to alert the user
1700        // with a dialog instead of going through the normal disconnect
1701        // routine.
1702        if (cause == Connection.DisconnectCause.CALL_BARRED) {
1703            showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
1704            return;
1705        } else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
1706            showGenericErrorDialog(R.string.callFailed_fdn_only, false);
1707            return;
1708        } else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
1709            showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
1710            return;
1711        } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
1712            showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
1713            return;
1714        } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
1715            showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
1716            return;
1717        }
1718
1719        if (phoneIsCdma) {
1720            Call.State callState = mApp.notifier.getPreviousCdmaCallState();
1721            if ((callState == Call.State.ACTIVE)
1722                    && (cause != Connection.DisconnectCause.INCOMING_MISSED)
1723                    && (cause != Connection.DisconnectCause.NORMAL)
1724                    && (cause != Connection.DisconnectCause.LOCAL)
1725                    && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1726                showCallLostDialog();
1727            } else if ((callState == Call.State.DIALING || callState == Call.State.ALERTING)
1728                        && (cause != Connection.DisconnectCause.INCOMING_MISSED)
1729                        && (cause != Connection.DisconnectCause.NORMAL)
1730                        && (cause != Connection.DisconnectCause.LOCAL)
1731                        && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1732
1733                if (mApp.inCallUiState.needToShowCallLostDialog) {
1734                    // Show the dialog now since the call that just failed was a retry.
1735                    showCallLostDialog();
1736                    mApp.inCallUiState.needToShowCallLostDialog = false;
1737                } else {
1738                    if (autoretrySetting == AUTO_RETRY_OFF) {
1739                        // Show the dialog for failed call if Auto Retry is OFF in Settings.
1740                        showCallLostDialog();
1741                        mApp.inCallUiState.needToShowCallLostDialog = false;
1742                    } else {
1743                        // Set the needToShowCallLostDialog flag now, so we'll know to show
1744                        // the dialog if *this* call fails.
1745                        mApp.inCallUiState.needToShowCallLostDialog = true;
1746                    }
1747                }
1748            }
1749        }
1750
1751        // Explicitly clean up up any DISCONNECTED connections
1752        // in a conference call.
1753        // [Background: Even after a connection gets disconnected, its
1754        // Connection object still stays around for a few seconds, in the
1755        // DISCONNECTED state.  With regular calls, this state drives the
1756        // "call ended" UI.  But when a single person disconnects from a
1757        // conference call there's no "call ended" state at all; in that
1758        // case we blow away any DISCONNECTED connections right now to make sure
1759        // the UI updates instantly to reflect the current state.]
1760        final Call call = c.getCall();
1761        if (call != null) {
1762            // We only care about situation of a single caller
1763            // disconnecting from a conference call.  In that case, the
1764            // call will have more than one Connection (including the one
1765            // that just disconnected, which will be in the DISCONNECTED
1766            // state) *and* at least one ACTIVE connection.  (If the Call
1767            // has *no* ACTIVE connections, that means that the entire
1768            // conference call just ended, so we *do* want to show the
1769            // "Call ended" state.)
1770            List<Connection> connections = call.getConnections();
1771            if (connections != null && connections.size() > 1) {
1772                for (Connection conn : connections) {
1773                    if (conn.getState() == Call.State.ACTIVE) {
1774                        // This call still has at least one ACTIVE connection!
1775                        // So blow away any DISCONNECTED connections
1776                        // (including, presumably, the one that just
1777                        // disconnected from this conference call.)
1778
1779                        // We also force the wake state to refresh, just in
1780                        // case the disconnected connections are removed
1781                        // before the phone state change.
1782                        if (VDBG) log("- Still-active conf call; clearing DISCONNECTED...");
1783                        mApp.updateWakeState();
1784                        mCM.clearDisconnected();  // This happens synchronously.
1785                        break;
1786                    }
1787                }
1788            }
1789        }
1790
1791        // Note: see CallNotifier.onDisconnect() for some other behavior
1792        // that might be triggered by a disconnect event, like playing the
1793        // busy/congestion tone.
1794
1795        // Stash away some info about the call that just disconnected.
1796        // (This might affect what happens after we exit the InCallScreen; see
1797        // delayedCleanupAfterDisconnect().)
1798        // TODO: rather than stashing this away now and then reading it in
1799        // delayedCleanupAfterDisconnect(), it would be cleaner to just pass
1800        // this as an argument to delayedCleanupAfterDisconnect() (if we call
1801        // it directly) or else pass it as a Message argument when we post the
1802        // DELAYED_CLEANUP_AFTER_DISCONNECT message.
1803        mLastDisconnectCause = cause;
1804
1805        // We bail out immediately (and *don't* display the "call ended"
1806        // state at all) if this was an incoming call.
1807        boolean bailOutImmediately =
1808                ((cause == Connection.DisconnectCause.INCOMING_MISSED)
1809                 || (cause == Connection.DisconnectCause.INCOMING_REJECTED))
1810                && currentlyIdle;
1811
1812        boolean showingQuickResponseDialog =
1813                mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
1814
1815        // Note: we also do some special handling for the case when a call
1816        // disconnects with cause==OUT_OF_SERVICE while making an
1817        // emergency call from airplane mode.  That's handled by
1818        // EmergencyCallHelper.onDisconnect().
1819
1820        if (bailOutImmediately && showingQuickResponseDialog) {
1821            if (DBG) log("- onDisconnect: Respond-via-SMS dialog is still being displayed...");
1822
1823            // Do *not* exit the in-call UI yet!
1824            // If the call was an incoming call that was missed *and* the user is using
1825            // quick response screen, we keep showing the screen for a moment, assuming the
1826            // user wants to reply the call anyway.
1827            //
1828            // For this case, we will exit the screen when:
1829            // - the message is sent (RespondViaSmsManager)
1830            // - the message is canceled (RespondViaSmsManager), or
1831            // - when the whole in-call UI becomes background (onPause())
1832        } else if (bailOutImmediately) {
1833            if (DBG) log("- onDisconnect: bailOutImmediately...");
1834
1835            // Exit the in-call UI!
1836            // (This is basically the same "delayed cleanup" we do below,
1837            // just with zero delay.  Since the Phone is currently idle,
1838            // this call is guaranteed to immediately finish this activity.)
1839            delayedCleanupAfterDisconnect();
1840        } else {
1841            if (DBG) log("- onDisconnect: delayed bailout...");
1842            // Stay on the in-call screen for now.  (Either the phone is
1843            // still in use, or the phone is idle but we want to display
1844            // the "call ended" state for a couple of seconds.)
1845
1846            // Switch to the special "Call ended" state when the phone is idle
1847            // but there's still a call in the DISCONNECTED state:
1848            if (currentlyIdle
1849                && (mCM.hasDisconnectedFgCall() || mCM.hasDisconnectedBgCall())) {
1850                if (DBG) log("- onDisconnect: switching to 'Call ended' state...");
1851                setInCallScreenMode(InCallScreenMode.CALL_ENDED);
1852            }
1853
1854            // Force a UI update in case we need to display anything
1855            // special based on this connection's DisconnectCause
1856            // (see CallCard.getCallFailedString()).
1857            updateScreen();
1858
1859            // Some other misc cleanup that we do if the call that just
1860            // disconnected was the foreground call.
1861            final boolean hasActiveCall = mCM.hasActiveFgCall();
1862            if (!hasActiveCall) {
1863                if (DBG) log("- onDisconnect: cleaning up after FG call disconnect...");
1864
1865                // Dismiss any dialogs which are only meaningful for an
1866                // active call *and* which become moot if the call ends.
1867                if (mWaitPromptDialog != null) {
1868                    if (VDBG) log("- DISMISSING mWaitPromptDialog.");
1869                    mWaitPromptDialog.dismiss();  // safe even if already dismissed
1870                    mWaitPromptDialog = null;
1871                }
1872                if (mWildPromptDialog != null) {
1873                    if (VDBG) log("- DISMISSING mWildPromptDialog.");
1874                    mWildPromptDialog.dismiss();  // safe even if already dismissed
1875                    mWildPromptDialog = null;
1876                }
1877                if (mPausePromptDialog != null) {
1878                    if (DBG) log("- DISMISSING mPausePromptDialog.");
1879                    mPausePromptDialog.dismiss();  // safe even if already dismissed
1880                    mPausePromptDialog = null;
1881                }
1882            }
1883
1884            // Updating the screen wake state is done in onPhoneStateChanged().
1885
1886
1887            // CDMA: We only clean up if the Phone state is IDLE as we might receive an
1888            // onDisconnect for a Call Collision case (rare but possible).
1889            // For Call collision cases i.e. when the user makes an out going call
1890            // and at the same time receives an Incoming Call, the Incoming Call is given
1891            // higher preference. At this time framework sends a disconnect for the Out going
1892            // call connection hence we should *not* bring down the InCallScreen as the Phone
1893            // State would be RINGING
1894            if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
1895                if (!currentlyIdle) {
1896                    // Clean up any connections in the DISCONNECTED state.
1897                    // This is necessary cause in CallCollision the foreground call might have
1898                    // connections in DISCONNECTED state which needs to be cleared.
1899                    mCM.clearDisconnected();
1900
1901                    // The phone is still in use.  Stay here in this activity.
1902                    // But we don't need to keep the screen on.
1903                    if (DBG) log("onDisconnect: Call Collision case - staying on InCallScreen.");
1904                    if (DBG) PhoneUtils.dumpCallState(mPhone);
1905                    return;
1906                }
1907            }
1908
1909            // This is onDisconnect() request from the last phone call; no available call anymore.
1910            //
1911            // When the in-call UI is in background *because* the screen is turned off (unlike the
1912            // other case where the other activity is being shown), we wake up the screen and
1913            // show "DISCONNECTED" state once, with appropriate elapsed time. After showing that
1914            // we *must* bail out of the screen again, showing screen lock if needed.
1915            //
1916            // See also comments for isForegroundActivityForProximity()
1917            //
1918            // TODO: Consider moving this to CallNotifier. This code assumes the InCallScreen
1919            // never gets destroyed. For this exact case, it works (since InCallScreen won't be
1920            // destroyed), while technically this isn't right; Activity may be destroyed when
1921            // in background.
1922            if (currentlyIdle && !isForegroundActivity() && isForegroundActivityForProximity()) {
1923                log("Force waking up the screen to let users see \"disconnected\" state");
1924                if (call != null) {
1925                    mCallCard.updateElapsedTimeWidget(call);
1926                }
1927                // This variable will be kept true until the next InCallScreen#onPause(), which
1928                // forcibly turns it off regardless of the situation (for avoiding unnecessary
1929                // confusion around this special case).
1930                mApp.inCallUiState.showAlreadyDisconnectedState = true;
1931
1932                // Finally request wake-up..
1933                mApp.wakeUpScreen();
1934
1935                // InCallScreen#onResume() will set DELAYED_CLEANUP_AFTER_DISCONNECT message,
1936                // so skip the following section.
1937                return;
1938            }
1939
1940            // Finally, arrange for delayedCleanupAfterDisconnect() to get
1941            // called after a short interval (during which we display the
1942            // "call ended" state.)  At that point, if the
1943            // Phone is idle, we'll finish out of this activity.
1944            final int callEndedDisplayDelay;
1945            switch (cause) {
1946                // When the local user hanged up the ongoing call, it is ok to dismiss the screen
1947                // soon. In other cases, we show the "hung up" screen longer.
1948                //
1949                // - For expected reasons we will use CALL_ENDED_LONG_DELAY.
1950                // -- when the peer hanged up the call
1951                // -- when the local user rejects the incoming call during the other ongoing call
1952                // (TODO: there may be other cases which should be in this category)
1953                //
1954                // - For other unexpected reasons, we will use CALL_ENDED_EXTRA_LONG_DELAY,
1955                //   assuming the local user wants to confirm the disconnect reason.
1956                case LOCAL:
1957                    callEndedDisplayDelay = CALL_ENDED_SHORT_DELAY;
1958                    break;
1959                case NORMAL:
1960                case INCOMING_REJECTED:
1961                    callEndedDisplayDelay = CALL_ENDED_LONG_DELAY;
1962                    break;
1963                default:
1964                    callEndedDisplayDelay = CALL_ENDED_EXTRA_LONG_DELAY;
1965                    break;
1966            }
1967            mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
1968            mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
1969                    callEndedDisplayDelay);
1970        }
1971
1972        // Remove 3way timer (only meaningful for CDMA)
1973        // TODO: this call needs to happen in the CallController, not here.
1974        // (It should probably be triggered by the CallNotifier's onDisconnect method.)
1975        // mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
1976    }
1977
1978    /**
1979     * Brings up the "MMI Started" dialog.
1980     */
1981    /* TODO: sort out MMI code (probably we should remove this method entirely). See also
1982       MMI handling code in onResume()
1983    private void onMMIInitiate(AsyncResult r) {
1984        if (VDBG) log("onMMIInitiate()...  AsyncResult r = " + r);
1985
1986        // Watch out: don't do this if we're not the foreground activity,
1987        // mainly since in the Dialog.show() might fail if we don't have a
1988        // valid window token any more...
1989        // (Note that this exact sequence can happen if you try to start
1990        // an MMI code while the radio is off or out of service.)
1991        if (!mIsForegroundActivity) {
1992            if (VDBG) log("Activity not in foreground! Bailing out...");
1993            return;
1994        }
1995
1996        // Also, if any other dialog is up right now (presumably the
1997        // generic error dialog displaying the "Starting MMI..."  message)
1998        // take it down before bringing up the real "MMI Started" dialog
1999        // in its place.
2000        dismissAllDialogs();
2001
2002        MmiCode mmiCode = (MmiCode) r.result;
2003        if (VDBG) log("  - MmiCode: " + mmiCode);
2004
2005        Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
2006        mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
2007                                                          message, mMmiStartedDialog);
2008    }*/
2009
2010    /**
2011     * Handles an MMI_CANCEL event, which is triggered by the button
2012     * (labeled either "OK" or "Cancel") on the "MMI Started" dialog.
2013     * @see PhoneUtils#cancelMmiCode(Phone)
2014     */
2015    private void onMMICancel() {
2016        if (VDBG) log("onMMICancel()...");
2017
2018        // First of all, cancel the outstanding MMI code (if possible.)
2019        PhoneUtils.cancelMmiCode(mPhone);
2020
2021        // Regardless of whether the current MMI code was cancelable, the
2022        // PhoneApp will get an MMI_COMPLETE event very soon, which will
2023        // take us to the MMI Complete dialog (see
2024        // PhoneUtils.displayMMIComplete().)
2025        //
2026        // But until that event comes in, we *don't* want to stay here on
2027        // the in-call screen, since we'll be visible in a
2028        // partially-constructed state as soon as the "MMI Started" dialog
2029        // gets dismissed.  So let's forcibly bail out right now.
2030        if (DBG) log("onMMICancel: finishing InCallScreen...");
2031        dismissAllDialogs();
2032        endInCallScreenSession();
2033    }
2034
2035    /**
2036     * Handles an MMI_COMPLETE event, which is triggered by telephony,
2037     * implying MMI
2038     */
2039    private void onMMIComplete(MmiCode mmiCode) {
2040        // Check the code to see if the request is ready to
2041        // finish, this includes any MMI state that is not
2042        // PENDING.
2043
2044        // if phone is a CDMA phone display feature code completed message
2045        int phoneType = mPhone.getPhoneType();
2046        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2047            PhoneUtils.displayMMIComplete(mPhone, mApp, mmiCode, null, null);
2048        } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
2049            if (mmiCode.getState() != MmiCode.State.PENDING) {
2050                if (DBG) log("Got MMI_COMPLETE, finishing InCallScreen...");
2051                dismissAllDialogs();
2052                endInCallScreenSession();
2053            }
2054        }
2055    }
2056
2057    /**
2058     * Handles the POST_ON_DIAL_CHARS message from the Phone
2059     * (see our call to mPhone.setOnPostDialCharacter() above.)
2060     *
2061     * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle
2062     * "dialable" key events here in the InCallScreen: we do directly to the
2063     * Dialer UI instead.  Similarly, we may now need to go directly to the
2064     * Dialer to handle POST_ON_DIAL_CHARS too.
2065     */
2066    private void handlePostOnDialChars(AsyncResult r, char ch) {
2067        Connection c = (Connection) r.result;
2068
2069        if (c != null) {
2070            Connection.PostDialState state =
2071                    (Connection.PostDialState) r.userObj;
2072
2073            if (VDBG) log("handlePostOnDialChar: state = " +
2074                    state + ", ch = " + ch);
2075
2076            switch (state) {
2077                case STARTED:
2078                    mDialer.stopLocalToneIfNeeded();
2079                    if (mPauseInProgress) {
2080                        /**
2081                         * Note that on some devices, this will never happen,
2082                         * because we will not ever enter the PAUSE state.
2083                         */
2084                        showPausePromptDialog(c, mPostDialStrAfterPause);
2085                    }
2086                    mPauseInProgress = false;
2087                    mDialer.startLocalToneIfNeeded(ch);
2088
2089                    // TODO: is this needed, now that you can't actually
2090                    // type DTMF chars or dial directly from here?
2091                    // If so, we'd need to yank you out of the in-call screen
2092                    // here too (and take you to the 12-key dialer in "in-call" mode.)
2093                    // displayPostDialedChar(ch);
2094                    break;
2095
2096                case WAIT:
2097                    // wait shows a prompt.
2098                    if (DBG) log("handlePostOnDialChars: show WAIT prompt...");
2099                    mDialer.stopLocalToneIfNeeded();
2100                    String postDialStr = c.getRemainingPostDialString();
2101                    showWaitPromptDialog(c, postDialStr);
2102                    break;
2103
2104                case WILD:
2105                    if (DBG) log("handlePostOnDialChars: show WILD prompt");
2106                    mDialer.stopLocalToneIfNeeded();
2107                    showWildPromptDialog(c);
2108                    break;
2109
2110                case COMPLETE:
2111                    mDialer.stopLocalToneIfNeeded();
2112                    break;
2113
2114                case PAUSE:
2115                    // pauses for a brief period of time then continue dialing.
2116                    mDialer.stopLocalToneIfNeeded();
2117                    mPostDialStrAfterPause = c.getRemainingPostDialString();
2118                    mPauseInProgress = true;
2119                    break;
2120
2121                default:
2122                    break;
2123            }
2124        }
2125    }
2126
2127    /**
2128     * Pop up an alert dialog with OK and Cancel buttons to allow user to
2129     * Accept or Reject the WAIT inserted as part of the Dial string.
2130     */
2131    private void showWaitPromptDialog(final Connection c, String postDialStr) {
2132        if (DBG) log("showWaitPromptDialogChoice: '" + postDialStr + "'...");
2133
2134        Resources r = getResources();
2135        StringBuilder buf = new StringBuilder();
2136        buf.append(r.getText(R.string.wait_prompt_str));
2137        buf.append(postDialStr);
2138
2139        // if (DBG) log("- mWaitPromptDialog = " + mWaitPromptDialog);
2140        if (mWaitPromptDialog != null) {
2141            if (DBG) log("- DISMISSING mWaitPromptDialog.");
2142            mWaitPromptDialog.dismiss();  // safe even if already dismissed
2143            mWaitPromptDialog = null;
2144        }
2145
2146        mWaitPromptDialog = new AlertDialog.Builder(this)
2147                .setMessage(buf.toString())
2148                .setPositiveButton(R.string.pause_prompt_yes,
2149                        new DialogInterface.OnClickListener() {
2150                            @Override
2151                            public void onClick(DialogInterface dialog, int whichButton) {
2152                                if (DBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
2153                                c.proceedAfterWaitChar();
2154                            }
2155                        })
2156                .setNegativeButton(R.string.pause_prompt_no,
2157                        new DialogInterface.OnClickListener() {
2158                            @Override
2159                            public void onClick(DialogInterface dialog, int whichButton) {
2160                                if (DBG) log("handle POST_DIAL_CANCELED!");
2161                                c.cancelPostDial();
2162                            }
2163                        })
2164                .create();
2165        mWaitPromptDialog.getWindow().addFlags(
2166                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2167
2168        mWaitPromptDialog.show();
2169    }
2170
2171    /**
2172     * Pop up an alert dialog which waits for 2 seconds for each P (Pause) Character entered
2173     * as part of the Dial String.
2174     */
2175    private void showPausePromptDialog(final Connection c, String postDialStrAfterPause) {
2176        Resources r = getResources();
2177        StringBuilder buf = new StringBuilder();
2178        buf.append(r.getText(R.string.pause_prompt_str));
2179        buf.append(postDialStrAfterPause);
2180
2181        if (mPausePromptDialog != null) {
2182            if (DBG) log("- DISMISSING mPausePromptDialog.");
2183            mPausePromptDialog.dismiss();  // safe even if already dismissed
2184            mPausePromptDialog = null;
2185        }
2186
2187        mPausePromptDialog = new AlertDialog.Builder(this)
2188                .setMessage(buf.toString())
2189                .create();
2190        mPausePromptDialog.show();
2191        // 2 second timer
2192        Message msg = Message.obtain(mHandler, EVENT_PAUSE_DIALOG_COMPLETE);
2193        mHandler.sendMessageDelayed(msg, PAUSE_PROMPT_DIALOG_TIMEOUT);
2194    }
2195
2196    private View createWildPromptView() {
2197        LinearLayout result = new LinearLayout(this);
2198        result.setOrientation(LinearLayout.VERTICAL);
2199        result.setPadding(5, 5, 5, 5);
2200
2201        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
2202                        ViewGroup.LayoutParams.MATCH_PARENT,
2203                        ViewGroup.LayoutParams.WRAP_CONTENT);
2204
2205        TextView promptMsg = new TextView(this);
2206        promptMsg.setTextSize(14);
2207        promptMsg.setTypeface(Typeface.DEFAULT_BOLD);
2208        promptMsg.setText(getResources().getText(R.string.wild_prompt_str));
2209
2210        result.addView(promptMsg, lp);
2211
2212        mWildPromptText = new EditText(this);
2213        mWildPromptText.setKeyListener(DialerKeyListener.getInstance());
2214        mWildPromptText.setMovementMethod(null);
2215        mWildPromptText.setTextSize(14);
2216        mWildPromptText.setMaxLines(1);
2217        mWildPromptText.setHorizontallyScrolling(true);
2218        mWildPromptText.setBackgroundResource(android.R.drawable.editbox_background);
2219
2220        LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
2221                        ViewGroup.LayoutParams.MATCH_PARENT,
2222                        ViewGroup.LayoutParams.WRAP_CONTENT);
2223        lp2.setMargins(0, 3, 0, 0);
2224
2225        result.addView(mWildPromptText, lp2);
2226
2227        return result;
2228    }
2229
2230    private void showWildPromptDialog(final Connection c) {
2231        View v = createWildPromptView();
2232
2233        if (mWildPromptDialog != null) {
2234            if (VDBG) log("- DISMISSING mWildPromptDialog.");
2235            mWildPromptDialog.dismiss();  // safe even if already dismissed
2236            mWildPromptDialog = null;
2237        }
2238
2239        mWildPromptDialog = new AlertDialog.Builder(this)
2240                .setView(v)
2241                .setPositiveButton(
2242                        R.string.send_button,
2243                        new DialogInterface.OnClickListener() {
2244                            @Override
2245                            public void onClick(DialogInterface dialog, int whichButton) {
2246                                if (VDBG) log("handle WILD_PROMPT_CHAR_ENTERED, proceed...");
2247                                String replacement = null;
2248                                if (mWildPromptText != null) {
2249                                    replacement = mWildPromptText.getText().toString();
2250                                    mWildPromptText = null;
2251                                }
2252                                c.proceedAfterWildChar(replacement);
2253                                mApp.pokeUserActivity();
2254                            }
2255                        })
2256                .setOnCancelListener(
2257                        new DialogInterface.OnCancelListener() {
2258                            @Override
2259                            public void onCancel(DialogInterface dialog) {
2260                                if (VDBG) log("handle POST_DIAL_CANCELED!");
2261                                c.cancelPostDial();
2262                                mApp.pokeUserActivity();
2263                            }
2264                        })
2265                .create();
2266        mWildPromptDialog.getWindow().addFlags(
2267                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2268        mWildPromptDialog.show();
2269
2270        mWildPromptText.requestFocus();
2271    }
2272
2273    /**
2274     * Updates the state of the in-call UI based on the current state of
2275     * the Phone.  This call has no effect if we're not currently the
2276     * foreground activity.
2277     *
2278     * This method is only allowed to be called from the UI thread (since it
2279     * manipulates our View hierarchy).  If you need to update the screen from
2280     * some other thread, or if you just want to "post a request" for the screen
2281     * to be updated (rather than doing it synchronously), call
2282     * requestUpdateScreen() instead.
2283     *
2284     * Right now this method will update UI visibility immediately, with no animation.
2285     * TODO: have animate flag here and use it anywhere possible.
2286     */
2287    private void updateScreen() {
2288        if (DBG) log("updateScreen()...");
2289        final InCallScreenMode inCallScreenMode = mApp.inCallUiState.inCallScreenMode;
2290        if (VDBG) {
2291            PhoneConstants.State state = mCM.getState();
2292            log("  - phone state = " + state);
2293            log("  - inCallScreenMode = " + inCallScreenMode);
2294        }
2295
2296        // Don't update anything if we're not in the foreground (there's
2297        // no point updating our UI widgets since we're not visible!)
2298        // Also note this check also ensures we won't update while we're
2299        // in the middle of pausing, which could cause a visible glitch in
2300        // the "activity ending" transition.
2301        if (!mIsForegroundActivity) {
2302            if (DBG) log("- updateScreen: not the foreground Activity! Bailing out...");
2303            return;
2304        }
2305
2306        if (inCallScreenMode == InCallScreenMode.OTA_NORMAL) {
2307            if (DBG) log("- updateScreen: OTA call state NORMAL (NOT updating in-call UI)...");
2308            mCallCard.setVisibility(View.GONE);
2309            if (mApp.otaUtils != null) {
2310                mApp.otaUtils.otaShowProperScreen();
2311            } else {
2312                Log.w(LOG_TAG, "OtaUtils object is null, not showing any screen for that.");
2313            }
2314            return;  // Return without updating in-call UI.
2315        } else if (inCallScreenMode == InCallScreenMode.OTA_ENDED) {
2316            if (DBG) log("- updateScreen: OTA call ended state (NOT updating in-call UI)...");
2317            mCallCard.setVisibility(View.GONE);
2318            // Wake up the screen when we get notification, good or bad.
2319            mApp.wakeUpScreen();
2320            if (mApp.cdmaOtaScreenState.otaScreenState
2321                    == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION) {
2322                if (DBG) log("- updateScreen: OTA_STATUS_ACTIVATION");
2323                if (mApp.otaUtils != null) {
2324                    if (DBG) log("- updateScreen: mApp.otaUtils is not null, "
2325                                  + "call otaShowActivationScreen");
2326                    mApp.otaUtils.otaShowActivateScreen();
2327                }
2328            } else {
2329                if (DBG) log("- updateScreen: OTA Call end state for Dialogs");
2330                if (mApp.otaUtils != null) {
2331                    if (DBG) log("- updateScreen: Show OTA Success Failure dialog");
2332                    mApp.otaUtils.otaShowSuccessFailure();
2333                }
2334            }
2335            return;  // Return without updating in-call UI.
2336        } else if (inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
2337            if (DBG) log("- updateScreen: manage conference mode (NOT updating in-call UI)...");
2338            mCallCard.setVisibility(View.GONE);
2339            updateManageConferencePanelIfNecessary();
2340            return;  // Return without updating in-call UI.
2341        } else if (inCallScreenMode == InCallScreenMode.CALL_ENDED) {
2342            if (DBG) log("- updateScreen: call ended state...");
2343            // Continue with the rest of updateScreen() as usual, since we do
2344            // need to update the background (to the special "call ended" color)
2345            // and the CallCard (to show the "Call ended" label.)
2346        }
2347
2348        if (DBG) log("- updateScreen: updating the in-call UI...");
2349        // Note we update the InCallTouchUi widget before the CallCard,
2350        // since the CallCard adjusts its size based on how much vertical
2351        // space the InCallTouchUi widget needs.
2352        updateInCallTouchUi();
2353        mCallCard.updateState(mCM);
2354
2355        // If an incoming call is ringing, make sure the dialpad is
2356        // closed.  (We do this to make sure we're not covering up the
2357        // "incoming call" UI.)
2358        if (mCM.getState() == PhoneConstants.State.RINGING) {
2359            if (mDialer.isOpened()) {
2360              Log.i(LOG_TAG, "During RINGING state we force hiding dialpad.");
2361              closeDialpadInternal(false);  // don't do the "closing" animation
2362            }
2363
2364            // At this point, we are guranteed that the dialer is closed.
2365            // This means that it is safe to clear out the "history" of DTMF digits
2366            // you may have typed into the previous call (so you don't see the
2367            // previous call's digits if you answer this call and then bring up the
2368            // dialpad.)
2369            //
2370            // TODO: it would be more precise to do this when you *answer* the
2371            // incoming call, rather than as soon as it starts ringing, but
2372            // the InCallScreen doesn't keep enough state right now to notice
2373            // that specific transition in onPhoneStateChanged().
2374            // TODO: This clears out the dialpad context as well so when a second
2375            // call comes in while a voicemail call is happening, the voicemail
2376            // dialpad will no longer have the "Voice Mail" context. It's a small
2377            // case so not terribly bad, but we need to maintain a better
2378            // call-to-callstate mapping before we can fix this.
2379            mDialer.clearDigits();
2380        }
2381
2382
2383        // Now that we're sure DTMF dialpad is in an appropriate state, reflect
2384        // the dialpad state into CallCard
2385        updateCallCardVisibilityPerDialerState(false);
2386
2387        updateProgressIndication();
2388
2389        // Forcibly take down all dialog if an incoming call is ringing.
2390        if (mCM.hasActiveRingingCall()) {
2391            dismissAllDialogs();
2392        } else {
2393            // Wait prompt dialog is not currently up.  But it *should* be
2394            // up if the FG call has a connection in the WAIT state and
2395            // the phone isn't ringing.
2396            String postDialStr = null;
2397            List<Connection> fgConnections = mCM.getFgCallConnections();
2398            int phoneType = mCM.getFgPhone().getPhoneType();
2399            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2400                Connection fgLatestConnection = mCM.getFgCallLatestConnection();
2401                if (mApp.cdmaPhoneCallState.getCurrentCallState() ==
2402                        CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
2403                    for (Connection cn : fgConnections) {
2404                        if ((cn != null) && (cn.getPostDialState() ==
2405                                Connection.PostDialState.WAIT)) {
2406                            cn.cancelPostDial();
2407                        }
2408                    }
2409                } else if ((fgLatestConnection != null)
2410                     && (fgLatestConnection.getPostDialState() == Connection.PostDialState.WAIT)) {
2411                    if(DBG) log("show the Wait dialog for CDMA");
2412                    postDialStr = fgLatestConnection.getRemainingPostDialString();
2413                    showWaitPromptDialog(fgLatestConnection, postDialStr);
2414                }
2415            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
2416                    || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
2417                for (Connection cn : fgConnections) {
2418                    if ((cn != null) && (cn.getPostDialState() == Connection.PostDialState.WAIT)) {
2419                        postDialStr = cn.getRemainingPostDialString();
2420                        showWaitPromptDialog(cn, postDialStr);
2421                    }
2422                }
2423            } else {
2424                throw new IllegalStateException("Unexpected phone type: " + phoneType);
2425            }
2426        }
2427    }
2428
2429    /**
2430     * (Re)synchronizes the onscreen UI with the current state of the
2431     * telephony framework.
2432     *
2433     * @return SyncWithPhoneStateStatus.SUCCESS if we successfully updated the UI, or
2434     *    SyncWithPhoneStateStatus.PHONE_NOT_IN_USE if there was no phone state to sync
2435     *    with (ie. the phone was completely idle).  In the latter case, we
2436     *    shouldn't even be in the in-call UI in the first place, and it's
2437     *    the caller's responsibility to bail out of this activity by
2438     *    calling endInCallScreenSession if appropriate.
2439     *
2440     * This method directly calls updateScreen() in the normal "phone is
2441     * in use" case, so there's no need for the caller to do so.
2442     */
2443    private SyncWithPhoneStateStatus syncWithPhoneState() {
2444        boolean updateSuccessful = false;
2445        if (DBG) log("syncWithPhoneState()...");
2446        if (DBG) PhoneUtils.dumpCallState(mPhone);
2447        if (VDBG) dumpBluetoothState();
2448
2449        // Make sure the Phone is "in use".  (If not, we shouldn't be on
2450        // this screen in the first place.)
2451
2452        // An active or just-ended OTA call counts as "in use".
2453        if (TelephonyCapabilities.supportsOtasp(mCM.getFgPhone())
2454                && ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
2455                    || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED))) {
2456            // Even when OTA Call ends, need to show OTA End UI,
2457            // so return Success to allow UI update.
2458            return SyncWithPhoneStateStatus.SUCCESS;
2459        }
2460
2461        // If an MMI code is running that also counts as "in use".
2462        //
2463        // TODO: We currently only call getPendingMmiCodes() for GSM
2464        //   phones.  (The code's been that way all along.)  But CDMAPhone
2465        //   does in fact implement getPendingMmiCodes(), so should we
2466        //   check that here regardless of the phone type?
2467        boolean hasPendingMmiCodes =
2468                (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
2469                && !mPhone.getPendingMmiCodes().isEmpty();
2470
2471        // Finally, it's also OK to stay here on the InCallScreen if we
2472        // need to display a progress indicator while something's
2473        // happening in the background.
2474        boolean showProgressIndication = mApp.inCallUiState.isProgressIndicationActive();
2475
2476        boolean showScreenEvenAfterDisconnect = mApp.inCallUiState.showAlreadyDisconnectedState;
2477
2478        if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()
2479                || hasPendingMmiCodes || showProgressIndication || showScreenEvenAfterDisconnect) {
2480            if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");
2481            updateScreen();
2482            return SyncWithPhoneStateStatus.SUCCESS;
2483        }
2484
2485        Log.i(LOG_TAG, "syncWithPhoneState: phone is idle (shouldn't be here)");
2486        return SyncWithPhoneStateStatus.PHONE_NOT_IN_USE;
2487    }
2488
2489
2490
2491    private void handleMissingVoiceMailNumber() {
2492        if (DBG) log("handleMissingVoiceMailNumber");
2493
2494        final Message msg = Message.obtain(mHandler);
2495        msg.what = DONT_ADD_VOICEMAIL_NUMBER;
2496
2497        final Message msg2 = Message.obtain(mHandler);
2498        msg2.what = ADD_VOICEMAIL_NUMBER;
2499
2500        mMissingVoicemailDialog = new AlertDialog.Builder(this)
2501                .setTitle(R.string.no_vm_number)
2502                .setMessage(R.string.no_vm_number_msg)
2503                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2504                        public void onClick(DialogInterface dialog, int which) {
2505                            if (VDBG) log("Missing voicemail AlertDialog: POSITIVE click...");
2506                            msg.sendToTarget();  // see dontAddVoiceMailNumber()
2507                            mApp.pokeUserActivity();
2508                        }})
2509                .setNegativeButton(R.string.add_vm_number_str,
2510                                   new DialogInterface.OnClickListener() {
2511                        public void onClick(DialogInterface dialog, int which) {
2512                            if (VDBG) log("Missing voicemail AlertDialog: NEGATIVE click...");
2513                            msg2.sendToTarget();  // see addVoiceMailNumber()
2514                            mApp.pokeUserActivity();
2515                        }})
2516                .setOnCancelListener(new OnCancelListener() {
2517                        public void onCancel(DialogInterface dialog) {
2518                            if (VDBG) log("Missing voicemail AlertDialog: CANCEL handler...");
2519                            msg.sendToTarget();  // see dontAddVoiceMailNumber()
2520                            mApp.pokeUserActivity();
2521                        }})
2522                .create();
2523
2524        // When the dialog is up, completely hide the in-call UI
2525        // underneath (which is in a partially-constructed state).
2526        mMissingVoicemailDialog.getWindow().addFlags(
2527                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
2528
2529        mMissingVoicemailDialog.show();
2530    }
2531
2532    private void addVoiceMailNumberPanel() {
2533        if (mMissingVoicemailDialog != null) {
2534            mMissingVoicemailDialog.dismiss();
2535            mMissingVoicemailDialog = null;
2536        }
2537        if (DBG) log("addVoiceMailNumberPanel: finishing InCallScreen...");
2538        endInCallScreenSession();
2539
2540        if (DBG) log("show vm setting");
2541
2542        // navigate to the Voicemail setting in the Call Settings activity.
2543        Intent intent = new Intent(CallFeaturesSetting.ACTION_ADD_VOICEMAIL);
2544        intent.setClass(this, CallFeaturesSetting.class);
2545        startActivity(intent);
2546    }
2547
2548    private void dontAddVoiceMailNumber() {
2549        if (mMissingVoicemailDialog != null) {
2550            mMissingVoicemailDialog.dismiss();
2551            mMissingVoicemailDialog = null;
2552        }
2553        if (DBG) log("dontAddVoiceMailNumber: finishing InCallScreen...");
2554        endInCallScreenSession();
2555    }
2556
2557    /**
2558     * Do some delayed cleanup after a Phone call gets disconnected.
2559     *
2560     * This method gets called a couple of seconds after any DISCONNECT
2561     * event from the Phone; it's triggered by the
2562     * DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
2563     *
2564     * If the Phone is totally idle right now, that means we've already
2565     * shown the "call ended" state for a couple of seconds, and it's now
2566     * time to endInCallScreenSession this activity.
2567     *
2568     * If the Phone is *not* idle right now, that probably means that one
2569     * call ended but the other line is still in use.  In that case, do
2570     * nothing, and instead stay here on the InCallScreen.
2571     */
2572    private void delayedCleanupAfterDisconnect() {
2573        if (VDBG) log("delayedCleanupAfterDisconnect()...  Phone state = " + mCM.getState());
2574
2575        // Clean up any connections in the DISCONNECTED state.
2576        //
2577        // [Background: Even after a connection gets disconnected, its
2578        // Connection object still stays around, in the special
2579        // DISCONNECTED state.  This is necessary because we we need the
2580        // caller-id information from that Connection to properly draw the
2581        // "Call ended" state of the CallCard.
2582        //   But at this point we truly don't need that connection any
2583        // more, so tell the Phone that it's now OK to to clean up any
2584        // connections still in that state.]
2585        mCM.clearDisconnected();
2586
2587        // There are two cases where we should *not* exit the InCallScreen:
2588        //   (1) Phone is still in use
2589        // or
2590        //   (2) There's an active progress indication (i.e. the "Retrying..."
2591        //       progress dialog) that we need to continue to display.
2592
2593        boolean stayHere = phoneIsInUse() || mApp.inCallUiState.isProgressIndicationActive();
2594
2595        if (stayHere) {
2596            if (DBG) log("- delayedCleanupAfterDisconnect: staying on the InCallScreen...");
2597        } else {
2598            // Phone is idle!  We should exit the in-call UI now.
2599            if (DBG) log("- delayedCleanupAfterDisconnect: phone is idle...");
2600
2601            // And (finally!) exit from the in-call screen
2602            // (but not if we're already in the process of pausing...)
2603            if (mIsForegroundActivity) {
2604                if (DBG) log("- delayedCleanupAfterDisconnect: finishing InCallScreen...");
2605
2606                // In some cases we finish the call by taking the user to the
2607                // Call Log.  Otherwise, we simply call endInCallScreenSession,
2608                // which will take us back to wherever we came from.
2609                //
2610                // UI note: In eclair and earlier, we went to the Call Log
2611                // after outgoing calls initiated on the device, but never for
2612                // incoming calls.  Now we do it for incoming calls too, as
2613                // long as the call was answered by the user.  (We always go
2614                // back where you came from after a rejected or missed incoming
2615                // call.)
2616                //
2617                // And in any case, *never* go to the call log if we're in
2618                // emergency mode (i.e. if the screen is locked and a lock
2619                // pattern or PIN/password is set), or if we somehow got here
2620                // on a non-voice-capable device.
2621
2622                if (VDBG) log("- Post-call behavior:");
2623                if (VDBG) log("  - mLastDisconnectCause = " + mLastDisconnectCause);
2624                if (VDBG) log("  - isPhoneStateRestricted() = " + isPhoneStateRestricted());
2625
2626                // DisconnectCause values in the most common scenarios:
2627                // - INCOMING_MISSED: incoming ringing call times out, or the
2628                //                    other end hangs up while still ringing
2629                // - INCOMING_REJECTED: user rejects the call while ringing
2630                // - LOCAL: user hung up while a call was active (after
2631                //          answering an incoming call, or after making an
2632                //          outgoing call)
2633                // - NORMAL: the other end hung up (after answering an incoming
2634                //           call, or after making an outgoing call)
2635
2636                if ((mLastDisconnectCause != Connection.DisconnectCause.INCOMING_MISSED)
2637                        && (mLastDisconnectCause != Connection.DisconnectCause.INCOMING_REJECTED)
2638                        && !isPhoneStateRestricted()
2639                        && PhoneGlobals.sVoiceCapable) {
2640                    final Intent intent = mApp.createPhoneEndIntentUsingCallOrigin();
2641                    ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
2642                            R.anim.activity_close_enter, R.anim.activity_close_exit);
2643                    if (VDBG) {
2644                        log("- Show Call Log (or Dialtacts) after disconnect. Current intent: "
2645                                + intent);
2646                    }
2647                    try {
2648                        startActivity(intent, opts.toBundle());
2649                    } catch (ActivityNotFoundException e) {
2650                        // Don't crash if there's somehow no "Call log" at
2651                        // all on this device.
2652                        // (This should never happen, though, since we already
2653                        // checked PhoneApp.sVoiceCapable above, and any
2654                        // voice-capable device surely *should* have a call
2655                        // log activity....)
2656                        Log.w(LOG_TAG, "delayedCleanupAfterDisconnect: "
2657                              + "transition to call log failed; intent = " + intent);
2658                        // ...so just return back where we came from....
2659                    }
2660                    // Even if we did go to the call log, note that we still
2661                    // call endInCallScreenSession (below) to make sure we don't
2662                    // stay in the activity history.
2663                }
2664
2665            }
2666            endInCallScreenSession();
2667
2668            // Reset the call origin when the session ends and this in-call UI is being finished.
2669            mApp.setLatestActiveCallOrigin(null);
2670        }
2671    }
2672
2673
2674    /**
2675     * View.OnClickListener implementation.
2676     *
2677     * This method handles clicks from UI elements that use the
2678     * InCallScreen itself as their OnClickListener.
2679     *
2680     * Note: Currently this method is used only for a few special buttons:
2681     * - the mButtonManageConferenceDone "Back to call" button
2682     * - the "dim" effect for the secondary call photo in CallCard as the second "swap" button
2683     * - other OTASP-specific buttons managed by OtaUtils.java.
2684     *
2685     * *Most* in-call controls are handled by the handleOnscreenButtonClick() method, via the
2686     * InCallTouchUi widget.
2687     */
2688    @Override
2689    public void onClick(View view) {
2690        int id = view.getId();
2691        if (VDBG) log("onClick(View " + view + ", id " + id + ")...");
2692
2693        switch (id) {
2694            case R.id.manage_done:  // mButtonManageConferenceDone
2695                if (VDBG) log("onClick: mButtonManageConferenceDone...");
2696                // Hide the Manage Conference panel, return to NORMAL mode.
2697                setInCallScreenMode(InCallScreenMode.NORMAL);
2698                requestUpdateScreen();
2699                break;
2700
2701            case R.id.dim_effect_for_secondary_photo:
2702                if (mInCallControlState.canSwap) {
2703                    internalSwapCalls();
2704                }
2705                break;
2706
2707            default:
2708                // Presumably one of the OTASP-specific buttons managed by
2709                // OtaUtils.java.
2710                // (TODO: It would be cleaner for the OtaUtils instance itself to
2711                // be the OnClickListener for its own buttons.)
2712
2713                if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
2714                     || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
2715                    && mApp.otaUtils != null) {
2716                    mApp.otaUtils.onClickHandler(id);
2717                } else {
2718                    // Uh oh: we *should* only receive clicks here from the
2719                    // buttons managed by OtaUtils.java, but if we're not in one
2720                    // of the special OTASP modes, those buttons shouldn't have
2721                    // been visible in the first place.
2722                    Log.w(LOG_TAG,
2723                          "onClick: unexpected click from ID " + id + " (View = " + view + ")");
2724                }
2725                break;
2726        }
2727
2728        EventLog.writeEvent(EventLogTags.PHONE_UI_BUTTON_CLICK,
2729                (view instanceof TextView) ? ((TextView) view).getText() : "");
2730
2731        // Clicking any onscreen UI element counts as explicit "user activity".
2732        mApp.pokeUserActivity();
2733    }
2734
2735    private void onHoldClick() {
2736        final boolean hasActiveCall = mCM.hasActiveFgCall();
2737        final boolean hasHoldingCall = mCM.hasActiveBgCall();
2738        log("onHoldClick: hasActiveCall = " + hasActiveCall
2739            + ", hasHoldingCall = " + hasHoldingCall);
2740        boolean newHoldState;
2741        boolean holdButtonEnabled;
2742        if (hasActiveCall && !hasHoldingCall) {
2743            // There's only one line in use, and that line is active.
2744            PhoneUtils.switchHoldingAndActive(
2745                mCM.getFirstActiveBgCall());  // Really means "hold" in this state
2746            newHoldState = true;
2747            holdButtonEnabled = true;
2748        } else if (!hasActiveCall && hasHoldingCall) {
2749            // There's only one line in use, and that line is on hold.
2750            PhoneUtils.switchHoldingAndActive(
2751                mCM.getFirstActiveBgCall());  // Really means "unhold" in this state
2752            newHoldState = false;
2753            holdButtonEnabled = true;
2754        } else {
2755            // Either zero or 2 lines are in use; "hold/unhold" is meaningless.
2756            newHoldState = false;
2757            holdButtonEnabled = false;
2758        }
2759        // No need to forcibly update the onscreen UI; just wait for the
2760        // onPhoneStateChanged() callback.  (This seems to be responsive
2761        // enough.)
2762
2763        // Also, any time we hold or unhold, force the DTMF dialpad to close.
2764        closeDialpadInternal(true);  // do the "closing" animation
2765    }
2766
2767    /**
2768     * Toggles in-call audio between speaker and the built-in earpiece (or
2769     * wired headset.)
2770     */
2771    public void toggleSpeaker() {
2772        // TODO: Turning on the speaker seems to enable the mic
2773        //   whether or not the "mute" feature is active!
2774        // Not sure if this is an feature of the telephony API
2775        //   that I need to handle specially, or just a bug.
2776        boolean newSpeakerState = !PhoneUtils.isSpeakerOn(this);
2777        log("toggleSpeaker(): newSpeakerState = " + newSpeakerState);
2778
2779        if (newSpeakerState && isBluetoothAvailable() && isBluetoothAudioConnected()) {
2780            disconnectBluetoothAudio();
2781        }
2782        PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
2783
2784        // And update the InCallTouchUi widget (since the "audio mode"
2785        // button might need to change its appearance based on the new
2786        // audio state.)
2787        updateInCallTouchUi();
2788    }
2789
2790    /*
2791     * onMuteClick is called only when there is a foreground call
2792     */
2793    private void onMuteClick() {
2794        boolean newMuteState = !PhoneUtils.getMute();
2795        log("onMuteClick(): newMuteState = " + newMuteState);
2796        PhoneUtils.setMute(newMuteState);
2797    }
2798
2799    /**
2800     * Toggles whether or not to route in-call audio to the bluetooth
2801     * headset, or do nothing (but log a warning) if no bluetooth device
2802     * is actually connected.
2803     *
2804     * TODO: this method is currently unused, but the "audio mode" UI
2805     * design is still in flux so let's keep it around for now.
2806     * (But if we ultimately end up *not* providing any way for the UI to
2807     * simply "toggle bluetooth", we can get rid of this method.)
2808     */
2809    public void toggleBluetooth() {
2810        if (VDBG) log("toggleBluetooth()...");
2811
2812        if (isBluetoothAvailable()) {
2813            // Toggle the bluetooth audio connection state:
2814            if (isBluetoothAudioConnected()) {
2815                disconnectBluetoothAudio();
2816            } else {
2817                // Manually turn the speaker phone off, instead of allowing the
2818                // Bluetooth audio routing to handle it, since there's other
2819                // important state-updating that needs to happen in the
2820                // PhoneUtils.turnOnSpeaker() method.
2821                // (Similarly, whenever the user turns *on* the speaker, we
2822                // manually disconnect the active bluetooth headset;
2823                // see toggleSpeaker() and/or switchInCallAudio().)
2824                if (PhoneUtils.isSpeakerOn(this)) {
2825                    PhoneUtils.turnOnSpeaker(this, false, true);
2826                }
2827
2828                connectBluetoothAudio();
2829            }
2830        } else {
2831            // Bluetooth isn't available; the onscreen UI shouldn't have
2832            // allowed this request in the first place!
2833            Log.w(LOG_TAG, "toggleBluetooth(): bluetooth is unavailable");
2834        }
2835
2836        // And update the InCallTouchUi widget (since the "audio mode"
2837        // button might need to change its appearance based on the new
2838        // audio state.)
2839        updateInCallTouchUi();
2840    }
2841
2842    /**
2843     * Switches the current routing of in-call audio between speaker,
2844     * bluetooth, and the built-in earpiece (or wired headset.)
2845     *
2846     * This method is used on devices that provide a single 3-way switch
2847     * for audio routing.  For devices that provide separate toggles for
2848     * Speaker and Bluetooth, see toggleBluetooth() and toggleSpeaker().
2849     *
2850     * TODO: UI design is still in flux.  If we end up totally
2851     * eliminating the concept of Speaker and Bluetooth toggle buttons,
2852     * we can get rid of toggleBluetooth() and toggleSpeaker().
2853     */
2854    public void switchInCallAudio(InCallAudioMode newMode) {
2855        log("switchInCallAudio: new mode = " + newMode);
2856        switch (newMode) {
2857            case SPEAKER:
2858                if (!PhoneUtils.isSpeakerOn(this)) {
2859                    // Switch away from Bluetooth, if it was active.
2860                    if (isBluetoothAvailable() && isBluetoothAudioConnected()) {
2861                        disconnectBluetoothAudio();
2862                    }
2863                    PhoneUtils.turnOnSpeaker(this, true, true);
2864                }
2865                break;
2866
2867            case BLUETOOTH:
2868                // If already connected to BT, there's nothing to do here.
2869                if (isBluetoothAvailable() && !isBluetoothAudioConnected()) {
2870                    // Manually turn the speaker phone off, instead of allowing the
2871                    // Bluetooth audio routing to handle it, since there's other
2872                    // important state-updating that needs to happen in the
2873                    // PhoneUtils.turnOnSpeaker() method.
2874                    // (Similarly, whenever the user turns *on* the speaker, we
2875                    // manually disconnect the active bluetooth headset;
2876                    // see toggleSpeaker() and/or switchInCallAudio().)
2877                    if (PhoneUtils.isSpeakerOn(this)) {
2878                        PhoneUtils.turnOnSpeaker(this, false, true);
2879                    }
2880                    connectBluetoothAudio();
2881                }
2882                break;
2883
2884            case EARPIECE:
2885                // Switch to either the handset earpiece, or the wired headset (if connected.)
2886                // (Do this by simply making sure both speaker and bluetooth are off.)
2887                if (isBluetoothAvailable() && isBluetoothAudioConnected()) {
2888                    disconnectBluetoothAudio();
2889                }
2890                if (PhoneUtils.isSpeakerOn(this)) {
2891                    PhoneUtils.turnOnSpeaker(this, false, true);
2892                }
2893                break;
2894
2895            default:
2896                Log.wtf(LOG_TAG, "switchInCallAudio: unexpected mode " + newMode);
2897                break;
2898        }
2899
2900        // And finally, update the InCallTouchUi widget (since the "audio
2901        // mode" button might need to change its appearance based on the
2902        // new audio state.)
2903        updateInCallTouchUi();
2904    }
2905
2906    /**
2907     * Handle a click on the "Open/Close dialpad" button.
2908     *
2909     * @see DTMFTwelveKeyDialer#openDialer(boolean)
2910     * @see DTMFTwelveKeyDialer#closeDialer(boolean)
2911     */
2912    private void onOpenCloseDialpad() {
2913        if (VDBG) log("onOpenCloseDialpad()...");
2914        if (mDialer.isOpened()) {
2915            closeDialpadInternal(true);  // do the "closing" animation
2916        } else {
2917            openDialpadInternal(true);  // do the "opening" animation
2918        }
2919        mApp.updateProximitySensorMode(mCM.getState());
2920    }
2921
2922    /** Internal wrapper around {@link DTMFTwelveKeyDialer#openDialer(boolean)} */
2923    private void openDialpadInternal(boolean animate) {
2924        mDialer.openDialer(animate);
2925        // And update the InCallUiState (so that we'll restore the dialpad
2926        // to the correct state if we get paused/resumed).
2927        mApp.inCallUiState.showDialpad = true;
2928    }
2929
2930    // Internal wrapper around DTMFTwelveKeyDialer.closeDialer()
2931    private void closeDialpadInternal(boolean animate) {
2932        mDialer.closeDialer(animate);
2933        // And update the InCallUiState (so that we'll restore the dialpad
2934        // to the correct state if we get paused/resumed).
2935        mApp.inCallUiState.showDialpad = false;
2936    }
2937
2938    /**
2939     * Handles button clicks from the InCallTouchUi widget.
2940     */
2941    /* package */ void handleOnscreenButtonClick(int id) {
2942        if (DBG) log("handleOnscreenButtonClick(id " + id + ")...");
2943
2944        switch (id) {
2945            // Actions while an incoming call is ringing:
2946            case R.id.incomingCallAnswer:
2947                internalAnswerCall();
2948                break;
2949            case R.id.incomingCallReject:
2950                hangupRingingCall();
2951                break;
2952            case R.id.incomingCallRespondViaSms:
2953                internalRespondViaSms();
2954                break;
2955
2956            // The other regular (single-tap) buttons used while in-call:
2957            case R.id.holdButton:
2958                onHoldClick();
2959                break;
2960            case R.id.swapButton:
2961                internalSwapCalls();
2962                break;
2963            case R.id.endButton:
2964                internalHangup();
2965                break;
2966            case R.id.dialpadButton:
2967                onOpenCloseDialpad();
2968                break;
2969            case R.id.muteButton:
2970                onMuteClick();
2971                break;
2972            case R.id.addButton:
2973                PhoneUtils.startNewCall(mCM);  // Fires off an ACTION_DIAL intent
2974                break;
2975            case R.id.mergeButton:
2976            case R.id.cdmaMergeButton:
2977                PhoneUtils.mergeCalls(mCM);
2978                break;
2979            case R.id.manageConferenceButton:
2980                // Show the Manage Conference panel.
2981                setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
2982                requestUpdateScreen();
2983                break;
2984
2985            default:
2986                Log.w(LOG_TAG, "handleOnscreenButtonClick: unexpected ID " + id);
2987                break;
2988        }
2989
2990        // Clicking any onscreen UI element counts as explicit "user activity".
2991        mApp.pokeUserActivity();
2992
2993        // Just in case the user clicked a "stateful" UI element (like one
2994        // of the toggle buttons), we force the in-call buttons to update,
2995        // to make sure the user sees the *new* current state.
2996        //
2997        // Note that some in-call buttons will *not* immediately change the
2998        // state of the UI, namely those that send a request to the telephony
2999        // layer (like "Hold" or "End call".)  For those buttons, the
3000        // updateInCallTouchUi() call here won't have any visible effect.
3001        // Instead, the UI will be updated eventually when the next
3002        // onPhoneStateChanged() event comes in and triggers an updateScreen()
3003        // call.
3004        //
3005        // TODO: updateInCallTouchUi() is overkill here; it would be
3006        // more efficient to update *only* the affected button(s).
3007        // (But this isn't a big deal since updateInCallTouchUi() is pretty
3008        // cheap anyway...)
3009        updateInCallTouchUi();
3010    }
3011
3012    /**
3013     * Display a status or error indication to the user according to the
3014     * specified InCallUiState.CallStatusCode value.
3015     */
3016    private void showStatusIndication(CallStatusCode status) {
3017        switch (status) {
3018            case SUCCESS:
3019                // The InCallScreen does not need to display any kind of error indication,
3020                // so we shouldn't have gotten here in the first place.
3021                Log.wtf(LOG_TAG, "showStatusIndication: nothing to display");
3022                break;
3023
3024            case POWER_OFF:
3025                // Radio is explictly powered off, presumably because the
3026                // device is in airplane mode.
3027                //
3028                // TODO: For now this UI is ultra-simple: we simply display
3029                // a message telling the user to turn off airplane mode.
3030                // But it might be nicer for the dialog to offer the option
3031                // to turn the radio on right there (and automatically retry
3032                // the call once network registration is complete.)
3033                showGenericErrorDialog(R.string.incall_error_power_off,
3034                                       true /* isStartupError */);
3035                break;
3036
3037            case EMERGENCY_ONLY:
3038                // Only emergency numbers are allowed, but we tried to dial
3039                // a non-emergency number.
3040                // (This state is currently unused; see comments above.)
3041                showGenericErrorDialog(R.string.incall_error_emergency_only,
3042                                       true /* isStartupError */);
3043                break;
3044
3045            case OUT_OF_SERVICE:
3046                // No network connection.
3047                showGenericErrorDialog(R.string.incall_error_out_of_service,
3048                                       true /* isStartupError */);
3049                break;
3050
3051            case NO_PHONE_NUMBER_SUPPLIED:
3052                // The supplied Intent didn't contain a valid phone number.
3053                // (This is rare and should only ever happen with broken
3054                // 3rd-party apps.)  For now just show a generic error.
3055                showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied,
3056                                       true /* isStartupError */);
3057                break;
3058
3059            case DIALED_MMI:
3060                // Our initial phone number was actually an MMI sequence.
3061                // There's no real "error" here, but we do bring up the
3062                // a Toast (as requested of the New UI paradigm).
3063                //
3064                // In-call MMIs do not trigger the normal MMI Initiate
3065                // Notifications, so we should notify the user here.
3066                // Otherwise, the code in PhoneUtils.java should handle
3067                // user notifications in the form of Toasts or Dialogs.
3068                if (mCM.getState() == PhoneConstants.State.OFFHOOK) {
3069                    Toast.makeText(mApp, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
3070                            .show();
3071                }
3072                break;
3073
3074            case CALL_FAILED:
3075                // We couldn't successfully place the call; there was some
3076                // failure in the telephony layer.
3077                // TODO: Need UI spec for this failure case; for now just
3078                // show a generic error.
3079                showGenericErrorDialog(R.string.incall_error_call_failed,
3080                                       true /* isStartupError */);
3081                break;
3082
3083            case VOICEMAIL_NUMBER_MISSING:
3084                // We tried to call a voicemail: URI but the device has no
3085                // voicemail number configured.
3086                handleMissingVoiceMailNumber();
3087                break;
3088
3089            case CDMA_CALL_LOST:
3090                // This status indicates that InCallScreen should display the
3091                // CDMA-specific "call lost" dialog.  (If an outgoing call fails,
3092                // and the CDMA "auto-retry" feature is enabled, *and* the retried
3093                // call fails too, we display this specific dialog.)
3094                //
3095                // TODO: currently unused; see InCallUiState.needToShowCallLostDialog
3096                break;
3097
3098            case EXITED_ECM:
3099                // This status indicates that InCallScreen needs to display a
3100                // warning that we're exiting ECM (emergency callback mode).
3101                showExitingECMDialog();
3102                break;
3103
3104            default:
3105                throw new IllegalStateException(
3106                    "showStatusIndication: unexpected status code: " + status);
3107        }
3108
3109        // TODO: still need to make sure that pressing OK or BACK from
3110        // *any* of the dialogs we launch here ends up calling
3111        // inCallUiState.clearPendingCallStatusCode()
3112        //  *and*
3113        // make sure the Dialog handles both OK *and* cancel by calling
3114        // endInCallScreenSession.  (See showGenericErrorDialog() for an
3115        // example.)
3116        //
3117        // (showGenericErrorDialog() currently does this correctly,
3118        // but handleMissingVoiceMailNumber() probably needs to be fixed too.)
3119        //
3120        // Also need to make sure that bailing out of any of these dialogs by
3121        // pressing Home clears out the pending status code too.  (If you do
3122        // that, neither the dialog's clickListener *or* cancelListener seems
3123        // to run...)
3124    }
3125
3126    /**
3127     * Utility function to bring up a generic "error" dialog, and then bail
3128     * out of the in-call UI when the user hits OK (or the BACK button.)
3129     */
3130    private void showGenericErrorDialog(int resid, boolean isStartupError) {
3131        CharSequence msg = getResources().getText(resid);
3132        if (DBG) log("showGenericErrorDialog('" + msg + "')...");
3133
3134        // create the clicklistener and cancel listener as needed.
3135        DialogInterface.OnClickListener clickListener;
3136        OnCancelListener cancelListener;
3137        if (isStartupError) {
3138            clickListener = new DialogInterface.OnClickListener() {
3139                public void onClick(DialogInterface dialog, int which) {
3140                    bailOutAfterErrorDialog();
3141                }};
3142            cancelListener = new OnCancelListener() {
3143                public void onCancel(DialogInterface dialog) {
3144                    bailOutAfterErrorDialog();
3145                }};
3146        } else {
3147            clickListener = new DialogInterface.OnClickListener() {
3148                public void onClick(DialogInterface dialog, int which) {
3149                    delayedCleanupAfterDisconnect();
3150                }};
3151            cancelListener = new OnCancelListener() {
3152                public void onCancel(DialogInterface dialog) {
3153                    delayedCleanupAfterDisconnect();
3154                }};
3155        }
3156
3157        // TODO: Consider adding a setTitle() call here (with some generic
3158        // "failure" title?)
3159        mGenericErrorDialog = new AlertDialog.Builder(this)
3160                .setMessage(msg)
3161                .setPositiveButton(R.string.ok, clickListener)
3162                .setOnCancelListener(cancelListener)
3163                .create();
3164
3165        // When the dialog is up, completely hide the in-call UI
3166        // underneath (which is in a partially-constructed state).
3167        mGenericErrorDialog.getWindow().addFlags(
3168                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
3169
3170        mGenericErrorDialog.show();
3171    }
3172
3173    private void showCallLostDialog() {
3174        if (DBG) log("showCallLostDialog()...");
3175
3176        // Don't need to show the dialog if InCallScreen isn't in the forgeround
3177        if (!mIsForegroundActivity) {
3178            if (DBG) log("showCallLostDialog: not the foreground Activity! Bailing out...");
3179            return;
3180        }
3181
3182        // Don't need to show the dialog again, if there is one already.
3183        if (mCallLostDialog != null) {
3184            if (DBG) log("showCallLostDialog: There is a mCallLostDialog already.");
3185            return;
3186        }
3187
3188        mCallLostDialog = new AlertDialog.Builder(this)
3189                .setMessage(R.string.call_lost)
3190                .setIconAttribute(android.R.attr.alertDialogIcon)
3191                .create();
3192        mCallLostDialog.show();
3193    }
3194
3195    /**
3196     * Displays the "Exiting ECM" warning dialog.
3197     *
3198     * Background: If the phone is currently in ECM (Emergency callback
3199     * mode) and we dial a non-emergency number, that automatically
3200     * *cancels* ECM.  (That behavior comes from CdmaCallTracker.dial().)
3201     * When that happens, we need to warn the user that they're no longer
3202     * in ECM (bug 4207607.)
3203     *
3204     * So bring up a dialog explaining what's happening.  There's nothing
3205     * for the user to do, by the way; we're simply providing an
3206     * indication that they're exiting ECM.  We *could* use a Toast for
3207     * this, but toasts are pretty easy to miss, so instead use a dialog
3208     * with a single "OK" button.
3209     *
3210     * TODO: it's ugly that the code here has to make assumptions about
3211     *   the behavior of the telephony layer (namely that dialing a
3212     *   non-emergency number while in ECM causes us to exit ECM.)
3213     *
3214     *   Instead, this warning dialog should really be triggered by our
3215     *   handler for the
3216     *   TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED intent in
3217     *   PhoneApp.java.  But that won't work until that intent also
3218     *   includes a *reason* why we're exiting ECM, since we need to
3219     *   display this dialog when exiting ECM because of an outgoing call,
3220     *   but NOT if we're exiting ECM because the user manually turned it
3221     *   off via the EmergencyCallbackModeExitDialog.
3222     *
3223     *   Or, it might be simpler to just have outgoing non-emergency calls
3224     *   *not* cancel ECM.  That way the UI wouldn't have to do anything
3225     *   special here.
3226     */
3227    private void showExitingECMDialog() {
3228        Log.i(LOG_TAG, "showExitingECMDialog()...");
3229
3230        if (mExitingECMDialog != null) {
3231            if (DBG) log("- DISMISSING mExitingECMDialog.");
3232            mExitingECMDialog.dismiss();  // safe even if already dismissed
3233            mExitingECMDialog = null;
3234        }
3235
3236        // When the user dismisses the "Exiting ECM" dialog, we clear out
3237        // the pending call status code field (since we're done with this
3238        // dialog), but do *not* bail out of the InCallScreen.
3239
3240        final InCallUiState inCallUiState = mApp.inCallUiState;
3241        DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
3242                public void onClick(DialogInterface dialog, int which) {
3243                    inCallUiState.clearPendingCallStatusCode();
3244                }};
3245        OnCancelListener cancelListener = new OnCancelListener() {
3246                public void onCancel(DialogInterface dialog) {
3247                    inCallUiState.clearPendingCallStatusCode();
3248                }};
3249
3250        // Ultra-simple AlertDialog with only an OK button:
3251        mExitingECMDialog = new AlertDialog.Builder(this)
3252                .setMessage(R.string.progress_dialog_exiting_ecm)
3253                .setPositiveButton(R.string.ok, clickListener)
3254                .setOnCancelListener(cancelListener)
3255                .create();
3256        mExitingECMDialog.getWindow().addFlags(
3257                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
3258        mExitingECMDialog.show();
3259    }
3260
3261    private void bailOutAfterErrorDialog() {
3262        if (mGenericErrorDialog != null) {
3263            if (DBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
3264            mGenericErrorDialog.dismiss();
3265            mGenericErrorDialog = null;
3266        }
3267        if (DBG) log("bailOutAfterErrorDialog(): end InCallScreen session...");
3268
3269        // Now that the user has dismissed the error dialog (presumably by
3270        // either hitting the OK button or pressing Back, we can now reset
3271        // the pending call status code field.
3272        //
3273        // (Note that the pending call status is NOT cleared simply
3274        // by the InCallScreen being paused or finished, since the resulting
3275        // dialog is supposed to persist across orientation changes or if the
3276        // screen turns off.)
3277        //
3278        // See the "Error / diagnostic indications" section of
3279        // InCallUiState.java for more detailed info about the
3280        // pending call status code field.
3281        final InCallUiState inCallUiState = mApp.inCallUiState;
3282        inCallUiState.clearPendingCallStatusCode();
3283
3284        // Force the InCallScreen to truly finish(), rather than just
3285        // moving it to the back of the activity stack (which is what
3286        // our finish() method usually does.)
3287        // This is necessary to avoid an obscure scenario where the
3288        // InCallScreen can get stuck in an inconsistent state, somehow
3289        // causing a *subsequent* outgoing call to fail (bug 4172599).
3290        endInCallScreenSession(true /* force a real finish() call */);
3291    }
3292
3293    /**
3294     * Dismisses (and nulls out) all persistent Dialogs managed
3295     * by the InCallScreen.  Useful if (a) we're about to bring up
3296     * a dialog and want to pre-empt any currently visible dialogs,
3297     * or (b) as a cleanup step when the Activity is going away.
3298     */
3299    private void dismissAllDialogs() {
3300        if (DBG) log("dismissAllDialogs()...");
3301
3302        // Note it's safe to dismiss() a dialog that's already dismissed.
3303        // (Even if the AlertDialog object(s) below are still around, it's
3304        // possible that the actual dialog(s) may have already been
3305        // dismissed by the user.)
3306
3307        if (mMissingVoicemailDialog != null) {
3308            if (VDBG) log("- DISMISSING mMissingVoicemailDialog.");
3309            mMissingVoicemailDialog.dismiss();
3310            mMissingVoicemailDialog = null;
3311        }
3312        if (mMmiStartedDialog != null) {
3313            if (VDBG) log("- DISMISSING mMmiStartedDialog.");
3314            mMmiStartedDialog.dismiss();
3315            mMmiStartedDialog = null;
3316        }
3317        if (mGenericErrorDialog != null) {
3318            if (VDBG) log("- DISMISSING mGenericErrorDialog.");
3319            mGenericErrorDialog.dismiss();
3320            mGenericErrorDialog = null;
3321        }
3322        if (mSuppServiceFailureDialog != null) {
3323            if (VDBG) log("- DISMISSING mSuppServiceFailureDialog.");
3324            mSuppServiceFailureDialog.dismiss();
3325            mSuppServiceFailureDialog = null;
3326        }
3327        if (mWaitPromptDialog != null) {
3328            if (VDBG) log("- DISMISSING mWaitPromptDialog.");
3329            mWaitPromptDialog.dismiss();
3330            mWaitPromptDialog = null;
3331        }
3332        if (mWildPromptDialog != null) {
3333            if (VDBG) log("- DISMISSING mWildPromptDialog.");
3334            mWildPromptDialog.dismiss();
3335            mWildPromptDialog = null;
3336        }
3337        if (mCallLostDialog != null) {
3338            if (VDBG) log("- DISMISSING mCallLostDialog.");
3339            mCallLostDialog.dismiss();
3340            mCallLostDialog = null;
3341        }
3342        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
3343                || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
3344                && mApp.otaUtils != null) {
3345            mApp.otaUtils.dismissAllOtaDialogs();
3346        }
3347        if (mPausePromptDialog != null) {
3348            if (DBG) log("- DISMISSING mPausePromptDialog.");
3349            mPausePromptDialog.dismiss();
3350            mPausePromptDialog = null;
3351        }
3352        if (mExitingECMDialog != null) {
3353            if (DBG) log("- DISMISSING mExitingECMDialog.");
3354            mExitingECMDialog.dismiss();
3355            mExitingECMDialog = null;
3356        }
3357    }
3358
3359    /**
3360     * Updates the state of the onscreen "progress indication" used in
3361     * some (relatively rare) scenarios where we need to wait for
3362     * something to happen before enabling the in-call UI.
3363     *
3364     * If necessary, this method will cause a ProgressDialog (i.e. a
3365     * spinning wait cursor) to be drawn *on top of* whatever the current
3366     * state of the in-call UI is.
3367     *
3368     * @see InCallUiState.ProgressIndicationType
3369     */
3370    private void updateProgressIndication() {
3371        // If an incoming call is ringing, that takes priority over any
3372        // possible value of inCallUiState.progressIndication.
3373        if (mCM.hasActiveRingingCall()) {
3374            dismissProgressIndication();
3375            return;
3376        }
3377
3378        // Otherwise, put up a progress indication if indicated by the
3379        // inCallUiState.progressIndication field.
3380        final InCallUiState inCallUiState = mApp.inCallUiState;
3381        switch (inCallUiState.getProgressIndication()) {
3382            case NONE:
3383                // No progress indication necessary, so make sure it's dismissed.
3384                dismissProgressIndication();
3385                break;
3386
3387            case TURNING_ON_RADIO:
3388                showProgressIndication(
3389                    R.string.emergency_enable_radio_dialog_title,
3390                    R.string.emergency_enable_radio_dialog_message);
3391                break;
3392
3393            case RETRYING:
3394                showProgressIndication(
3395                    R.string.emergency_enable_radio_dialog_title,
3396                    R.string.emergency_enable_radio_dialog_retry);
3397                break;
3398
3399            default:
3400                Log.wtf(LOG_TAG, "updateProgressIndication: unexpected value: "
3401                        + inCallUiState.getProgressIndication());
3402                dismissProgressIndication();
3403                break;
3404        }
3405    }
3406
3407    /**
3408     * Show an onscreen "progress indication" with the specified title and message.
3409     */
3410    private void showProgressIndication(int titleResId, int messageResId) {
3411        if (DBG) log("showProgressIndication(message " + messageResId + ")...");
3412
3413        // TODO: make this be a no-op if the progress indication is
3414        // already visible with the exact same title and message.
3415
3416        dismissProgressIndication();  // Clean up any prior progress indication
3417        mProgressDialog = new ProgressDialog(this);
3418        mProgressDialog.setTitle(getText(titleResId));
3419        mProgressDialog.setMessage(getText(messageResId));
3420        mProgressDialog.setIndeterminate(true);
3421        mProgressDialog.setCancelable(false);
3422        mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
3423        mProgressDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
3424        mProgressDialog.show();
3425    }
3426
3427    /**
3428     * Dismiss the onscreen "progress indication" (if present).
3429     */
3430    private void dismissProgressIndication() {
3431        if (DBG) log("dismissProgressIndication()...");
3432        if (mProgressDialog != null) {
3433            mProgressDialog.dismiss();  // safe even if already dismissed
3434            mProgressDialog = null;
3435        }
3436    }
3437
3438
3439    //
3440    // Helper functions for answering incoming calls.
3441    //
3442
3443    /**
3444     * Answer a ringing call.  This method does nothing if there's no
3445     * ringing or waiting call.
3446     */
3447    private void internalAnswerCall() {
3448        if (DBG) log("internalAnswerCall()...");
3449        // if (DBG) PhoneUtils.dumpCallState(mPhone);
3450
3451        final boolean hasRingingCall = mCM.hasActiveRingingCall();
3452
3453        if (hasRingingCall) {
3454            Phone phone = mCM.getRingingPhone();
3455            Call ringing = mCM.getFirstActiveRingingCall();
3456            int phoneType = phone.getPhoneType();
3457            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
3458                if (DBG) log("internalAnswerCall: answering (CDMA)...");
3459                if (mCM.hasActiveFgCall()
3460                        && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
3461                    // The incoming call is CDMA call and the ongoing
3462                    // call is a SIP call. The CDMA network does not
3463                    // support holding an active call, so there's no
3464                    // way to swap between a CDMA call and a SIP call.
3465                    // So for now, we just don't allow a CDMA call and
3466                    // a SIP call to be active at the same time.We'll
3467                    // "answer incoming, end ongoing" in this case.
3468                    if (DBG) log("internalAnswerCall: answer "
3469                            + "CDMA incoming and end SIP ongoing");
3470                    PhoneUtils.answerAndEndActive(mCM, ringing);
3471                } else {
3472                    PhoneUtils.answerCall(ringing);
3473                }
3474            } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
3475                if (DBG) log("internalAnswerCall: answering (SIP)...");
3476                if (mCM.hasActiveFgCall()
3477                        && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
3478                    // Similar to the PHONE_TYPE_CDMA handling.
3479                    // The incoming call is SIP call and the ongoing
3480                    // call is a CDMA call. The CDMA network does not
3481                    // support holding an active call, so there's no
3482                    // way to swap between a CDMA call and a SIP call.
3483                    // So for now, we just don't allow a CDMA call and
3484                    // a SIP call to be active at the same time.We'll
3485                    // "answer incoming, end ongoing" in this case.
3486                    if (DBG) log("internalAnswerCall: answer "
3487                            + "SIP incoming and end CDMA ongoing");
3488                    PhoneUtils.answerAndEndActive(mCM, ringing);
3489                } else {
3490                    PhoneUtils.answerCall(ringing);
3491                }
3492            } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
3493                if (DBG) log("internalAnswerCall: answering (GSM)...");
3494                // GSM: this is usually just a wrapper around
3495                // PhoneUtils.answerCall(), *but* we also need to do
3496                // something special for the "both lines in use" case.
3497
3498                final boolean hasActiveCall = mCM.hasActiveFgCall();
3499                final boolean hasHoldingCall = mCM.hasActiveBgCall();
3500
3501                if (hasActiveCall && hasHoldingCall) {
3502                    if (DBG) log("internalAnswerCall: answering (both lines in use!)...");
3503                    // The relatively rare case where both lines are
3504                    // already in use.  We "answer incoming, end ongoing"
3505                    // in this case, according to the current UI spec.
3506                    PhoneUtils.answerAndEndActive(mCM, ringing);
3507
3508                    // Alternatively, we could use
3509                    // PhoneUtils.answerAndEndHolding(mPhone);
3510                    // here to end the on-hold call instead.
3511                } else {
3512                    if (DBG) log("internalAnswerCall: answering...");
3513                    PhoneUtils.answerCall(ringing);  // Automatically holds the current active call,
3514                                                    // if there is one
3515                }
3516            } else {
3517                throw new IllegalStateException("Unexpected phone type: " + phoneType);
3518            }
3519
3520            // Call origin is valid only with outgoing calls. Disable it on incoming calls.
3521            mApp.setLatestActiveCallOrigin(null);
3522        }
3523    }
3524
3525    /**
3526     * Answer the ringing call *and* hang up the ongoing call.
3527     */
3528    private void internalAnswerAndEnd() {
3529        if (DBG) log("internalAnswerAndEnd()...");
3530        if (VDBG) PhoneUtils.dumpCallManager();
3531        // In the rare case when multiple calls are ringing, the UI policy
3532        // it to always act on the first ringing call.
3533        PhoneUtils.answerAndEndActive(mCM, mCM.getFirstActiveRingingCall());
3534    }
3535
3536    /**
3537     * Hang up the ringing call (aka "Don't answer").
3538     */
3539    /* package */ void hangupRingingCall() {
3540        if (DBG) log("hangupRingingCall()...");
3541        if (VDBG) PhoneUtils.dumpCallManager();
3542        // In the rare case when multiple calls are ringing, the UI policy
3543        // it to always act on the first ringing call.
3544        PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
3545    }
3546
3547    /**
3548     * Silence the ringer (if an incoming call is ringing.)
3549     */
3550    private void internalSilenceRinger() {
3551        if (DBG) log("internalSilenceRinger()...");
3552        final CallNotifier notifier = mApp.notifier;
3553        if (notifier.isRinging()) {
3554            // ringer is actually playing, so silence it.
3555            notifier.silenceRinger();
3556        }
3557    }
3558
3559    /**
3560     * Respond via SMS to the ringing call.
3561     * @see RespondViaSmsManager
3562     */
3563    private void internalRespondViaSms() {
3564        log("internalRespondViaSms()...");
3565        if (VDBG) PhoneUtils.dumpCallManager();
3566
3567        // In the rare case when multiple calls are ringing, the UI policy
3568        // it to always act on the first ringing call.
3569        Call ringingCall = mCM.getFirstActiveRingingCall();
3570
3571        mRespondViaSmsManager.showRespondViaSmsPopup(ringingCall);
3572
3573        // Silence the ringer, since it would be distracting while you're trying
3574        // to pick a response.  (Note that we'll restart the ringer if you bail
3575        // out of the popup, though; see RespondViaSmsCancelListener.)
3576        internalSilenceRinger();
3577    }
3578
3579    /**
3580     * Hang up the current active call.
3581     */
3582    private void internalHangup() {
3583        PhoneConstants.State state = mCM.getState();
3584        log("internalHangup()...  phone state = " + state);
3585
3586        // Regardless of the phone state, issue a hangup request.
3587        // (If the phone is already idle, this call will presumably have no
3588        // effect (but also see the note below.))
3589        PhoneUtils.hangup(mCM);
3590
3591        // If the user just hung up the only active call, we'll eventually exit
3592        // the in-call UI after the following sequence:
3593        // - When the hangup() succeeds, we'll get a DISCONNECT event from
3594        //   the telephony layer (see onDisconnect()).
3595        // - We immediately switch to the "Call ended" state (see the "delayed
3596        //   bailout" code path in onDisconnect()) and also post a delayed
3597        //   DELAYED_CLEANUP_AFTER_DISCONNECT message.
3598        // - When the DELAYED_CLEANUP_AFTER_DISCONNECT message comes in (see
3599        //   delayedCleanupAfterDisconnect()) we do some final cleanup, and exit
3600        //   this activity unless the phone is still in use (i.e. if there's
3601        //   another call, or something else going on like an active MMI
3602        //   sequence.)
3603
3604        if (state == PhoneConstants.State.IDLE) {
3605            // The user asked us to hang up, but the phone was (already) idle!
3606            Log.w(LOG_TAG, "internalHangup(): phone is already IDLE!");
3607
3608            // This is rare, but can happen in a few cases:
3609            // (a) If the user quickly double-taps the "End" button.  In this case
3610            //   we'll see that 2nd press event during the brief "Call ended"
3611            //   state (where the phone is IDLE), or possibly even before the
3612            //   radio has been able to respond to the initial hangup request.
3613            // (b) More rarely, this can happen if the user presses "End" at the
3614            //   exact moment that the call ends on its own (like because of the
3615            //   other person hanging up.)
3616            // (c) Finally, this could also happen if we somehow get stuck here on
3617            //   the InCallScreen with the phone truly idle, perhaps due to a
3618            //   bug where we somehow *didn't* exit when the phone became idle
3619            //   in the first place.
3620
3621            // TODO: as a "safety valve" for case (c), consider immediately
3622            // bailing out of the in-call UI right here.  (The user can always
3623            // bail out by pressing Home, of course, but they'll probably try
3624            // pressing End first.)
3625            //
3626            //    Log.i(LOG_TAG, "internalHangup(): phone is already IDLE!  Bailing out...");
3627            //    endInCallScreenSession();
3628        }
3629    }
3630
3631    /**
3632     * InCallScreen-specific wrapper around PhoneUtils.switchHoldingAndActive().
3633     */
3634    private void internalSwapCalls() {
3635        if (DBG) log("internalSwapCalls()...");
3636
3637        // Any time we swap calls, force the DTMF dialpad to close.
3638        // (We want the regular in-call UI to be visible right now, so the
3639        // user can clearly see which call is now in the foreground.)
3640        closeDialpadInternal(true);  // do the "closing" animation
3641
3642        // Also, clear out the "history" of DTMF digits you typed, to make
3643        // sure you don't see digits from call #1 while call #2 is active.
3644        // (Yes, this does mean that swapping calls twice will cause you
3645        // to lose any previous digits from the current call; see the TODO
3646        // comment on DTMFTwelvKeyDialer.clearDigits() for more info.)
3647        mDialer.clearDigits();
3648
3649        // Swap the fg and bg calls.
3650        // In the future we may provides some way for user to choose among
3651        // multiple background calls, for now, always act on the first background calll.
3652        PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
3653
3654        // If we have a valid BluetoothPhoneService then since CDMA network or
3655        // Telephony FW does not send us information on which caller got swapped
3656        // we need to update the second call active state in BluetoothPhoneService internally
3657        if (mCM.getBgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
3658            IBluetoothHeadsetPhone btPhone = mApp.getBluetoothPhoneService();
3659            if (btPhone != null) {
3660                try {
3661                    btPhone.cdmaSwapSecondCallState();
3662                } catch (RemoteException e) {
3663                    Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
3664                }
3665            }
3666        }
3667
3668    }
3669
3670    /**
3671     * Sets the current high-level "mode" of the in-call UI.
3672     *
3673     * NOTE: if newMode is CALL_ENDED, the caller is responsible for
3674     * posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
3675     * sure the "call ended" state goes away after a couple of seconds.
3676     *
3677     * Note this method does NOT refresh of the onscreen UI; the caller is
3678     * responsible for calling updateScreen() or requestUpdateScreen() if
3679     * necessary.
3680     */
3681    private void setInCallScreenMode(InCallScreenMode newMode) {
3682        if (DBG) log("setInCallScreenMode: " + newMode);
3683        mApp.inCallUiState.inCallScreenMode = newMode;
3684
3685        switch (newMode) {
3686            case MANAGE_CONFERENCE:
3687                if (!PhoneUtils.isConferenceCall(mCM.getActiveFgCall())) {
3688                    Log.w(LOG_TAG, "MANAGE_CONFERENCE: no active conference call!");
3689                    // Hide the Manage Conference panel, return to NORMAL mode.
3690                    setInCallScreenMode(InCallScreenMode.NORMAL);
3691                    return;
3692                }
3693                List<Connection> connections = mCM.getFgCallConnections();
3694                // There almost certainly will be > 1 connection,
3695                // since isConferenceCall() just returned true.
3696                if ((connections == null) || (connections.size() <= 1)) {
3697                    Log.w(LOG_TAG,
3698                          "MANAGE_CONFERENCE: Bogus TRUE from isConferenceCall(); connections = "
3699                          + connections);
3700                    // Hide the Manage Conference panel, return to NORMAL mode.
3701                    setInCallScreenMode(InCallScreenMode.NORMAL);
3702                    return;
3703                }
3704
3705                // TODO: Don't do this here. The call to
3706                // initManageConferencePanel() should instead happen
3707                // automagically in ManageConferenceUtils the very first
3708                // time you call updateManageConferencePanel() or
3709                // setPanelVisible(true).
3710                mManageConferenceUtils.initManageConferencePanel();  // if necessary
3711
3712                mManageConferenceUtils.updateManageConferencePanel(connections);
3713
3714                // The "Manage conference" UI takes up the full main frame,
3715                // replacing the CallCard PopupWindow.
3716                mManageConferenceUtils.setPanelVisible(true);
3717
3718                // Start the chronometer.
3719                // TODO: Similarly, we shouldn't expose startConferenceTime()
3720                // and stopConferenceTime(); the ManageConferenceUtils
3721                // class ought to manage the conferenceTime widget itself
3722                // based on setPanelVisible() calls.
3723
3724                // Note: there is active Fg call since we are in conference call
3725                long callDuration =
3726                        mCM.getActiveFgCall().getEarliestConnection().getDurationMillis();
3727                mManageConferenceUtils.startConferenceTime(
3728                        SystemClock.elapsedRealtime() - callDuration);
3729
3730                // No need to close the dialer here, since the Manage
3731                // Conference UI will just cover it up anyway.
3732
3733                break;
3734
3735            case CALL_ENDED:
3736            case NORMAL:
3737                mManageConferenceUtils.setPanelVisible(false);
3738                mManageConferenceUtils.stopConferenceTime();
3739                break;
3740
3741            case OTA_NORMAL:
3742                mApp.otaUtils.setCdmaOtaInCallScreenUiState(
3743                        OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL);
3744                break;
3745
3746            case OTA_ENDED:
3747                mApp.otaUtils.setCdmaOtaInCallScreenUiState(
3748                        OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
3749                break;
3750
3751            case UNDEFINED:
3752                // Set our Activities intent to ACTION_UNDEFINED so
3753                // that if we get resumed after we've completed a call
3754                // the next call will not cause checkIsOtaCall to
3755                // return true.
3756                //
3757                // TODO(OTASP): update these comments
3758                //
3759                // With the framework as of October 2009 the sequence below
3760                // causes the framework to call onResume, onPause, onNewIntent,
3761                // onResume. If we don't call setIntent below then when the
3762                // first onResume calls checkIsOtaCall via checkOtaspStateOnResume it will
3763                // return true and the Activity will be confused.
3764                //
3765                //  1) Power up Phone A
3766                //  2) Place *22899 call and activate Phone A
3767                //  3) Press the power key on Phone A to turn off the display
3768                //  4) Call Phone A from Phone B answering Phone A
3769                //  5) The screen will be blank (Should be normal InCallScreen)
3770                //  6) Hang up the Phone B
3771                //  7) Phone A displays the activation screen.
3772                //
3773                // Step 3 is the critical step to cause the onResume, onPause
3774                // onNewIntent, onResume sequence. If step 3 is skipped the
3775                // sequence will be onNewIntent, onResume and all will be well.
3776                setIntent(new Intent(ACTION_UNDEFINED));
3777
3778                // Cleanup Ota Screen if necessary and set the panel
3779                // to VISIBLE.
3780                if (mCM.getState() != PhoneConstants.State.OFFHOOK) {
3781                    if (mApp.otaUtils != null) {
3782                        mApp.otaUtils.cleanOtaScreen(true);
3783                    }
3784                } else {
3785                    log("WARNING: Setting mode to UNDEFINED but phone is OFFHOOK,"
3786                            + " skip cleanOtaScreen.");
3787                }
3788                break;
3789        }
3790    }
3791
3792    /**
3793     * @return true if the "Manage conference" UI is currently visible.
3794     */
3795    /* package */ boolean isManageConferenceMode() {
3796        return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE);
3797    }
3798
3799    /**
3800     * Checks if the "Manage conference" UI needs to be updated.
3801     * If the state of the current conference call has changed
3802     * since our previous call to updateManageConferencePanel()),
3803     * do a fresh update.  Also, if the current call is no longer a
3804     * conference call at all, bail out of the "Manage conference" UI and
3805     * return to InCallScreenMode.NORMAL mode.
3806     */
3807    private void updateManageConferencePanelIfNecessary() {
3808        if (VDBG) log("updateManageConferencePanelIfNecessary: " + mCM.getActiveFgCall() + "...");
3809
3810        List<Connection> connections = mCM.getFgCallConnections();
3811        if (connections == null) {
3812            if (VDBG) log("==> no connections on foreground call!");
3813            // Hide the Manage Conference panel, return to NORMAL mode.
3814            setInCallScreenMode(InCallScreenMode.NORMAL);
3815            SyncWithPhoneStateStatus status = syncWithPhoneState();
3816            if (status != SyncWithPhoneStateStatus.SUCCESS) {
3817                Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3818                // We shouldn't even be in the in-call UI in the first
3819                // place, so bail out:
3820                if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 1");
3821                endInCallScreenSession();
3822                return;
3823            }
3824            return;
3825        }
3826
3827        int numConnections = connections.size();
3828        if (numConnections <= 1) {
3829            if (VDBG) log("==> foreground call no longer a conference!");
3830            // Hide the Manage Conference panel, return to NORMAL mode.
3831            setInCallScreenMode(InCallScreenMode.NORMAL);
3832            SyncWithPhoneStateStatus status = syncWithPhoneState();
3833            if (status != SyncWithPhoneStateStatus.SUCCESS) {
3834                Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3835                // We shouldn't even be in the in-call UI in the first
3836                // place, so bail out:
3837                if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 2");
3838                endInCallScreenSession();
3839                return;
3840            }
3841            return;
3842        }
3843
3844        // TODO: the test to see if numConnections has changed can go in
3845        // updateManageConferencePanel(), rather than here.
3846        if (numConnections != mManageConferenceUtils.getNumCallersInConference()) {
3847            if (VDBG) log("==> Conference size has changed; need to rebuild UI!");
3848            mManageConferenceUtils.updateManageConferencePanel(connections);
3849        }
3850    }
3851
3852    /**
3853     * Updates {@link #mCallCard}'s visibility state per DTMF dialpad visibility. They
3854     * cannot be shown simultaneously and thus we should reflect DTMF dialpad visibility into
3855     * another.
3856     *
3857     * Note: During OTA calls or users' managing conference calls, we should *not* call this method
3858     * but manually manage both visibility.
3859     *
3860     * @see #updateScreen()
3861     */
3862    private void updateCallCardVisibilityPerDialerState(boolean animate) {
3863        // We need to hide the CallCard while the dialpad is visible.
3864        if (isDialerOpened()) {
3865            if (VDBG) {
3866                log("- updateCallCardVisibilityPerDialerState(animate="
3867                        + animate + "): dialpad open, hide mCallCard...");
3868            }
3869            if (animate) {
3870                AnimationUtils.Fade.hide(mCallCard, View.GONE);
3871            } else {
3872                mCallCard.setVisibility(View.GONE);
3873            }
3874        } else {
3875            // Dialpad is dismissed; bring back the CallCard if it's supposed to be visible.
3876            if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.NORMAL)
3877                || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.CALL_ENDED)) {
3878                if (VDBG) {
3879                    log("- updateCallCardVisibilityPerDialerState(animate="
3880                            + animate + "): dialpad dismissed, show mCallCard...");
3881                }
3882                if (animate) {
3883                    AnimationUtils.Fade.show(mCallCard);
3884                } else {
3885                    mCallCard.setVisibility(View.VISIBLE);
3886                }
3887            }
3888        }
3889    }
3890
3891    /**
3892     * @see DTMFTwelveKeyDialer#isOpened()
3893     */
3894    /* package */ boolean isDialerOpened() {
3895        return (mDialer != null && mDialer.isOpened());
3896    }
3897
3898    /**
3899     * Called any time the DTMF dialpad is opened.
3900     * @see DTMFTwelveKeyDialer#openDialer(boolean)
3901     */
3902    /* package */ void onDialerOpen(boolean animate) {
3903        if (DBG) log("onDialerOpen()...");
3904
3905        // Update the in-call touch UI.
3906        updateInCallTouchUi();
3907
3908        // Update CallCard UI, which depends on the dialpad.
3909        updateCallCardVisibilityPerDialerState(animate);
3910
3911        // This counts as explicit "user activity".
3912        mApp.pokeUserActivity();
3913
3914        //If on OTA Call, hide OTA Screen
3915        // TODO: This may not be necessary, now that the dialpad is
3916        // always visible in OTA mode.
3917        if  ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
3918                || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
3919                && mApp.otaUtils != null) {
3920            mApp.otaUtils.hideOtaScreen();
3921        }
3922    }
3923
3924    /**
3925     * Called any time the DTMF dialpad is closed.
3926     * @see DTMFTwelveKeyDialer#closeDialer(boolean)
3927     */
3928    /* package */ void onDialerClose(boolean animate) {
3929        if (DBG) log("onDialerClose()...");
3930
3931        // OTA-specific cleanup upon closing the dialpad.
3932        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
3933            || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
3934            || ((mApp.cdmaOtaScreenState != null)
3935                && (mApp.cdmaOtaScreenState.otaScreenState ==
3936                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
3937            if (mApp.otaUtils != null) {
3938                mApp.otaUtils.otaShowProperScreen();
3939            }
3940        }
3941
3942        // Update the in-call touch UI.
3943        updateInCallTouchUi();
3944
3945        // Update CallCard UI, which depends on the dialpad.
3946        updateCallCardVisibilityPerDialerState(animate);
3947
3948        // This counts as explicit "user activity".
3949        mApp.pokeUserActivity();
3950    }
3951
3952    /**
3953     * Determines when we can dial DTMF tones.
3954     */
3955    /* package */ boolean okToDialDTMFTones() {
3956        final boolean hasRingingCall = mCM.hasActiveRingingCall();
3957        final Call.State fgCallState = mCM.getActiveFgCallState();
3958
3959        // We're allowed to send DTMF tones when there's an ACTIVE
3960        // foreground call, and not when an incoming call is ringing
3961        // (since DTMF tones are useless in that state), or if the
3962        // Manage Conference UI is visible (since the tab interferes
3963        // with the "Back to call" button.)
3964
3965        // We can also dial while in ALERTING state because there are
3966        // some connections that never update to an ACTIVE state (no
3967        // indication from the network).
3968        boolean canDial =
3969            (fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
3970            && !hasRingingCall
3971            && (mApp.inCallUiState.inCallScreenMode != InCallScreenMode.MANAGE_CONFERENCE);
3972
3973        if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
3974                ", ringing state: " + hasRingingCall +
3975                ", call screen mode: " + mApp.inCallUiState.inCallScreenMode +
3976                ", result: " + canDial);
3977
3978        return canDial;
3979    }
3980
3981    /**
3982     * @return true if the in-call DTMF dialpad should be available to the
3983     *      user, given the current state of the phone and the in-call UI.
3984     *      (This is used to control the enabledness of the "Show
3985     *      dialpad" onscreen button; see InCallControlState.dialpadEnabled.)
3986     */
3987    /* package */ boolean okToShowDialpad() {
3988        // Very similar to okToDialDTMFTones(), but allow DIALING here.
3989        final Call.State fgCallState = mCM.getActiveFgCallState();
3990        return okToDialDTMFTones() || (fgCallState == Call.State.DIALING);
3991    }
3992
3993    /**
3994     * Initializes the in-call touch UI on devices that need it.
3995     */
3996    private void initInCallTouchUi() {
3997        if (DBG) log("initInCallTouchUi()...");
3998        // TODO: we currently use the InCallTouchUi widget in at least
3999        // some states on ALL platforms.  But if some devices ultimately
4000        // end up not using *any* onscreen touch UI, we should make sure
4001        // to not even inflate the InCallTouchUi widget on those devices.
4002        mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
4003        mInCallTouchUi.setInCallScreenInstance(this);
4004
4005        // RespondViaSmsManager implements the "Respond via SMS"
4006        // feature that's triggered from the incoming call widget.
4007        mRespondViaSmsManager = new RespondViaSmsManager();
4008        mRespondViaSmsManager.setInCallScreenInstance(this);
4009    }
4010
4011    /**
4012     * Updates the state of the in-call touch UI.
4013     */
4014    private void updateInCallTouchUi() {
4015        if (mInCallTouchUi != null) {
4016            mInCallTouchUi.updateState(mCM);
4017        }
4018    }
4019
4020    /**
4021     * @return the InCallTouchUi widget
4022     */
4023    /* package */ InCallTouchUi getInCallTouchUi() {
4024        return mInCallTouchUi;
4025    }
4026
4027    /**
4028     * Posts a handler message telling the InCallScreen to refresh the
4029     * onscreen in-call UI.
4030     *
4031     * This is just a wrapper around updateScreen(), for use by the
4032     * rest of the phone app or from a thread other than the UI thread.
4033     *
4034     * updateScreen() is a no-op if the InCallScreen is not the foreground
4035     * activity, so it's safe to call this whether or not the InCallScreen
4036     * is currently visible.
4037     */
4038    /* package */ void requestUpdateScreen() {
4039        if (DBG) log("requestUpdateScreen()...");
4040        mHandler.removeMessages(REQUEST_UPDATE_SCREEN);
4041        mHandler.sendEmptyMessage(REQUEST_UPDATE_SCREEN);
4042    }
4043
4044    /**
4045     * @return true if we're in restricted / emergency dialing only mode.
4046     */
4047    public boolean isPhoneStateRestricted() {
4048        // TODO:  This needs to work IN TANDEM with the KeyGuardViewMediator Code.
4049        // Right now, it looks like the mInputRestricted flag is INTERNAL to the
4050        // KeyGuardViewMediator and SPECIFICALLY set to be FALSE while the emergency
4051        // phone call is being made, to allow for input into the InCallScreen.
4052        // Having the InCallScreen judge the state of the device from this flag
4053        // becomes meaningless since it is always false for us.  The mediator should
4054        // have an additional API to let this app know that it should be restricted.
4055        int serviceState = mCM.getServiceState();
4056        return ((serviceState == ServiceState.STATE_EMERGENCY_ONLY) ||
4057                (serviceState == ServiceState.STATE_OUT_OF_SERVICE) ||
4058                (mApp.getKeyguardManager().inKeyguardRestrictedInputMode()));
4059    }
4060
4061
4062    //
4063    // Bluetooth helper methods.
4064    //
4065    // - BluetoothAdapter is the Bluetooth system service.  If
4066    //   getDefaultAdapter() returns null
4067    //   then the device is not BT capable.  Use BluetoothDevice.isEnabled()
4068    //   to see if BT is enabled on the device.
4069    //
4070    // - BluetoothHeadset is the API for the control connection to a
4071    //   Bluetooth Headset.  This lets you completely connect/disconnect a
4072    //   headset (which we don't do from the Phone UI!) but also lets you
4073    //   get the address of the currently active headset and see whether
4074    //   it's currently connected.
4075
4076    /**
4077     * @return true if the Bluetooth on/off switch in the UI should be
4078     *         available to the user (i.e. if the device is BT-capable
4079     *         and a headset is connected.)
4080     */
4081    /* package */ boolean isBluetoothAvailable() {
4082        if (VDBG) log("isBluetoothAvailable()...");
4083
4084        // There's no need to ask the Bluetooth system service if BT is enabled:
4085        //
4086        //    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
4087        //    if ((adapter == null) || !adapter.isEnabled()) {
4088        //        if (DBG) log("  ==> FALSE (BT not enabled)");
4089        //        return false;
4090        //    }
4091        //    if (DBG) log("  - BT enabled!  device name " + adapter.getName()
4092        //                 + ", address " + adapter.getAddress());
4093        //
4094        // ...since we already have a BluetoothHeadset instance.  We can just
4095        // call isConnected() on that, and assume it'll be false if BT isn't
4096        // enabled at all.
4097
4098        // Check if there's a connected headset, using the BluetoothHeadset API.
4099        boolean isConnected = false;
4100        if (mBluetoothHeadset != null) {
4101            List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
4102
4103            if (deviceList.size() > 0) {
4104                BluetoothDevice device = deviceList.get(0);
4105                isConnected = true;
4106
4107                if (VDBG) log("  - headset state = " +
4108                              mBluetoothHeadset.getConnectionState(device));
4109                if (VDBG) log("  - headset address: " + device);
4110                if (VDBG) log("  - isConnected: " + isConnected);
4111            }
4112        }
4113
4114        if (VDBG) log("  ==> " + isConnected);
4115        return isConnected;
4116    }
4117
4118    /**
4119     * @return true if a BT Headset is available, and its audio is currently connected.
4120     */
4121    /* package */ boolean isBluetoothAudioConnected() {
4122        if (mBluetoothHeadset == null) {
4123            if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
4124            return false;
4125        }
4126        List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
4127
4128        if (deviceList.isEmpty()) {
4129            return false;
4130        }
4131        BluetoothDevice device = deviceList.get(0);
4132        boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
4133        if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
4134        return isAudioOn;
4135    }
4136
4137    /**
4138     * Helper method used to control the onscreen "Bluetooth" indication;
4139     * see InCallControlState.bluetoothIndicatorOn.
4140     *
4141     * @return true if a BT device is available and its audio is currently connected,
4142     *              <b>or</b> if we issued a BluetoothHeadset.connectAudio()
4143     *              call within the last 5 seconds (which presumably means
4144     *              that the BT audio connection is currently being set
4145     *              up, and will be connected soon.)
4146     */
4147    /* package */ boolean isBluetoothAudioConnectedOrPending() {
4148        if (isBluetoothAudioConnected()) {
4149            if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
4150            return true;
4151        }
4152
4153        // If we issued a connectAudio() call "recently enough", even
4154        // if BT isn't actually connected yet, let's still pretend BT is
4155        // on.  This makes the onscreen indication more responsive.
4156        if (mBluetoothConnectionPending) {
4157            long timeSinceRequest =
4158                    SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
4159            if (timeSinceRequest < 5000 /* 5 seconds */) {
4160                if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
4161                             + timeSinceRequest + " msec ago)");
4162                return true;
4163            } else {
4164                if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
4165                             + timeSinceRequest + " msec ago)");
4166                mBluetoothConnectionPending = false;
4167                return false;
4168            }
4169        }
4170
4171        if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
4172        return false;
4173    }
4174
4175    /**
4176     * Posts a message to our handler saying to update the onscreen UI
4177     * based on a bluetooth headset state change.
4178     */
4179    /* package */ void requestUpdateBluetoothIndication() {
4180        if (VDBG) log("requestUpdateBluetoothIndication()...");
4181        // No need to look at the current state here; any UI elements that
4182        // care about the bluetooth state (i.e. the CallCard) get
4183        // the necessary state directly from PhoneApp.showBluetoothIndication().
4184        mHandler.removeMessages(REQUEST_UPDATE_BLUETOOTH_INDICATION);
4185        mHandler.sendEmptyMessage(REQUEST_UPDATE_BLUETOOTH_INDICATION);
4186    }
4187
4188    private void dumpBluetoothState() {
4189        log("============== dumpBluetoothState() =============");
4190        log("= isBluetoothAvailable: " + isBluetoothAvailable());
4191        log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
4192        log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
4193        log("= PhoneApp.showBluetoothIndication: "
4194            + mApp.showBluetoothIndication());
4195        log("=");
4196        if (mBluetoothAdapter != null) {
4197            if (mBluetoothHeadset != null) {
4198                List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
4199
4200                if (deviceList.size() > 0) {
4201                    BluetoothDevice device = deviceList.get(0);
4202                    log("= BluetoothHeadset.getCurrentDevice: " + device);
4203                    log("= BluetoothHeadset.State: "
4204                        + mBluetoothHeadset.getConnectionState(device));
4205                    log("= BluetoothHeadset audio connected: " +
4206                        mBluetoothHeadset.isAudioConnected(device));
4207                }
4208            } else {
4209                log("= mBluetoothHeadset is null");
4210            }
4211        } else {
4212            log("= mBluetoothAdapter is null; device is not BT capable");
4213        }
4214    }
4215
4216    /* package */ void connectBluetoothAudio() {
4217        if (VDBG) log("connectBluetoothAudio()...");
4218        if (mBluetoothHeadset != null) {
4219            // TODO(BT) check return
4220            mBluetoothHeadset.connectAudio();
4221        }
4222
4223        // Watch out: The bluetooth connection doesn't happen instantly;
4224        // the connectAudio() call returns instantly but does its real
4225        // work in another thread.  The mBluetoothConnectionPending flag
4226        // is just a little trickery to ensure that the onscreen UI updates
4227        // instantly. (See isBluetoothAudioConnectedOrPending() above.)
4228        mBluetoothConnectionPending = true;
4229        mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
4230    }
4231
4232    /* package */ void disconnectBluetoothAudio() {
4233        if (VDBG) log("disconnectBluetoothAudio()...");
4234        if (mBluetoothHeadset != null) {
4235            mBluetoothHeadset.disconnectAudio();
4236        }
4237        mBluetoothConnectionPending = false;
4238    }
4239
4240    /**
4241     * Posts a handler message telling the InCallScreen to close
4242     * the OTA failure notice after the specified delay.
4243     * @see OtaUtils.otaShowProgramFailureNotice
4244     */
4245    /* package */ void requestCloseOtaFailureNotice(long timeout) {
4246        if (DBG) log("requestCloseOtaFailureNotice() with timeout: " + timeout);
4247        mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_OTA_FAILURE_NOTICE, timeout);
4248
4249        // TODO: we probably ought to call removeMessages() for this
4250        // message code in either onPause or onResume, just to be 100%
4251        // sure that the message we just posted has no way to affect a
4252        // *different* call if the user quickly backs out and restarts.
4253        // (This is also true for requestCloseSpcErrorNotice() below, and
4254        // probably anywhere else we use mHandler.sendEmptyMessageDelayed().)
4255    }
4256
4257    /**
4258     * Posts a handler message telling the InCallScreen to close
4259     * the SPC error notice after the specified delay.
4260     * @see OtaUtils.otaShowSpcErrorNotice
4261     */
4262    /* package */ void requestCloseSpcErrorNotice(long timeout) {
4263        if (DBG) log("requestCloseSpcErrorNotice() with timeout: " + timeout);
4264        mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_SPC_ERROR_NOTICE, timeout);
4265    }
4266
4267    public boolean isOtaCallInActiveState() {
4268        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
4269                || ((mApp.cdmaOtaScreenState != null)
4270                    && (mApp.cdmaOtaScreenState.otaScreenState ==
4271                        CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
4272            return true;
4273        } else {
4274            return false;
4275        }
4276    }
4277
4278    /**
4279     * Handle OTA Call End scenario when display becomes dark during OTA Call
4280     * and InCallScreen is in pause mode.  CallNotifier will listen for call
4281     * end indication and call this api to handle OTA Call end scenario
4282     */
4283    public void handleOtaCallEnd() {
4284        if (DBG) log("handleOtaCallEnd entering");
4285        if (((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
4286                || ((mApp.cdmaOtaScreenState != null)
4287                && (mApp.cdmaOtaScreenState.otaScreenState !=
4288                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))
4289                && ((mApp.cdmaOtaProvisionData != null)
4290                && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
4291            if (DBG) log("handleOtaCallEnd - Set OTA Call End stater");
4292            setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4293            updateScreen();
4294        }
4295    }
4296
4297    public boolean isOtaCallInEndState() {
4298        return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED);
4299    }
4300
4301
4302    /**
4303     * Upon resuming the in-call UI, check to see if an OTASP call is in
4304     * progress, and if so enable the special OTASP-specific UI.
4305     *
4306     * TODO: have a simple single flag in InCallUiState for this rather than
4307     * needing to know about all those mApp.cdma*State objects.
4308     *
4309     * @return true if any OTASP-related UI is active
4310     */
4311    private boolean checkOtaspStateOnResume() {
4312        // If there's no OtaUtils instance, that means we haven't even tried
4313        // to start an OTASP call (yet), so there's definitely nothing to do here.
4314        if (mApp.otaUtils == null) {
4315            if (DBG) log("checkOtaspStateOnResume: no OtaUtils instance; nothing to do.");
4316            return false;
4317        }
4318
4319        if ((mApp.cdmaOtaScreenState == null) || (mApp.cdmaOtaProvisionData == null)) {
4320            // Uh oh -- something wrong with our internal OTASP state.
4321            // (Since this is an OTASP-capable device, these objects
4322            // *should* have already been created by PhoneApp.onCreate().)
4323            throw new IllegalStateException("checkOtaspStateOnResume: "
4324                                            + "app.cdmaOta* objects(s) not initialized");
4325        }
4326
4327        // The PhoneApp.cdmaOtaInCallScreenUiState instance is the
4328        // authoritative source saying whether or not the in-call UI should
4329        // show its OTASP-related UI.
4330
4331        OtaUtils.CdmaOtaInCallScreenUiState.State cdmaOtaInCallScreenState =
4332                mApp.otaUtils.getCdmaOtaInCallScreenUiState();
4333        // These states are:
4334        // - UNDEFINED: no OTASP-related UI is visible
4335        // - NORMAL: OTASP call in progress, so show in-progress OTASP UI
4336        // - ENDED: OTASP call just ended, so show success/failure indication
4337
4338        boolean otaspUiActive =
4339                (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL)
4340                || (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
4341
4342        if (otaspUiActive) {
4343            // Make sure the OtaUtils instance knows about the InCallScreen's
4344            // OTASP-related UI widgets.
4345            //
4346            // (This call has no effect if the UI widgets have already been set up.
4347            // It only really matters  the very first time that the InCallScreen instance
4348            // is onResume()d after starting an OTASP call.)
4349            mApp.otaUtils.updateUiWidgets(this, mInCallTouchUi, mCallCard);
4350
4351            // Also update the InCallScreenMode based on the cdmaOtaInCallScreenState.
4352
4353            if (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL) {
4354                if (DBG) log("checkOtaspStateOnResume - in OTA Normal mode");
4355                setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
4356            } else if (cdmaOtaInCallScreenState ==
4357                       OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED) {
4358                if (DBG) log("checkOtaspStateOnResume - in OTA END mode");
4359                setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4360            }
4361
4362            // TODO(OTASP): we might also need to go into OTA_ENDED mode
4363            // in one extra case:
4364            //
4365            // else if (mApp.cdmaOtaScreenState.otaScreenState ==
4366            //            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG) {
4367            //     if (DBG) log("checkOtaspStateOnResume - set OTA END Mode");
4368            //     setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4369            // }
4370
4371        } else {
4372            // OTASP is not active; reset to regular in-call UI.
4373
4374            if (DBG) log("checkOtaspStateOnResume - Set OTA NORMAL Mode");
4375            setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
4376
4377            if (mApp.otaUtils != null) {
4378                mApp.otaUtils.cleanOtaScreen(false);
4379            }
4380        }
4381
4382        // TODO(OTASP):
4383        // The original check from checkIsOtaCall() when handling ACTION_MAIN was this:
4384        //
4385        //        [ . . . ]
4386        //        else if (action.equals(intent.ACTION_MAIN)) {
4387        //            if (DBG) log("checkIsOtaCall action ACTION_MAIN");
4388        //            boolean isRingingCall = mCM.hasActiveRingingCall();
4389        //            if (isRingingCall) {
4390        //                if (DBG) log("checkIsOtaCall isRingingCall: " + isRingingCall);
4391        //                return false;
4392        //            } else if ((mApp.cdmaOtaInCallScreenUiState.state
4393        //                            == CdmaOtaInCallScreenUiState.State.NORMAL)
4394        //                    || (mApp.cdmaOtaInCallScreenUiState.state
4395        //                            == CdmaOtaInCallScreenUiState.State.ENDED)) {
4396        //                if (DBG) log("action ACTION_MAIN, OTA call already in progress");
4397        //                isOtaCall = true;
4398        //            } else {
4399        //                if (mApp.cdmaOtaScreenState.otaScreenState !=
4400        //                        CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED) {
4401        //                    if (DBG) log("checkIsOtaCall action ACTION_MAIN, "
4402        //                                 + "OTA call in progress with UNDEFINED");
4403        //                    isOtaCall = true;
4404        //                }
4405        //            }
4406        //        }
4407        //
4408        // Also, in internalResolveIntent() we used to do this:
4409        //
4410        //        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
4411        //                || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)) {
4412        //            // If in OTA Call, update the OTA UI
4413        //            updateScreen();
4414        //            return;
4415        //        }
4416        //
4417        // We still need more cleanup to simplify the mApp.cdma*State objects.
4418
4419        return otaspUiActive;
4420    }
4421
4422    /**
4423     * Updates and returns the InCallControlState instance.
4424     */
4425    public InCallControlState getUpdatedInCallControlState() {
4426        if (VDBG) log("getUpdatedInCallControlState()...");
4427        mInCallControlState.update();
4428        return mInCallControlState;
4429    }
4430
4431    public void resetInCallScreenMode() {
4432        if (DBG) log("resetInCallScreenMode: setting mode to UNDEFINED...");
4433        setInCallScreenMode(InCallScreenMode.UNDEFINED);
4434    }
4435
4436    /**
4437     * Updates the onscreen hint displayed while the user is dragging one
4438     * of the handles of the RotarySelector widget used for incoming
4439     * calls.
4440     *
4441     * @param hintTextResId resource ID of the hint text to display,
4442     *        or 0 if no hint should be visible.
4443     * @param hintColorResId resource ID for the color of the hint text
4444     */
4445    /* package */ void updateIncomingCallWidgetHint(int hintTextResId, int hintColorResId) {
4446        if (VDBG) log("updateIncomingCallWidgetHint(" + hintTextResId + ")...");
4447        if (mCallCard != null) {
4448            mCallCard.setIncomingCallWidgetHint(hintTextResId, hintColorResId);
4449            mCallCard.updateState(mCM);
4450            // TODO: if hintTextResId == 0, consider NOT clearing the onscreen
4451            // hint right away, but instead post a delayed handler message to
4452            // keep it onscreen for an extra second or two.  (This might make
4453            // the hint more helpful if the user quickly taps one of the
4454            // handles without dragging at all...)
4455            // (Or, maybe this should happen completely within the RotarySelector
4456            // widget, since the widget itself probably wants to keep the colored
4457            // arrow visible for some extra time also...)
4458        }
4459    }
4460
4461
4462    /**
4463     * Used when we need to update buttons outside InCallTouchUi's updateInCallControls() along
4464     * with that method being called. CallCard may call this too because it doesn't have
4465     * enough information to update buttons inside itself (more specifically, the class cannot
4466     * obtain mInCallControllState without some side effect. See also
4467     * {@link #getUpdatedInCallControlState()}. We probably don't want a method like
4468     * getRawCallControlState() which returns raw intance with no side effect just for this
4469     * corner case scenario)
4470     *
4471     * TODO: need better design for buttons outside InCallTouchUi.
4472     */
4473    /* package */ void updateButtonStateOutsideInCallTouchUi() {
4474        if (mCallCard != null) {
4475            mCallCard.setSecondaryCallClickable(mInCallControlState.canSwap);
4476        }
4477    }
4478
4479    @Override
4480    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
4481        super.dispatchPopulateAccessibilityEvent(event);
4482        mCallCard.dispatchPopulateAccessibilityEvent(event);
4483        return true;
4484    }
4485
4486    /**
4487     * Manually handle configuration changes.
4488     *
4489     * Originally android:configChanges was set to "orientation|keyboardHidden|uiMode"
4490     * in order "to make sure the system doesn't destroy and re-create us due to the
4491     * above config changes". However it is currently set to "keyboardHidden" since
4492     * the system needs to handle rotation when inserted into a compatible cardock.
4493     * Even without explicitly handling orientation and uiMode, the app still runs
4494     * and does not drop the call when rotated.
4495     *
4496     */
4497    public void onConfigurationChanged(Configuration newConfig) {
4498        if (DBG) log("onConfigurationChanged: newConfig = " + newConfig);
4499
4500        // Note: At the time this function is called, our Resources object
4501        // will have already been updated to return resource values matching
4502        // the new configuration.
4503
4504        // Watch out: we *can* still get destroyed and recreated if a
4505        // configuration change occurs that is *not* listed in the
4506        // android:configChanges attribute.  TODO: Any others we need to list?
4507
4508        super.onConfigurationChanged(newConfig);
4509
4510        // Nothing else to do here, since (currently) the InCallScreen looks
4511        // exactly the same regardless of configuration.
4512        // (Specifically, we'll never be in landscape mode because we set
4513        // android:screenOrientation="portrait" in our manifest, and we don't
4514        // change our UI at all based on newConfig.keyboardHidden or
4515        // newConfig.uiMode.)
4516
4517        // TODO: we do eventually want to handle at least some config changes, such as:
4518        boolean isKeyboardOpen = (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO);
4519        if (DBG) log("  - isKeyboardOpen = " + isKeyboardOpen);
4520        boolean isLandscape = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
4521        if (DBG) log("  - isLandscape = " + isLandscape);
4522        if (DBG) log("  - uiMode = " + newConfig.uiMode);
4523        // See bug 2089513.
4524    }
4525
4526    /**
4527     * Handles an incoming RING event from the telephony layer.
4528     */
4529    private void onIncomingRing() {
4530        if (DBG) log("onIncomingRing()...");
4531        // IFF we're visible, forward this event to the InCallTouchUi
4532        // instance (which uses this event to drive the animation of the
4533        // incoming-call UI.)
4534        if (mIsForegroundActivity && (mInCallTouchUi != null)) {
4535            mInCallTouchUi.onIncomingRing();
4536        }
4537    }
4538
4539    /**
4540     * Handles a "new ringing connection" event from the telephony layer.
4541     *
4542     * This event comes in right at the start of the incoming-call sequence,
4543     * exactly once per incoming call.
4544     *
4545     * Watch out: this won't be called if InCallScreen isn't ready yet,
4546     * which typically happens for the first incoming phone call (even before
4547     * the possible first outgoing call).
4548     */
4549    private void onNewRingingConnection() {
4550        if (DBG) log("onNewRingingConnection()...");
4551
4552        // We use this event to reset any incoming-call-related UI elements
4553        // that might have been left in an inconsistent state after a prior
4554        // incoming call.
4555        // (Note we do this whether or not we're the foreground activity,
4556        // since this event comes in *before* we actually get launched to
4557        // display the incoming-call UI.)
4558
4559        // If there's a "Respond via SMS" popup still around since the
4560        // last time we were the foreground activity, make sure it's not
4561        // still active(!) since that would interfere with *this* incoming
4562        // call.
4563        // (Note that we also do this same check in onResume().  But we
4564        // need it here too, to make sure the popup gets reset in the case
4565        // where a call-waiting call comes in while the InCallScreen is
4566        // already in the foreground.)
4567        mRespondViaSmsManager.dismissPopup();  // safe even if already dismissed
4568    }
4569
4570    /**
4571     * Enables or disables the status bar "window shade" based on the current situation.
4572     */
4573    private void updateExpandedViewState() {
4574        if (mIsForegroundActivity) {
4575            if (mApp.proximitySensorModeEnabled()) {
4576                // We should not enable notification's expanded view on RINGING state.
4577                mApp.notificationMgr.statusBarHelper.enableExpandedView(
4578                        mCM.getState() != PhoneConstants.State.RINGING);
4579            } else {
4580                // If proximity sensor is unavailable on the device, disable it to avoid false
4581                // touches toward notifications.
4582                mApp.notificationMgr.statusBarHelper.enableExpandedView(false);
4583            }
4584        } else {
4585            mApp.notificationMgr.statusBarHelper.enableExpandedView(true);
4586        }
4587    }
4588
4589    private void log(String msg) {
4590        Log.d(LOG_TAG, msg);
4591    }
4592
4593    /**
4594     * Requests to remove provider info frame after having
4595     * {@link #PROVIDER_INFO_TIMEOUT}) msec delay.
4596     */
4597    /* package */ void requestRemoveProviderInfoWithDelay() {
4598        // Remove any zombie messages and then send a message to
4599        // self to remove the provider info after some time.
4600        mHandler.removeMessages(EVENT_HIDE_PROVIDER_INFO);
4601        Message msg = Message.obtain(mHandler, EVENT_HIDE_PROVIDER_INFO);
4602        mHandler.sendMessageDelayed(msg, PROVIDER_INFO_TIMEOUT);
4603        if (DBG) {
4604            log("Requested to remove provider info after " + PROVIDER_INFO_TIMEOUT + " msec.");
4605        }
4606    }
4607
4608    /**
4609     * Indicates whether or not the QuickResponseDialog is currently showing in the call screen
4610     */
4611    public boolean isQuickResponseDialogShowing() {
4612        return mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
4613    }
4614}
4615