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