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