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