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