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