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