Misc phone app code cleanup.
[android/platform/packages/apps/Phone.git] / src / com / android / phone / InCallScreen.java
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 com.android.internal.telephony.Call;
20 import com.android.internal.telephony.CallerInfo;
21 import com.android.internal.telephony.CallerInfoAsyncQuery;
22 import com.android.internal.telephony.Connection;
23 import com.android.internal.telephony.MmiCode;
24 import com.android.internal.telephony.Phone;
25
26 import android.app.Activity;
27 import android.app.AlertDialog;
28 import android.app.Dialog;
29 import android.bluetooth.BluetoothHeadset;
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.SystemClock;
45 import android.os.SystemProperties;
46 import android.provider.CallLog;
47 import android.provider.CallLog.Calls;
48 import android.provider.Checkin;
49 import android.provider.Settings;
50 import android.telephony.PhoneNumberUtils;
51 import android.telephony.ServiceState;
52 import android.text.TextUtils;
53 import android.text.method.DialerKeyListener;
54 import android.util.Log;
55 import android.view.KeyEvent;
56 import android.view.Menu;
57 import android.view.MotionEvent;
58 import android.view.View;
59 import android.view.ViewConfiguration;
60 import android.view.ViewGroup;
61 import android.view.ViewStub;
62 import android.view.Window;
63 import android.view.WindowManager;
64 import android.view.animation.Animation;
65 import android.view.animation.AnimationUtils;
66 import android.widget.Button;
67 import android.widget.Chronometer;
68 import android.widget.EditText;
69 import android.widget.ImageButton;
70 import android.widget.LinearLayout;
71 import android.widget.SlidingDrawer;
72 import android.widget.TextView;
73 import android.widget.Toast;
74
75 import java.util.List;
76
77 /**
78  * Phone app "in call" screen.
79  */
80 public class InCallScreen extends Activity
81         implements View.OnClickListener, View.OnTouchListener,
82                 CallerInfoAsyncQuery.OnQueryCompleteListener {
83     private static final String LOG_TAG = "InCallScreen";
84
85     private static final boolean DBG =
86             (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
87     private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);
88
89     // Enable detailed logs of user actions while on the in-call screen.
90     // TODO: For now this is totally disabled.  But for future user
91     // research, we should change the PHONE_UI_EVENT_* events to use
92     // android.util.EventLog rather than Checkin.logEvent, so that we can
93     // select which of those events to upload on a tag-by-tag basis
94     // (which means that we could actually ship devices with this logging
95     // enabled.)
96     /* package */ static final boolean ENABLE_PHONE_UI_EVENT_LOGGING = false;
97
98     /**
99      * Intent extra used to specify whether the DTMF dialpad should be
100      * initially visible when bringing up the InCallScreen.  (If this
101      * extra is present, the dialpad will be initially shown if the extra
102      * has the boolean value true, and initially hidden otherwise.)
103      */
104     static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
105
106     // Event values used with Checkin.Events.Tag.PHONE_UI events:
107     /** The in-call UI became active */
108     static final String PHONE_UI_EVENT_ENTER = "enter";
109     /** User exited the in-call UI */
110     static final String PHONE_UI_EVENT_EXIT = "exit";
111     /** User clicked one of the touchable in-call buttons */
112     static final String PHONE_UI_EVENT_BUTTON_CLICK = "button_click";
113
114     // Amount of time (in msec) that we display the "Call ended" state.
115     // The "short" value is for calls ended by the local user, and the
116     // "long" value is for calls ended by the remote caller.
117     private static final int CALL_ENDED_SHORT_DELAY =  200;  // msec
118     private static final int CALL_ENDED_LONG_DELAY = 2000;  // msec
119
120     // Amount of time (in msec) that we keep the in-call menu onscreen
121     // *after* the user changes the state of one of the toggle buttons.
122     private static final int MENU_DISMISS_DELAY =  1000;  // msec
123
124     // The "touch lock" overlay timeout comes from Gservices; this is the default.
125     private static final int TOUCH_LOCK_DELAY_DEFAULT =  6000;  // msec
126
127     // Amount of time for Displaying "Dialing" for 3way Calling origination
128     private static final int THREEWAY_CALLERINFO_DISPLAY_TIME = 2000; // msec
129
130     // See CallTracker.MAX_CONNECTIONS_PER_CALL
131     private static final int MAX_CALLERS_IN_CONFERENCE = 5;
132
133     // Message codes; see mHandler below.
134     // Note message codes < 100 are reserved for the PhoneApp.
135     private static final int PHONE_STATE_CHANGED = 101;
136     private static final int PHONE_DISCONNECT = 102;
137     private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
138     private static final int POST_ON_DIAL_CHARS = 104;
139     private static final int WILD_PROMPT_CHAR_ENTERED = 105;
140     private static final int ADD_VOICEMAIL_NUMBER = 106;
141     private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
142     private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
143     private static final int SUPP_SERVICE_FAILED = 110;
144     private static final int DISMISS_MENU = 111;
145     private static final int ALLOW_SCREEN_ON = 112;
146     private static final int TOUCH_LOCK_TIMER = 113;
147     private static final int BLUETOOTH_STATE_CHANGED = 114;
148     private static final int PHONE_CDMA_CALL_WAITING = 115;
149     private static final int THREEWAY_CALLERINFO_DISPLAY_DONE = 116;
150
151
152     // High-level "modes" of the in-call UI.
153     private enum InCallScreenMode {
154         /**
155          * Normal in-call UI elements visible.
156          */
157         NORMAL,
158         /**
159          * "Manage conference" UI is visible, totally replacing the
160          * normal in-call UI.
161          */
162         MANAGE_CONFERENCE,
163         /**
164          * Non-interactive UI state.  Call card is visible,
165          * displaying information about the call that just ended.
166          */
167         CALL_ENDED
168     }
169     private InCallScreenMode mInCallScreenMode;
170
171     // Possible error conditions that can happen on startup.
172     // These are returned as status codes from the various helper
173     // functions we call from onCreate() and/or onResume().
174     // See syncWithPhoneState() and checkIfOkToInitiateOutgoingCall() for details.
175     private enum InCallInitStatus {
176         SUCCESS,
177         VOICEMAIL_NUMBER_MISSING,
178         POWER_OFF,
179         EMERGENCY_ONLY,
180         PHONE_NOT_IN_USE,
181         NO_PHONE_NUMBER_SUPPLIED,
182         DIALED_MMI,
183         CALL_FAILED
184     }
185     private InCallInitStatus mInCallInitialStatus;  // see onResume()
186
187     private boolean mRegisteredForPhoneStates;
188
189     private Phone mPhone;
190     private Call mForegroundCall;
191     private Call mBackgroundCall;
192     private Call mRingingCall;
193
194     private BluetoothHandsfree mBluetoothHandsfree;
195     private BluetoothHeadset mBluetoothHeadset;
196     private boolean mBluetoothConnectionPending;
197     private long mBluetoothConnectionRequestTime;
198
199     // Main in-call UI ViewGroups
200     private ViewGroup mMainFrame;
201     private ViewGroup mInCallPanel;
202
203     // Menu button hint below the "main frame"
204     private TextView mMenuButtonHint;
205
206     // Main in-call UI elements:
207     private CallCard mCallCard;
208     private InCallMenu mInCallMenu;  // created lazily on first MENU keypress
209
210     /**
211      * DTMF Dialer objects, including the model and the sliding drawer / dialer
212      * UI container and the dialer display field for landscape presentation.
213      */
214     private DTMFTwelveKeyDialer mDialer;
215     private SlidingDrawer mDialerDrawer;
216     private EditText mDTMFDisplay;
217
218     // "Manage conference" UI elements
219     private ViewGroup mManageConferencePanel;
220     private Button mButtonManageConferenceDone;
221     private ViewGroup[] mConferenceCallList;
222     private int mNumCallersInConference;
223     private Chronometer mConferenceTime;
224
225     private EditText mWildPromptText;
226
227     // "Touch lock" overlay graphic
228     private View mTouchLockOverlay;  // The overlay over the whole screen
229     private View mTouchLockIcon;  // The "lock" icon in the middle of the screen
230     private Animation mTouchLockFadeIn;
231     private long mTouchLockLastTouchTime;  // in SystemClock.uptimeMillis() time base
232
233     // Onscreen "answer" UI, for devices with no hardware CALL button.
234     private View mOnscreenAnswerUiContainer;  // The container for the whole UI, or null if unused
235     private View mOnscreenAnswerButton;  // The "answer" button itself
236     private long mOnscreenAnswerButtonLastTouchTime;  // in SystemClock.uptimeMillis() time base
237
238     // Various dialogs we bring up (see dismissAllDialogs())
239     // The MMI started dialog can actually be one of 2 items:
240     //   1. An alert dialog if the MMI code is a normal MMI
241     //   2. A progress dialog if the user requested a USSD
242     private Dialog mMmiStartedDialog;
243     private AlertDialog mMissingVoicemailDialog;
244     private AlertDialog mGenericErrorDialog;
245     private AlertDialog mSuppServiceFailureDialog;
246     private AlertDialog mWaitPromptDialog;
247     private AlertDialog mWildPromptDialog;
248
249     // TODO: If the Activity class ever provides an easy way to get the
250     // current "activity lifecycle" state, we can remove these flags.
251     private boolean mIsDestroyed = false;
252     private boolean mIsForegroundActivity = false;
253
254     // Flag indicating whether or not we should bring up the Call Log when
255     // exiting the in-call UI due to the Phone becoming idle.  (This is
256     // true if the most recently disconnected Call was initiated by the
257     // user, or false if it was an incoming call.)
258     // This flag is used by delayedCleanupAfterDisconnect(), and is set by
259     // onDisconnect() (which is the only place that either posts a
260     // DELAYED_CLEANUP_AFTER_DISCONNECT event *or* calls
261     // delayedCleanupAfterDisconnect() directly.)
262     private boolean mShowCallLogAfterDisconnect;
263
264     private Handler mHandler = new Handler() {
265         @Override
266         public void handleMessage(Message msg) {
267             if (mIsDestroyed) {
268                 if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
269                 return;
270             }
271             if (!mIsForegroundActivity) {
272                 if (DBG) log("Handler: handling message " + msg + " while not in foreground");
273                 // Continue anyway; some of the messages below *want* to
274                 // be handled even if we're not the foreground activity
275                 // (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
276                 // should at least be safe to handle if we're not in the
277                 // foreground...
278             }
279
280             PhoneApp app = PhoneApp.getInstance();
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 (in
296                     // particular the "Speaker" menu button) change state
297                     // depending on 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 menu item.  (But even a full updateScreen()
301                     // is still pretty cheap, so let's keep this simple
302                     // for now.)
303                     if (msg.arg1 != 1 && !isBluetoothAudioConnected()){
304                         // If the state is "not connected", restore the speaker state.
305                         // We ONLY want to do this on the wired headset connect /
306                         // disconnect events for now though, so we're only triggering
307                         // on EVENT_HEADSET_PLUG_STATE_CHANGED.
308                         PhoneUtils.restoreSpeakerMode(getApplicationContext());
309                     }
310                     updateScreen();
311                     break;
312
313                 case PhoneApp.MMI_INITIATE:
314                     onMMIInitiate((AsyncResult) msg.obj);
315                     break;
316
317                 case PhoneApp.MMI_CANCEL:
318                     onMMICancel();
319                     break;
320
321                 // handle the mmi complete message.
322                 // since the message display class has been replaced with
323                 // a system dialog in PhoneUtils.displayMMIComplete(), we
324                 // should finish the activity here to close the window.
325                 case PhoneApp.MMI_COMPLETE:
326                     // Check the code to see if the request is ready to
327                     // finish, this includes any MMI state that is not
328                     // PENDING.
329                     MmiCode mmiCode = (MmiCode) ((AsyncResult) msg.obj).result;
330                     // if phone is a CDMA phone display feature code completed message
331                     if (mPhone.getPhoneName().equals("CDMA")) {
332                         PhoneUtils.displayMMIComplete(mPhone, app, mmiCode, null, null);
333                     } else {
334                         if (mmiCode.getState() != MmiCode.State.PENDING) {
335                             if (DBG) log("Got MMI_COMPLETE, finishing...");
336                             finish();
337                         }
338                     }
339                     break;
340
341                 case POST_ON_DIAL_CHARS:
342                     handlePostOnDialChars((AsyncResult) msg.obj, (char) msg.arg1);
343                     break;
344
345                 case ADD_VOICEMAIL_NUMBER:
346                     addVoiceMailNumberPanel();
347                     break;
348
349                 case DONT_ADD_VOICEMAIL_NUMBER:
350                     dontAddVoiceMailNumber();
351                     break;
352
353                 case DELAYED_CLEANUP_AFTER_DISCONNECT:
354                     delayedCleanupAfterDisconnect();
355                     break;
356
357                 case DISMISS_MENU:
358                     // dismissMenu() has no effect if the menu is already closed.
359                     dismissMenu(true);  // dismissImmediate = true
360                     break;
361
362                 case ALLOW_SCREEN_ON:
363                     if (VDBG) log("ALLOW_SCREEN_ON message...");
364                     // Undo our previous call to preventScreenOn(true).
365                     // (Note this will cause the screen to turn on
366                     // immediately, if it's currently off because of a
367                     // prior preventScreenOn(true) call.)
368                     app.preventScreenOn(false);
369                     break;
370
371                 case TOUCH_LOCK_TIMER:
372                     if (VDBG) log("TOUCH_LOCK_TIMER...");
373                     touchLockTimerExpired();
374                     break;
375
376                 case BLUETOOTH_STATE_CHANGED:
377                     if (VDBG) log("BLUETOOTH_STATE_CHANGED...");
378                     // The bluetooth headset state changed, so some UI
379                     // elements may need to update.  (There's no need to
380                     // look up the current state here, since any UI
381                     // elements that care about the bluetooth state get it
382                     // directly from PhoneApp.showBluetoothIndication().)
383                     updateScreen();
384                     break;
385
386                 case PHONE_CDMA_CALL_WAITING:
387                     if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
388                     Connection cn = mRingingCall.getLatestConnection();
389
390                     // Only proceed if we get a valid connection object
391                     if (cn != null) {
392                         // Finally update screen with Call waiting info and request
393                         // screen to wake up
394                         updateScreen();
395                         app.updateWakeState();
396                     }
397                     break;
398
399                 case THREEWAY_CALLERINFO_DISPLAY_DONE:
400                     if (DBG) log("Received THREEWAY_CALLERINFO_DISPLAY_DONE event ...");
401                     if (app.cdmaPhoneCallState.getCurrentCallState()
402                             == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
403                         // Set the mThreeWayCallOrigStateDialing state to true
404                         app.cdmaPhoneCallState.setThreeWayCallOrigState(false);
405
406                         //Finally update screen with with the current on going call
407                         updateScreen();
408                     }
409                     break;
410             }
411         }
412     };
413
414     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
415             @Override
416             public void onReceive(Context context, Intent intent) {
417                 String action = intent.getAction();
418                 if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
419                     // Listen for ACTION_HEADSET_PLUG broadcasts so that we
420                     // can update the onscreen UI when the headset state changes.
421                     // if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
422                     // if (DBG) log("==> intent: " + intent);
423                     // if (DBG) log("    state: " + intent.getIntExtra("state", 0));
424                     // if (DBG) log("    name: " + intent.getStringExtra("name"));
425                     // send the event and add the state as an argument.
426                     Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
427                             intent.getIntExtra("state", 0), 0);
428                     mHandler.sendMessage(message);
429                 }
430             }
431         };
432
433
434     @Override
435     protected void onCreate(Bundle icicle) {
436         if (DBG) log("onCreate()...  this = " + this);
437
438         Profiler.callScreenOnCreate();
439
440         super.onCreate(icicle);
441
442         final PhoneApp app = PhoneApp.getInstance();
443         app.setInCallScreenInstance(this);
444
445         setPhone(app.phone);  // Sets mPhone and mForegroundCall/mBackgroundCall/mRingingCall
446
447         mBluetoothHandsfree = app.getBluetoothHandsfree();
448         if (VDBG) log("- mBluetoothHandsfree: " + mBluetoothHandsfree);
449
450         if (mBluetoothHandsfree != null) {
451             // The PhoneApp only creates a BluetoothHandsfree instance in the
452             // first place if getSystemService(Context.BLUETOOTH_SERVICE)
453             // succeeds.  So at this point we know the device is BT-capable.
454             mBluetoothHeadset = new BluetoothHeadset(this, null);
455             if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
456         }
457
458         requestWindowFeature(Window.FEATURE_NO_TITLE);
459
460         // Inflate everything in incall_screen.xml and add it to the screen.
461         setContentView(R.layout.incall_screen);
462         mDialerDrawer = (SlidingDrawer) findViewById(R.id.dialer_container);
463
464         initInCallScreen();
465
466         // Create the dtmf dialer.  Note that mDialer is instantiated
467         // regardless of screen orientation, although the onscreen touchable
468         // dialpad is used only in portrait mode.
469         mDialer = new DTMFTwelveKeyDialer(this);
470
471         registerForPhoneStates();
472
473         // No need to change wake state here; that happens in onResume() when we
474         // are actually displayed.
475
476         // Handle the Intent we were launched with, but only if this is the
477         // the very first time we're being launched (ie. NOT if we're being
478         // re-initialized after previously being shut down.)
479         // Once we're up and running, any future Intents we need
480         // to handle will come in via the onNewIntent() method.
481         if (icicle == null) {
482             if (DBG) log("onCreate(): this is our very first launch, checking intent...");
483
484             // Stash the result code from internalResolveIntent() in the
485             // mInCallInitialStatus field.  If it's an error code, we'll
486             // handle it in onResume().
487             mInCallInitialStatus = internalResolveIntent(getIntent());
488             if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
489                 Log.w(LOG_TAG, "onCreate: status " + mInCallInitialStatus
490                       + " from internalResolveIntent()");
491                 // See onResume() for the actual error handling.
492             }
493         } else {
494             mInCallInitialStatus = InCallInitStatus.SUCCESS;
495         }
496
497         // When in landscape mode, the user can enter dtmf tones
498         // at any time.  We need to make sure the DTMFDialer is
499         // setup correctly.
500         if (ConfigurationHelper.isLandscape()) {
501             mDialer.startDialerSession();
502             if (VDBG) log("Dialer initialized (in landscape mode).");
503         }
504
505         Profiler.callScreenCreated();
506     }
507
508     /**
509      * Sets the Phone object used internally by the InCallScreen.
510      *
511      * In normal operation this is called from onCreate(), and the
512      * passed-in Phone object comes from the PhoneApp.
513      * For testing, test classes can use this method to
514      * inject a test Phone instance.
515      */
516     /* package */ void setPhone(Phone phone) {
517         mPhone = phone;
518         // Hang onto the three Call objects too; they're singletons that
519         // are constant (and never null) for the life of the Phone.
520         mForegroundCall = mPhone.getForegroundCall();
521         mBackgroundCall = mPhone.getBackgroundCall();
522         mRingingCall = mPhone.getRingingCall();
523     }
524
525     @Override
526     protected void onResume() {
527         if (DBG) log("onResume()...");
528         super.onResume();
529
530         mIsForegroundActivity = true;
531
532         final PhoneApp app = PhoneApp.getInstance();
533
534         // Disable the keyguard the entire time the InCallScreen is
535         // active.  (This is necessary only for the case of receiving an
536         // incoming call while the device is locked; we need to disable
537         // the keyguard so you can answer the call and use the in-call UI,
538         // but we always re-enable the keyguard as soon as you leave this
539         // screen (see onPause().))
540         app.disableKeyguard();
541
542         // Touch events are never considered "user activity" while the
543         // InCallScreen is active, so that unintentional touches won't
544         // prevent the device from going to sleep.
545         app.setIgnoreTouchUserActivity(true);
546
547         // Disable the status bar "window shade" the entire time we're on
548         // the in-call screen.
549         NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(false);
550
551         // Listen for broadcast intents that might affect the onscreen UI.
552         registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
553
554         // Check for any failures that happened during onCreate() or onNewIntent().
555         if (DBG) log("- onResume: initial status = " + mInCallInitialStatus);
556         if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
557             if (DBG) log("- onResume: failure during startup: " + mInCallInitialStatus);
558
559             // Don't bring up the regular Phone UI!  Instead bring up
560             // something more specific to let the user deal with the
561             // problem.
562             handleStartupError(mInCallInitialStatus);
563
564             // But it *is* OK to continue with the rest of onResume(),
565             // since any further setup steps (like updateScreen() and the
566             // CallCard setup) will fall back to a "blank" state if the
567             // phone isn't in use.
568             mInCallInitialStatus = InCallInitStatus.SUCCESS;
569         }
570
571         // Set the volume control handler while we are in the foreground.
572         if (isBluetoothAudioConnected()) {
573             setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
574         } else {
575             setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
576         }
577
578         takeKeyEvents(true);
579
580         // Always start off in NORMAL mode.
581         setInCallScreenMode(InCallScreenMode.NORMAL);
582
583         // Before checking the state of the phone, clean up any
584         // connections in the DISCONNECTED state.
585         // (The DISCONNECTED state is used only to drive the "call ended"
586         // UI; it's totally useless when *entering* the InCallScreen.)
587         mPhone.clearDisconnected();
588
589         InCallInitStatus status = syncWithPhoneState();
590         if (status != InCallInitStatus.SUCCESS) {
591             if (DBG) log("- syncWithPhoneState failed! status = " + status);
592             // Couldn't update the UI, presumably because the phone is totally
593             // idle.  But don't finish() immediately, since we might still
594             // have an error dialog up that the user needs to see.
595             // (And in that case, the error dialog is responsible for calling
596             // finish() when the user dismisses it.)
597         }
598
599         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
600             // InCallScreen is now active.
601             Checkin.logEvent(getContentResolver(),
602                              Checkin.Events.Tag.PHONE_UI,
603                              PHONE_UI_EVENT_ENTER);
604         }
605
606         // Update the poke lock and wake lock when we move to
607         // the foreground.
608         //
609         // But we need to do something special if we're coming
610         // to the foreground while an incoming call is ringing:
611         if (mPhone.getState() == Phone.State.RINGING) {
612             // If the phone is ringing, we *should* already be holding a
613             // full wake lock (which we would have acquired before
614             // firing off the intent that brought us here; see
615             // PhoneUtils.showIncomingCallUi().)
616             //
617             // We also called preventScreenOn(true) at that point, to
618             // avoid cosmetic glitches while we were being launched.
619             // So now we need to post an ALLOW_SCREEN_ON message to
620             // (eventually) undo the prior preventScreenOn(true) call.
621             //
622             // (In principle we shouldn't do this until after our first
623             // layout/draw pass.  But in practice, the delay caused by
624             // simply waiting for the end of the message queue is long
625             // enough to avoid any flickering of the lock screen before
626             // the InCallScreen comes up.)
627             if (VDBG) log("- posting ALLOW_SCREEN_ON message...");
628             mHandler.removeMessages(ALLOW_SCREEN_ON);
629             mHandler.sendEmptyMessage(ALLOW_SCREEN_ON);
630
631             // TODO: There ought to be a more elegant way of doing this,
632             // probably by having the PowerManager and ActivityManager
633             // work together to let apps request that the screen on/off
634             // state be synchronized with the Activity lifecycle.
635             // (See bug 1648751.)
636         } else {
637             // The phone isn't ringing; this is either an outgoing call, or
638             // we're returning to a call in progress.  There *shouldn't* be
639             // any prior preventScreenOn(true) call that we need to undo,
640             // but let's do this just to be safe:
641             app.preventScreenOn(false);
642         }
643         app.updateWakeState();
644
645         // The "touch lock" overlay is NEVER visible when we resume.
646         // (In particular, this check ensures that we won't still be
647         // locked after the user wakes up the screen by pressing MENU.)
648         enableTouchLock(false);
649         // ...but if the dialpad is open we DO need to start the timer
650         // that will eventually bring up the "touch lock" overlay.
651         if (mDialer.isOpened()) resetTouchLockTimer();
652
653         // Restore the mute state if the last mute state change was NOT
654         // done by the user.
655         if (app.getRestoreMuteOnInCallResume()) {
656             PhoneUtils.restoreMuteState(mPhone);
657             app.setRestoreMuteOnInCallResume(false);
658         }
659
660         Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
661         if (VDBG) log("onResume() done.");
662     }
663
664     @Override
665     protected void onSaveInstanceState(Bundle outState) {
666         if (VDBG) log("onSaveInstanceState()...");
667         super.onSaveInstanceState(outState);
668
669         // TODO: Save any state of the UI that needs to persist across
670         // configuration changes (ie. switching between portrait and
671         // landscape.)
672     }
673
674     @Override
675     protected void onPause() {
676         if (DBG) log("onPause()...");
677         super.onPause();
678
679         mIsForegroundActivity = false;
680
681         final PhoneApp app = PhoneApp.getInstance();
682
683         // make sure the chronometer is stopped when we move away from
684         // the foreground.
685         if (mConferenceTime != null) {
686             mConferenceTime.stop();
687         }
688
689         // as a catch-all, make sure that any dtmf tones are stopped
690         // when the UI is no longer in the foreground.
691         mDialer.onDialerKeyUp(null);
692
693         // If the device is put to sleep as the phone call is ending,
694         // we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
695         // event gets handled AFTER the device goes to sleep and wakes
696         // up again.
697
698         // This is because it is possible for a sleep command
699         // (executed with the End Call key) to come during the 2
700         // seconds that the "Call Ended" screen is up.  Sleep then
701         // pauses the device (including the cleanup event) and
702         // resumes the event when it wakes up.
703
704         // To fix this, we introduce a bit of code that pushes the UI
705         // to the background if we pause and see a request to
706         // DELAYED_CLEANUP_AFTER_DISCONNECT.
707
708         // Note: We can try to finish directly, by:
709         //  1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
710         //  2. Calling delayedCleanupAfterDisconnect directly
711
712         // However, doing so can cause problems between the phone
713         // app and the keyguard - the keyguard is trying to sleep at
714         // the same time that the phone state is changing.  This can
715         // end up causing the sleep request to be ignored.
716         if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)) {
717             if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
718             finish();
719         }
720
721         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
722             // InCallScreen is no longer active.
723             Checkin.logEvent(getContentResolver(),
724                              Checkin.Events.Tag.PHONE_UI,
725                              PHONE_UI_EVENT_EXIT);
726         }
727
728         // Clean up the menu, in case we get paused while the menu is up
729         // for some reason.
730         dismissMenu(true);  // dismiss immediately
731
732         // Dismiss any dialogs we may have brought up, just to be 100%
733         // sure they won't still be around when we get back here.
734         dismissAllDialogs();
735
736         // Re-enable the status bar (which we disabled in onResume().)
737         NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(true);
738
739         // Unregister for broadcast intents.  (These affect the visible UI
740         // of the InCallScreen, so we only care about them while we're in the
741         // foreground.)
742         unregisterReceiver(mReceiver);
743
744         // Re-enable "user activity" for touch events.
745         // We actually do this slightly *after* onPause(), to work around a
746         // race condition where a touch can come in after we've paused
747         // but before the device actually goes to sleep.
748         // TODO: The PowerManager itself should prevent this from happening.
749         mHandler.postDelayed(new Runnable() {
750                 public void run() {
751                     app.setIgnoreTouchUserActivity(false);
752                 }
753             }, 500);
754
755         // The keyguard was disabled the entire time the InCallScreen was
756         // active (see onResume()).  Re-enable it now.
757         app.reenableKeyguard();
758
759         // Make sure we revert the poke lock and wake lock when we move to
760         // the background.
761         app.updateWakeState();
762     }
763
764     @Override
765     protected void onStop() {
766         if (VDBG) log("onStop()...");
767         super.onStop();
768
769         stopTimer();
770
771         Phone.State state = mPhone.getState();
772         if (VDBG) log("onStop: state = " + state);
773
774         if (state == Phone.State.IDLE) {
775             // we don't want the call screen to remain in the activity history
776             // if there are not active or ringing calls.
777             if (DBG) log("- onStop: calling finish() to clear activity history...");
778             finish();
779         }
780     }
781
782     @Override
783     protected void onDestroy() {
784         if (DBG) log("onDestroy()...");
785         super.onDestroy();
786
787         // Set the magic flag that tells us NOT to handle any handler
788         // messages that come in asynchronously after we get destroyed.
789         mIsDestroyed = true;
790
791         final PhoneApp app = PhoneApp.getInstance();
792         app.setInCallScreenInstance(null);
793
794         // Clear out the InCallScreen references in various helper objects
795         // (to let them know we've been destroyed).
796         if (mInCallMenu != null) {
797             mInCallMenu.clearInCallScreenReference();
798         }
799         if (mCallCard != null) {
800             mCallCard.setInCallScreenInstance(null);
801         }
802
803         // Make sure that the dialer session is over and done with.
804         // 1. In Landscape mode, we stop the tone generator directly
805         // 2. In portrait mode, the tone generator is stopped
806         // whenever the dialer is closed by the framework, (either
807         // from a user request or calling close on the drawer
808         // directly), so all we have to do is to make sure the
809         // dialer is closed {see DTMFTwelvKeyDialer.onDialerClose}
810         // (it is ok to call this even if the dialer is not open).
811         if (ConfigurationHelper.isLandscape()) {
812             mDialer.stopDialerSession();
813         } else {
814             // make sure the dialer drawer is closed.
815             mDialer.closeDialer(false);
816         }
817         mDialer.clearInCallScreenReference();
818         mDialer = null;
819
820         unregisterForPhoneStates();
821         // No need to change wake state here; that happens in onPause() when we
822         // are moving out of the foreground.
823
824         if (mBluetoothHeadset != null) {
825             mBluetoothHeadset.close();
826             mBluetoothHeadset = null;
827         }
828     }
829
830     /**
831      * Dismisses the in-call screen.
832      *
833      * We never *really* finish() the InCallScreen, since we don't want to
834      * get destroyed and then have to be re-created from scratch for the
835      * next call.  Instead, we just move ourselves to the back of the
836      * activity stack.
837      *
838      * This also means that we'll no longer be reachable via the BACK
839      * button (since moveTaskToBack() puts us behind the Home app, but the
840      * home app doesn't allow the BACK key to move you any farther down in
841      * the history stack.)
842      *
843      * (Since the Phone app itself is never killed, this basically means
844      * that we'll keep a single InCallScreen instance around for the
845      * entire uptime of the device.  This noticeably improves the UI
846      * responsiveness for incoming calls.)
847      */
848     @Override
849     public void finish() {
850         if (DBG) log("finish()...");
851         moveTaskToBack(true);
852     }
853
854     /* package */ boolean isForegroundActivity() {
855         return mIsForegroundActivity;
856     }
857
858     private void registerForPhoneStates() {
859         if (!mRegisteredForPhoneStates) {
860             mPhone.registerForPhoneStateChanged(mHandler, PHONE_STATE_CHANGED, null);
861             mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
862             if (mPhone.getPhoneName().equals("GSM")) {
863                 mPhone.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
864
865                 // register for the MMI complete message.  Upon completion,
866                 // PhoneUtils will bring up a system dialog instead of the
867                 // message display class in PhoneUtils.displayMMIComplete().
868                 // We'll listen for that message too, so that we can finish
869                 // the activity at the same time.
870                 mPhone.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
871             } else { // CDMA
872                 if (DBG) log("Registering for Call Waiting.");
873                 mPhone.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
874             }
875
876             mPhone.setOnPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
877             mPhone.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
878             mRegisteredForPhoneStates = true;
879         }
880     }
881
882     private void unregisterForPhoneStates() {
883         mPhone.unregisterForPhoneStateChanged(mHandler);
884         mPhone.unregisterForDisconnect(mHandler);
885         mPhone.unregisterForMmiInitiate(mHandler);
886         mPhone.unregisterForCallWaiting(mHandler);
887         mPhone.setOnPostDialCharacter(null, POST_ON_DIAL_CHARS, null);
888         mRegisteredForPhoneStates = false;
889     }
890
891     /* package */ void updateAfterRadioTechnologyChange() {
892         if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
893         // Unregister for all events from the old obsolete phone
894         unregisterForPhoneStates();
895
896         // (Re)register for all events relevant to the new active phone
897         registerForPhoneStates();
898     }
899
900     @Override
901     protected void onNewIntent(Intent intent) {
902         if (DBG) log("onNewIntent: intent=" + intent);
903
904         // We're being re-launched with a new Intent.  Since we keep
905         // around a single InCallScreen instance for the life of the phone
906         // process (see finish()), this sequence will happen EVERY time
907         // there's a new incoming or outgoing call except for the very
908         // first time the InCallScreen gets created.  This sequence will
909         // also happen if the InCallScreen is already in the foreground
910         // (e.g. getting a new ACTION_CALL intent while we were already
911         // using the other line.)
912
913         // Stash away the new intent so that we can get it in the future
914         // by calling getIntent().  (Otherwise getIntent() will return the
915         // original Intent from when we first got created!)
916         setIntent(intent);
917
918         // Activities are always paused before receiving a new intent, so
919         // we can count on our onResume() method being called next.
920
921         // Just like in onCreate(), handle this intent, and stash the
922         // result code from internalResolveIntent() in the
923         // mInCallInitialStatus field.  If it's an error code, we'll
924         // handle it in onResume().
925         mInCallInitialStatus = internalResolveIntent(intent);
926         if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
927             Log.w(LOG_TAG, "onNewIntent: status " + mInCallInitialStatus
928                   + " from internalResolveIntent()");
929             // See onResume() for the actual error handling.
930         }
931     }
932
933     private InCallInitStatus internalResolveIntent(Intent intent) {
934         if (intent == null || intent.getAction() == null) {
935             return InCallInitStatus.SUCCESS;
936         }
937
938         String action = intent.getAction();
939         if (DBG) log("internalResolveIntent: action=" + action);
940
941         // The calls to setRestoreMuteOnInCallResume() inform the phone
942         // that we're dealing with new connections (either a placing an
943         // outgoing call or answering an incoming one, and NOT handling
944         // an aborted "Add Call" request), so we should let the mute state
945         // be handled by the PhoneUtils phone state change handler.
946         final PhoneApp app = PhoneApp.getInstance();
947         if (action.equals(Intent.ACTION_ANSWER)) {
948             internalAnswerCall();
949             app.setRestoreMuteOnInCallResume(false);
950             return InCallInitStatus.SUCCESS;
951         } else if (action.equals(Intent.ACTION_CALL)
952                 || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
953             app.setRestoreMuteOnInCallResume(false);
954             return placeCall(intent);
955         } else if (action.equals(intent.ACTION_MAIN)) {
956             // The MAIN action is used to bring up the in-call screen without
957             // doing any other explicit action, like when you return to the
958             // current call after previously bailing out of the in-call UI.
959             // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
960             // dialpad should be initially visible.  If the extra isn't
961             // present at all, we just leave the dialpad in its previous state.
962             if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
963                 boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
964                 if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA value = " + showDialpad);
965                 if (showDialpad) {
966                     mDialer.openDialer(false);  // no "opening" animation
967                 } else {
968                     mDialer.closeDialer(false);  // no "closing" animation
969                 }
970             }
971             return InCallInitStatus.SUCCESS;
972         } else {
973             Log.w(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
974             // But continue the best we can (basically treating this case
975             // like ACTION_MAIN...)
976             return InCallInitStatus.SUCCESS;
977         }
978     }
979
980     private void stopTimer() {
981         if (mCallCard != null) mCallCard.stopTimer();
982     }
983
984     private void initInCallScreen() {
985         if (VDBG) log("initInCallScreen()...");
986
987         // Have the WindowManager filter out touch events that are "too fat".
988         getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
989
990         mMainFrame = (ViewGroup) findViewById(R.id.mainFrame);
991         mInCallPanel = (ViewGroup) findViewById(R.id.inCallPanel);
992
993         ConfigurationHelper.initConfiguration(getResources().getConfiguration());
994
995         // Create a CallCard and add it to our View hierarchy.
996         // TODO: there's no good reason for call_card_popup to be a
997         // separate layout that we need to manually inflate here.
998         // (That design is left over from when the call card was drawn in
999         // its own PopupWindow.)
1000         // Instead, the CallCard should just be <include>d directly from
1001         // incall_screen.xml.
1002         View callCardLayout = getLayoutInflater().inflate(
1003                 R.layout.call_card_popup,
1004                 mInCallPanel);
1005         mCallCard = (CallCard) callCardLayout.findViewById(R.id.callCard);
1006         if (VDBG) log("  - mCallCard = " + mCallCard);
1007         mCallCard.setInCallScreenInstance(this);
1008
1009         // Menu Button hint
1010         mMenuButtonHint = (TextView) findViewById(R.id.menuButtonHint);
1011
1012         // Other platform-specific UI initialization.
1013         initOnscreenAnswerUi();
1014
1015         // Make any final updates to our View hierarchy that depend on the
1016         // current configuration.
1017         ConfigurationHelper.applyConfigurationToLayout(this);
1018     }
1019
1020     /**
1021      * Returns true if the phone is "in use", meaning that at least one line
1022      * is active (ie. off hook or ringing or dialing).  Conversely, a return
1023      * value of false means there's currently no phone activity at all.
1024      */
1025     private boolean phoneIsInUse() {
1026         return mPhone.getState() != Phone.State.IDLE;
1027     }
1028
1029     private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
1030         if (VDBG) log("handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
1031
1032         // As soon as the user starts typing valid dialable keys on the
1033         // keyboard (presumably to type DTMF tones) we start passing the
1034         // key events to the DTMFDialer's onDialerKeyDown.  We do so
1035         // only if the okToDialDTMFTones() conditions pass.
1036         if (okToDialDTMFTones()) {
1037             return mDialer.onDialerKeyDown(event);
1038         }
1039
1040         return false;
1041     }
1042
1043     /**
1044      * Handles a DOWN keypress on the BACK key.
1045      */
1046     private boolean handleBackKey() {
1047         if (VDBG) log("handleBackKey()...");
1048
1049         // While an incoming call is ringing, BACK behaves just like
1050         // ENDCALL: it stops the ringing and rejects the current call.
1051         final CallNotifier notifier = PhoneApp.getInstance().notifier;
1052         if (notifier.isRinging()) {
1053             if (DBG) log("BACK key while ringing: reject the call");
1054             internalHangupRingingCall();
1055
1056             // Don't consume the key; instead let the BACK event *also*
1057             // get handled normally by the framework (which presumably
1058             // will cause us to exit out of this activity.)
1059             return false;
1060         }
1061
1062         // BACK is also used to exit out of any "special modes" of the
1063         // in-call UI:
1064
1065         if (mDialer.isOpened()) {
1066             // Take down the "touch lock" overlay *immediately* to let the
1067             // user clearly see the DTMF dialpad's closing animation.
1068             enableTouchLock(false);
1069
1070             mDialer.closeDialer(true);  // do the "closing" animation
1071             return true;
1072         }
1073
1074         if (mInCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
1075             // Hide the Manage Conference panel, return to NORMAL mode.
1076             setInCallScreenMode(InCallScreenMode.NORMAL);
1077             return true;
1078         }
1079
1080         return false;
1081     }
1082
1083     /**
1084      * Handles the green CALL key while in-call.
1085      * @return true if we consumed the event.
1086      */
1087     private boolean handleCallKey() {
1088         // The green CALL button means either "Answer", "Unhold", or
1089         // "Swap calls", or can be a no-op, depending on the current state
1090         // of the Phone.
1091
1092         final boolean hasRingingCall = !mRingingCall.isIdle();
1093         final boolean hasActiveCall = !mForegroundCall.isIdle();
1094         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
1095
1096         if (mPhone.getPhoneName().equals("CDMA")) {
1097             // The green CALL button means either "Answer", "Swap calls/On Hold", or
1098             // "Add to 3WC", depending on the current state of the Phone.
1099
1100             PhoneApp app = PhoneApp.getInstance();
1101             CdmaPhoneCallState.PhoneCallState currCallState =
1102                 app.cdmaPhoneCallState.getCurrentCallState();
1103             if (hasRingingCall) {
1104                 //Scenario 1: Accepting the First Incoming and Call Waiting call
1105                 if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
1106                 internalAnswerCall();  // Automatically holds the current active call,
1107                                        // if there is one
1108             } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1109                     && (hasActiveCall)) {
1110                 //Scenario 2: Merging 3Way calls
1111                 if (DBG) log("answerCall: Merge 3-way call scenario");
1112                 // Merge calls
1113                 PhoneUtils.mergeCalls(mPhone);
1114             } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1115                 //Scenario 3: Switching between two Call waiting calls or drop the latest
1116                 // connection if in a 3Way merge scenario
1117                 if (DBG) log("answerCall: Switch btwn 2 calls scenario");
1118                 // Send flash cmd
1119                 PhoneUtils.switchHoldingAndActive(mPhone);
1120             }
1121         } else { // GSM.
1122             if (hasRingingCall) {
1123                 // If an incoming call is ringing, the CALL button is actually
1124                 // handled by the PhoneWindowManager.  (We do this to make
1125                 // sure that we'll respond to the key even if the InCallScreen
1126                 // hasn't come to the foreground yet.)
1127                 //
1128                 // We'd only ever get here in the extremely rare case that the
1129                 // incoming call started ringing *after*
1130                 // PhoneWindowManager.interceptKeyTq() but before the event
1131                 // got here, or else if the PhoneWindowManager had some
1132                 // problem connecting to the ITelephony service.
1133                 Log.w(LOG_TAG, "handleCallKey: incoming call is ringing!"
1134                       + " (PhoneWindowManager should have handled this key.)");
1135                 // But go ahead and handle the key as normal, since the
1136                 // PhoneWindowManager presumably did NOT handle it:
1137
1138                 // There's an incoming ringing call: CALL means "Answer".
1139                 if (hasActiveCall && hasHoldingCall) {
1140                     if (DBG) log("handleCallKey: ringing (both lines in use) ==> answer!");
1141                     internalAnswerCallBothLinesInUse();
1142                 } else {
1143                     if (DBG) log("handleCallKey: ringing ==> answer!");
1144                     internalAnswerCall();  // Automatically holds the current active call,
1145                                            // if there is one
1146                 }
1147             } else if (hasActiveCall && hasHoldingCall) {
1148                 // Two lines are in use: CALL means "Swap calls".
1149                 if (DBG) log("handleCallKey: both lines in use ==> swap calls.");
1150                 internalSwapCalls();
1151             } else if (hasHoldingCall) {
1152                 // There's only one line in use, AND it's on hold.
1153                 // In this case CALL is a shortcut for "unhold".
1154                 if (DBG) log("handleCallKey: call on hold ==> unhold.");
1155                 PhoneUtils.switchHoldingAndActive(mPhone);  // Really means "unhold" in this state
1156             } else {
1157                 // The most common case: there's only one line in use, and
1158                 // it's an active call (i.e. it's not on hold.)
1159                 // In this case CALL is a no-op.
1160                 // (This used to be a shortcut for "add call", but that was a
1161                 // bad idea because "Add call" is so infrequently-used, and
1162                 // because the user experience is pretty confusing if you
1163                 // inadvertently trigger it.)
1164                 if (VDBG) log("handleCallKey: call in foregound ==> ignoring.");
1165                 // But note we still consume this key event; see below.
1166             }
1167         }
1168
1169         // We *always* consume the CALL key, since the system-wide default
1170         // action ("go to the in-call screen") is useless here.
1171         return true;
1172     }
1173
1174     boolean isKeyEventAcceptableDTMF (KeyEvent event) {
1175         return (mDialer != null && mDialer.isKeyEventAcceptable(event));
1176     }
1177
1178     /**
1179      * Overriden to track relevant focus changes.
1180      *
1181      * If a key is down and some time later the focus changes, we may
1182      * NOT recieve the keyup event; logically the keyup event has not
1183      * occured in this window.  This issue is fixed by treating a focus
1184      * changed event as an interruption to the keydown, making sure
1185      * that any code that needs to be run in onKeyUp is ALSO run here.
1186      *
1187      * Note, this focus change event happens AFTER the in-call menu is
1188      * displayed, so mIsMenuDisplayed should always be correct by the
1189      * time this method is called in the framework, please see:
1190      * {@link onCreatePanelView}, {@link onOptionsMenuClosed}
1191      */
1192     @Override
1193     public void onWindowFocusChanged(boolean hasFocus) {
1194         // the dtmf tones should no longer be played
1195         if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
1196         if (!hasFocus && mDialer != null) {
1197             if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
1198             mDialer.onDialerKeyUp(null);
1199         }
1200     }
1201
1202     @Override
1203     public boolean dispatchKeyEvent(KeyEvent event) {
1204         // if (DBG) log("dispatchKeyEvent(event " + event + ")...");
1205
1206         // Intercept some events before they get dispatched to our views.
1207         switch (event.getKeyCode()) {
1208             case KeyEvent.KEYCODE_DPAD_CENTER:
1209             case KeyEvent.KEYCODE_DPAD_UP:
1210             case KeyEvent.KEYCODE_DPAD_DOWN:
1211             case KeyEvent.KEYCODE_DPAD_LEFT:
1212             case KeyEvent.KEYCODE_DPAD_RIGHT:
1213                 // Disable DPAD keys and trackball clicks if the touch lock
1214                 // overlay is up, since "touch lock" really means "disable
1215                 // the DTMF dialpad" (rather than only disabling touch events.)
1216                 if (mDialer.isOpened() && isTouchLocked()) {
1217                     if (DBG) log("- ignoring DPAD event while touch-locked...");
1218                     return true;
1219                 }
1220                 break;
1221
1222             default:
1223                 break;
1224         }
1225
1226         return super.dispatchKeyEvent(event);
1227     }
1228
1229     @Override
1230     public boolean onKeyUp(int keyCode, KeyEvent event) {
1231         // if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
1232
1233         // push input to the dialer.
1234         if ((mDialer != null) && (mDialer.onDialerKeyUp(event))){
1235             return true;
1236         } else if (keyCode == KeyEvent.KEYCODE_CALL) {
1237             // Always consume CALL to be sure the PhoneWindow won't do anything with it
1238             return true;
1239         }
1240         return super.onKeyUp(keyCode, event);
1241     }
1242
1243     @Override
1244     public boolean onKeyDown(int keyCode, KeyEvent event) {
1245         // if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
1246
1247         switch (keyCode) {
1248             case KeyEvent.KEYCODE_CALL:
1249                 boolean handled = handleCallKey();
1250                 if (!handled) {
1251                     Log.w(LOG_TAG, "InCallScreen should always handle KEYCODE_CALL in onKeyDown");
1252                 }
1253                 // Always consume CALL to be sure the PhoneWindow won't do anything with it
1254                 return true;
1255
1256             // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
1257             // The standard system-wide handling of the ENDCALL key
1258             // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
1259             // already implements exactly what the UI spec wants,
1260             // namely (1) "hang up" if there's a current active call,
1261             // or (2) "don't answer" if there's a current ringing call.
1262
1263             case KeyEvent.KEYCODE_BACK:
1264                 if (handleBackKey()) {
1265                     return true;
1266                 }
1267                 break;
1268
1269             case KeyEvent.KEYCODE_CAMERA:
1270                 // Disable the CAMERA button while in-call since it's too
1271                 // easy to press accidentally.
1272                 return true;
1273
1274             case KeyEvent.KEYCODE_VOLUME_UP:
1275             case KeyEvent.KEYCODE_VOLUME_DOWN:
1276                 if (mPhone.getState() == Phone.State.RINGING) {
1277                     // If an incoming call is ringing, the VOLUME buttons are
1278                     // actually handled by the PhoneWindowManager.  (We do
1279                     // this to make sure that we'll respond to them even if
1280                     // the InCallScreen hasn't come to the foreground yet.)
1281                     //
1282                     // We'd only ever get here in the extremely rare case that the
1283                     // incoming call started ringing *after*
1284                     // PhoneWindowManager.interceptKeyTq() but before the event
1285                     // got here, or else if the PhoneWindowManager had some
1286                     // problem connecting to the ITelephony service.
1287                     Log.w(LOG_TAG, "VOLUME key: incoming call is ringing!"
1288                           + " (PhoneWindowManager should have handled this key.)");
1289                     // But go ahead and handle the key as normal, since the
1290                     // PhoneWindowManager presumably did NOT handle it:
1291
1292                     final CallNotifier notifier = PhoneApp.getInstance().notifier;
1293                     if (notifier.isRinging()) {
1294                         // ringer is actually playing, so silence it.
1295                         PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
1296                         if (DBG) log("VOLUME key: silence ringer");
1297                         notifier.silenceRinger();
1298                     }
1299
1300                     // As long as an incoming call is ringing, we always
1301                     // consume the VOLUME keys.
1302                     return true;
1303                 }
1304                 break;
1305
1306             case KeyEvent.KEYCODE_MENU:
1307                 // Special case for the MENU key: if the "touch lock"
1308                 // overlay is up (over the DTMF dialpad), allow MENU to
1309                 // dismiss the overlay just as if you had double-tapped
1310                 // the onscreen icon.
1311                 // (We do this because MENU is normally used to bring the
1312                 // UI back after the screen turns off, and the touch lock
1313                 // overlay "feels" very similar to the screen going off.
1314                 // This is also here to be "backward-compatibile" with the
1315                 // 1.0 behavior, where you *needed* to hit MENU to bring
1316                 // back the dialpad after 6 seconds of idle time.)
1317                 if (mDialer.isOpened() && isTouchLocked()) {
1318                     if (VDBG) log("- allowing MENU to dismiss touch lock overlay...");
1319                     // Take down the touch lock overlay, but post a
1320                     // message in the future to bring it back later.
1321                     enableTouchLock(false);
1322                     resetTouchLockTimer();
1323                     return true;
1324                 }
1325                 break;
1326
1327             case KeyEvent.KEYCODE_MUTE:
1328                 PhoneUtils.setMute(mPhone, !PhoneUtils.getMute(mPhone));
1329                 return true;
1330
1331             // Various testing/debugging features, enabled ONLY when VDBG == true.
1332             case KeyEvent.KEYCODE_SLASH:
1333                 if (VDBG) {
1334                     log("----------- InCallScreen View dump --------------");
1335                     // Dump starting from the top-level view of the entire activity:
1336                     Window w = this.getWindow();
1337                     View decorView = w.getDecorView();
1338                     decorView.debug();
1339                     return true;
1340                 }
1341                 break;
1342             case KeyEvent.KEYCODE_EQUALS:
1343                 if (VDBG) {
1344                     log("----------- InCallScreen call state dump --------------");
1345                     PhoneUtils.dumpCallState(mPhone);
1346                     return true;
1347                 }
1348                 break;
1349             case KeyEvent.KEYCODE_GRAVE:
1350                 if (VDBG) {
1351                     // Placeholder for other misc temp testing
1352                     log("------------ Temp testing -----------------");
1353                     return true;
1354                 }
1355                 break;
1356         }
1357
1358         if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
1359             return true;
1360         }
1361
1362         return super.onKeyDown(keyCode, event);
1363     }
1364
1365     /**
1366      * Handle a failure notification for a supplementary service
1367      * (i.e. conference, switch, separate, transfer, etc.).
1368      */
1369     void onSuppServiceFailed(AsyncResult r) {
1370         Phone.SuppService service = (Phone.SuppService) r.result;
1371         if (DBG) log("onSuppServiceFailed: " + service);
1372
1373         int errorMessageResId;
1374         switch (service) {
1375             case SWITCH:
1376                 // Attempt to switch foreground and background/incoming calls failed
1377                 // ("Failed to switch calls")
1378                 errorMessageResId = R.string.incall_error_supp_service_switch;
1379                 break;
1380
1381             case SEPARATE:
1382                 // Attempt to separate a call from a conference call
1383                 // failed ("Failed to separate out call")
1384                 errorMessageResId = R.string.incall_error_supp_service_separate;
1385                 break;
1386
1387             case TRANSFER:
1388                 // Attempt to connect foreground and background calls to
1389                 // each other (and hanging up user's line) failed ("Call
1390                 // transfer failed")
1391                 errorMessageResId = R.string.incall_error_supp_service_transfer;
1392                 break;
1393
1394             case CONFERENCE:
1395                 // Attempt to add a call to conference call failed
1396                 // ("Conference call failed")
1397                 errorMessageResId = R.string.incall_error_supp_service_conference;
1398                 break;
1399
1400             case REJECT:
1401                 // Attempt to reject an incoming call failed
1402                 // ("Call rejection failed")
1403                 errorMessageResId = R.string.incall_error_supp_service_reject;
1404                 break;
1405
1406             case HANGUP:
1407                 // Attempt to release a call failed ("Failed to release call(s)")
1408                 errorMessageResId = R.string.incall_error_supp_service_hangup;
1409                 break;
1410
1411             case UNKNOWN:
1412             default:
1413                 // Attempt to use a service we don't recognize or support
1414                 // ("Unsupported service" or "Selected service failed")
1415                 errorMessageResId = R.string.incall_error_supp_service_unknown;
1416                 break;
1417         }
1418
1419         // mSuppServiceFailureDialog is a generic dialog used for any
1420         // supp service failure, and there's only ever have one
1421         // instance at a time.  So just in case a previous dialog is
1422         // still around, dismiss it.
1423         if (mSuppServiceFailureDialog != null) {
1424             if (DBG) log("- DISMISSING mSuppServiceFailureDialog.");
1425             mSuppServiceFailureDialog.dismiss();  // It's safe to dismiss() a dialog
1426                                                   // that's already dismissed.
1427             mSuppServiceFailureDialog = null;
1428         }
1429
1430         mSuppServiceFailureDialog = new AlertDialog.Builder(this)
1431                 .setMessage(errorMessageResId)
1432                 .setPositiveButton(R.string.ok, null)
1433                 .setCancelable(true)
1434                 .create();
1435         mSuppServiceFailureDialog.getWindow().addFlags(
1436                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1437         mSuppServiceFailureDialog.show();
1438     }
1439
1440     /**
1441      * Something has changed in the phone's state.  Update the UI.
1442      */
1443     private void onPhoneStateChanged(AsyncResult r) {
1444         if (VDBG) log("onPhoneStateChanged()...");
1445
1446         // There's nothing to do here if we're not the foreground activity.
1447         // (When we *do* eventually come to the foreground, we'll do a
1448         // full update then.)
1449         if (!mIsForegroundActivity) {
1450             if (VDBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
1451             return;
1452         }
1453
1454         updateScreen();
1455
1456         // Make sure we update the poke lock and wake lock when certain
1457         // phone state changes occur.
1458         PhoneApp.getInstance().updateWakeState();
1459     }
1460
1461     /**
1462      * Updates the UI after a phone connection is disconnected, as follows:
1463      *
1464      * - If this was a missed or rejected incoming call, and no other
1465      *   calls are active, dismiss the in-call UI immediately.  (The
1466      *   CallNotifier will still create a "missed call" notification if
1467      *   necessary.)
1468      *
1469      * - With any other disconnect cause, if the phone is now totally
1470      *   idle, display the "Call ended" state for a couple of seconds.
1471      *
1472      * - Or, if the phone is still in use, stay on the in-call screen
1473      *   (and update the UI to reflect the current state of the Phone.)
1474      *
1475      * @param r r.result contains the connection that just ended
1476      */
1477     private void onDisconnect(AsyncResult r) {
1478         Connection c = (Connection) r.result;
1479         Connection.DisconnectCause cause = c.getDisconnectCause();
1480         if (DBG) log("onDisconnect: " + c + ", cause=" + cause);
1481
1482         // Any time a call disconnects, clear out the "history" of DTMF
1483         // digits you typed (to make sure it doesn't persist from one call
1484         // to the next.)
1485         mDialer.clearDigits();
1486
1487         // Under certain call disconnected states, we want to alert the user
1488         // with a dialog instead of going through the normal disconnect
1489         // routine.
1490         if (cause == Connection.DisconnectCause.CALL_BARRED) {
1491             showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
1492             return;
1493         } else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
1494             showGenericErrorDialog(R.string.callFailed_fdn_only, false);
1495             return;
1496         } else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
1497             showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
1498             return;
1499         } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
1500             showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
1501             return;
1502         } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
1503             showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
1504             return;
1505         } else if (cause == Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE) {
1506             showGenericErrorDialog(R.string.callFailed_cdma_lockedUntilPowerCycle, false);
1507             return;
1508         } else if (cause == Connection.DisconnectCause.CDMA_DROP) {
1509             showGenericErrorDialog(R.string.callFailed_cdma_drop, false);
1510             return;
1511         } else if (cause == Connection.DisconnectCause.CDMA_INTERCEPT) {
1512             showGenericErrorDialog(R.string.callFailed_cdma_intercept, false);
1513             return;
1514         } else if (cause == Connection.DisconnectCause.CDMA_REORDER) {
1515             showGenericErrorDialog(R.string.callFailed_cdma_reorder, false);
1516             return;
1517         } else if (cause == Connection.DisconnectCause.CDMA_SO_REJECT) {
1518             showGenericErrorDialog(R.string.callFailed_cdma_SO_reject, false);
1519             return;
1520         }else if (cause == Connection.DisconnectCause.CDMA_RETRY_ORDER) {
1521             showGenericErrorDialog(R.string.callFailed_cdma_retryOrder, false);
1522             return;
1523         } else if (cause == Connection.DisconnectCause.CDMA_ACCESS_FAILURE) {
1524             showGenericErrorDialog(R.string.callFailed_cdma_accessFailure, false);
1525             return;
1526         } else if (cause == Connection.DisconnectCause.CDMA_PREEMPTED) {
1527             showGenericErrorDialog(R.string.callFailed_cdma_preempted, false);
1528             return;
1529         } else if (cause == Connection.DisconnectCause.CDMA_NOT_EMERGENCY) {
1530             showGenericErrorDialog(R.string.callFailed_cdma_notEmergency, false);
1531             return;
1532         }
1533
1534         final PhoneApp app = PhoneApp.getInstance();
1535         boolean currentlyIdle = !phoneIsInUse();
1536
1537         // Explicitly clean up up any DISCONNECTED connections
1538         // in a conference call.
1539         // [Background: Even after a connection gets disconnected, its
1540         // Connection object still stays around for a few seconds, in the
1541         // DISCONNECTED state.  With regular calls, this state drives the
1542         // "call ended" UI.  But when a single person disconnects from a
1543         // conference call there's no "call ended" state at all; in that
1544         // case we blow away any DISCONNECTED connections right now to make sure
1545         // the UI updates instantly to reflect the current state.]
1546         Call call = c.getCall();
1547         if (call != null) {
1548             // We only care about situation of a single caller
1549             // disconnecting from a conference call.  In that case, the
1550             // call will have more than one Connection (including the one
1551             // that just disconnected, which will be in the DISCONNECTED
1552             // state) *and* at least one ACTIVE connection.  (If the Call
1553             // has *no* ACTIVE connections, that means that the entire
1554             // conference call just ended, so we *do* want to show the
1555             // "Call ended" state.)
1556             List<Connection> connections = call.getConnections();
1557             if (connections != null && connections.size() > 1) {
1558                 for (Connection conn : connections) {
1559                     if (conn.getState() == Call.State.ACTIVE) {
1560                         // This call still has at least one ACTIVE connection!
1561                         // So blow away any DISCONNECTED connections
1562                         // (including, presumably, the one that just
1563                         // disconnected from this conference call.)
1564
1565                         // We also force the wake state to refresh, just in
1566                         // case the disconnected connections are removed
1567                         // before the phone state change.
1568                         if (VDBG) log("- Still-active conf call; clearing DISCONNECTED...");
1569                         app.updateWakeState();
1570                         mPhone.clearDisconnected();  // This happens synchronously.
1571                         break;
1572                     }
1573                 }
1574             }
1575         }
1576
1577         // Retrieve the emergency call retry count from this intent, in
1578         // case we need to retry the call again.
1579         int emergencyCallRetryCount = getIntent().getIntExtra(
1580                 EmergencyCallHandler.EMERGENCY_CALL_RETRY_KEY,
1581                 EmergencyCallHandler.INITIAL_ATTEMPT);
1582
1583         // Note: see CallNotifier.onDisconnect() for some other behavior
1584         // that might be triggered by a disconnect event, like playing the
1585         // busy/congestion tone.
1586
1587         // Keep track of whether this call was user-initiated or not.
1588         // (This affects where we take the user next; see delayedCleanupAfterDisconnect().)
1589         mShowCallLogAfterDisconnect = !c.isIncoming();
1590
1591         // We bail out immediately (and *don't* display the "call ended"
1592         // state at all) in a couple of cases, including those where we
1593         // are waiting for the radio to finish powering up for an
1594         // emergency call:
1595         boolean bailOutImmediately =
1596                 ((cause == Connection.DisconnectCause.INCOMING_MISSED)
1597                  || (cause == Connection.DisconnectCause.INCOMING_REJECTED)
1598                  || ((cause == Connection.DisconnectCause.OUT_OF_SERVICE)
1599                          && (emergencyCallRetryCount > 0)))
1600                 && currentlyIdle;
1601
1602         if (bailOutImmediately) {
1603             if (VDBG) log("- onDisconnect: bailOutImmediately...");
1604             // Exit the in-call UI!
1605             // (This is basically the same "delayed cleanup" we do below,
1606             // just with zero delay.  Since the Phone is currently idle,
1607             // this call is guaranteed to immediately finish() this activity.)
1608             delayedCleanupAfterDisconnect();
1609
1610             // Retry the call, by resending the intent to the emergency
1611             // call handler activity.
1612             if ((cause == Connection.DisconnectCause.OUT_OF_SERVICE)
1613                     && (emergencyCallRetryCount > 0)) {
1614                 startActivity(getIntent()
1615                         .setClassName(this, EmergencyCallHandler.class.getName()));
1616             }
1617         } else {
1618             if (VDBG) log("- onDisconnect: delayed bailout...");
1619             // Stay on the in-call screen for now.  (Either the phone is
1620             // still in use, or the phone is idle but we want to display
1621             // the "call ended" state for a couple of seconds.)
1622
1623             // Force a UI update in case we need to display anything
1624             // special given this connection's DisconnectCause (see
1625             // CallCard.getCallFailedString()).
1626             updateScreen();
1627
1628             // Display the special "Call ended" state when the phone is idle
1629             // but there's still a call in the DISCONNECTED state:
1630             if (currentlyIdle
1631                 && ((mForegroundCall.getState() == Call.State.DISCONNECTED)
1632                     || (mBackgroundCall.getState() == Call.State.DISCONNECTED))) {
1633                 if (VDBG) log("- onDisconnect: switching to 'Call ended' state...");
1634                 setInCallScreenMode(InCallScreenMode.CALL_ENDED);
1635             }
1636
1637             // Some other misc cleanup that we do if the call that just
1638             // disconnected was the foreground call.
1639             final boolean hasActiveCall = !mForegroundCall.isIdle();
1640             if (!hasActiveCall) {
1641                 if (VDBG) log("- onDisconnect: cleaning up after FG call disconnect...");
1642
1643                 // Dismiss any dialogs which are only meaningful for an
1644                 // active call *and* which become moot if the call ends.
1645                 if (mWaitPromptDialog != null) {
1646                     if (VDBG) log("- DISMISSING mWaitPromptDialog.");
1647                     mWaitPromptDialog.dismiss();  // safe even if already dismissed
1648                     mWaitPromptDialog = null;
1649                 }
1650                 if (mWildPromptDialog != null) {
1651                     if (VDBG) log("- DISMISSING mWildPromptDialog.");
1652                     mWildPromptDialog.dismiss();  // safe even if already dismissed
1653                     mWildPromptDialog = null;
1654                 }
1655             }
1656
1657             // Updating the screen wake state is done in onPhoneStateChanged().
1658
1659             // Finally, arrange for delayedCleanupAfterDisconnect() to get
1660             // called after a short interval (during which we display the
1661             // "call ended" state.)  At that point, if the
1662             // Phone is idle, we'll finish() out of this activity.
1663             int callEndedDisplayDelay =
1664                     (cause == Connection.DisconnectCause.LOCAL)
1665                     ? CALL_ENDED_SHORT_DELAY : CALL_ENDED_LONG_DELAY;
1666             mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
1667             mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
1668                                              callEndedDisplayDelay);
1669         }
1670
1671         // Remove 3way timer (only meaningful for CDMA)
1672         mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
1673     }
1674
1675     /**
1676      * Brings up the "MMI Started" dialog.
1677      */
1678     private void onMMIInitiate(AsyncResult r) {
1679         if (VDBG) log("onMMIInitiate()...  AsyncResult r = " + r);
1680
1681         // Watch out: don't do this if we're not the foreground activity,
1682         // mainly since in the Dialog.show() might fail if we don't have a
1683         // valid window token any more...
1684         // (Note that this exact sequence can happen if you try to start
1685         // an MMI code while the radio is off or out of service.)
1686         if (!mIsForegroundActivity) {
1687             if (VDBG) log("Activity not in foreground! Bailing out...");
1688             return;
1689         }
1690
1691         // Also, if any other dialog is up right now (presumably the
1692         // generic error dialog displaying the "Starting MMI..."  message)
1693         // take it down before bringing up the real "MMI Started" dialog
1694         // in its place.
1695         dismissAllDialogs();
1696
1697         MmiCode mmiCode = (MmiCode) r.result;
1698         if (VDBG) log("  - MmiCode: " + mmiCode);
1699
1700         Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
1701         mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
1702                                                           message, mMmiStartedDialog);
1703     }
1704
1705     /**
1706      * Handles an MMI_CANCEL event, which is triggered by the button
1707      * (labeled either "OK" or "Cancel") on the "MMI Started" dialog.
1708      * @see onMMIInitiate
1709      * @see PhoneUtils.cancelMmiCode
1710      */
1711     private void onMMICancel() {
1712         if (VDBG) log("onMMICancel()...");
1713
1714         // First of all, cancel the outstanding MMI code (if possible.)
1715         PhoneUtils.cancelMmiCode(mPhone);
1716
1717         // Regardless of whether the current MMI code was cancelable, the
1718         // PhoneApp will get an MMI_COMPLETE event very soon, which will
1719         // take us to the MMI Complete dialog (see
1720         // PhoneUtils.displayMMIComplete().)
1721         //
1722         // But until that event comes in, we *don't* want to stay here on
1723         // the in-call screen, since we'll be visible in a
1724         // partially-constructed state as soon as the "MMI Started" dialog
1725         // gets dismissed.  So let's forcibly bail out right now.
1726         if (DBG) log("onMMICancel: finishing...");
1727         finish();
1728     }
1729
1730     /**
1731      * Handles the POST_ON_DIAL_CHARS message from the Phone
1732      * (see our call to mPhone.setOnPostDialCharacter() above.)
1733      *
1734      * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle
1735      * "dialable" key events here in the InCallScreen: we do directly to the
1736      * Dialer UI instead.  Similarly, we may now need to go directly to the
1737      * Dialer to handle POST_ON_DIAL_CHARS too.
1738      */
1739     private void handlePostOnDialChars(AsyncResult r, char ch) {
1740         Connection c = (Connection) r.result;
1741
1742         if (c != null) {
1743             Connection.PostDialState state =
1744                     (Connection.PostDialState) r.userObj;
1745
1746             if (VDBG) log("handlePostOnDialChar: state = " +
1747                     state + ", ch = " + ch);
1748
1749             switch (state) {
1750                 case STARTED:
1751                     // TODO: is this needed, now that you can't actually
1752                     // type DTMF chars or dial directly from here?
1753                     // If so, we'd need to yank you out of the in-call screen
1754                     // here too (and take you to the 12-key dialer in "in-call" mode.)
1755                     // displayPostDialedChar(ch);
1756                     break;
1757
1758                 case WAIT:
1759                     //if (DBG) log("show wait prompt...");
1760                     String postDialStr = c.getRemainingPostDialString();
1761                     showWaitPromptDialog(c, postDialStr);
1762                     break;
1763
1764                 case WILD:
1765                     //if (DBG) log("prompt user to replace WILD char");
1766                     showWildPromptDialog(c);
1767                     break;
1768
1769                 case COMPLETE:
1770                     break;
1771
1772                 default:
1773                     break;
1774             }
1775         }
1776     }
1777
1778     private void showWaitPromptDialog(final Connection c, String postDialStr) {
1779         Resources r = getResources();
1780         StringBuilder buf = new StringBuilder();
1781         buf.append(r.getText(R.string.wait_prompt_str));
1782         buf.append(postDialStr);
1783
1784         if (mWaitPromptDialog != null) {
1785             if (VDBG) log("- DISMISSING mWaitPromptDialog.");
1786             mWaitPromptDialog.dismiss();  // safe even if already dismissed
1787             mWaitPromptDialog = null;
1788         }
1789
1790         mWaitPromptDialog = new AlertDialog.Builder(this)
1791                 .setMessage(buf.toString())
1792                 .setPositiveButton(R.string.send_button, new DialogInterface.OnClickListener() {
1793                         public void onClick(DialogInterface dialog, int whichButton) {
1794                             if (VDBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
1795                             c.proceedAfterWaitChar();
1796                             PhoneApp.getInstance().pokeUserActivity();
1797                         }
1798                     })
1799                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
1800                         public void onCancel(DialogInterface dialog) {
1801                             if (VDBG) log("handle POST_DIAL_CANCELED!");
1802                             c.cancelPostDial();
1803                             PhoneApp.getInstance().pokeUserActivity();
1804                         }
1805                     })
1806                 .create();
1807         mWaitPromptDialog.getWindow().addFlags(
1808                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1809         mWaitPromptDialog.show();
1810     }
1811
1812     private View createWildPromptView() {
1813         LinearLayout result = new LinearLayout(this);
1814         result.setOrientation(LinearLayout.VERTICAL);
1815         result.setPadding(5, 5, 5, 5);
1816
1817         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
1818                         ViewGroup.LayoutParams.FILL_PARENT,
1819                         ViewGroup.LayoutParams.WRAP_CONTENT);
1820
1821         TextView promptMsg = new TextView(this);
1822         promptMsg.setTextSize(14);
1823         promptMsg.setTypeface(Typeface.DEFAULT_BOLD);
1824         promptMsg.setText(getResources().getText(R.string.wild_prompt_str));
1825
1826         result.addView(promptMsg, lp);
1827
1828         mWildPromptText = new EditText(this);
1829         mWildPromptText.setKeyListener(DialerKeyListener.getInstance());
1830         mWildPromptText.setMovementMethod(null);
1831         mWildPromptText.setTextSize(14);
1832         mWildPromptText.setMaxLines(1);
1833         mWildPromptText.setHorizontallyScrolling(true);
1834         mWildPromptText.setBackgroundResource(android.R.drawable.editbox_background);
1835
1836         LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
1837                         ViewGroup.LayoutParams.FILL_PARENT,
1838                         ViewGroup.LayoutParams.WRAP_CONTENT);
1839         lp2.setMargins(0, 3, 0, 0);
1840
1841         result.addView(mWildPromptText, lp2);
1842
1843         return result;
1844     }
1845
1846     private void showWildPromptDialog(final Connection c) {
1847         View v = createWildPromptView();
1848
1849         if (mWildPromptDialog != null) {
1850             if (VDBG) log("- DISMISSING mWildPromptDialog.");
1851             mWildPromptDialog.dismiss();  // safe even if already dismissed
1852             mWildPromptDialog = null;
1853         }
1854
1855         mWildPromptDialog = new AlertDialog.Builder(this)
1856                 .setView(v)
1857                 .setPositiveButton(
1858                         R.string.send_button,
1859                         new DialogInterface.OnClickListener() {
1860                             public void onClick(DialogInterface dialog, int whichButton) {
1861                                 if (VDBG) log("handle WILD_PROMPT_CHAR_ENTERED, proceed...");
1862                                 String replacement = null;
1863                                 if (mWildPromptText != null) {
1864                                     replacement = mWildPromptText.getText().toString();
1865                                     mWildPromptText = null;
1866                                 }
1867                                 c.proceedAfterWildChar(replacement);
1868                                 PhoneApp.getInstance().pokeUserActivity();
1869                             }
1870                         })
1871                 .setOnCancelListener(
1872                         new DialogInterface.OnCancelListener() {
1873                             public void onCancel(DialogInterface dialog) {
1874                                 if (VDBG) log("handle POST_DIAL_CANCELED!");
1875                                 c.cancelPostDial();
1876                                 PhoneApp.getInstance().pokeUserActivity();
1877                             }
1878                         })
1879                 .create();
1880         mWildPromptDialog.getWindow().addFlags(
1881                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1882         mWildPromptDialog.show();
1883
1884         mWildPromptText.requestFocus();
1885     }
1886
1887     /**
1888      * Updates the state of the in-call UI based on the current state of
1889      * the Phone.
1890      */
1891     private void updateScreen() {
1892         if (VDBG) log("updateScreen()...");
1893
1894         // Don't update anything if we're not in the foreground (there's
1895         // no point updating our UI widgets since we're not visible!)
1896         // Also note this check also ensures we won't update while we're
1897         // in the middle of pausing, which could cause a visible glitch in
1898         // the "activity ending" transition.
1899         if (!mIsForegroundActivity) {
1900             if (VDBG) log("- updateScreen: not the foreground Activity! Bailing out...");
1901             return;
1902         }
1903
1904         // Update the state of the in-call menu items.
1905         if (mInCallMenu != null) {
1906             // TODO: do this only if the menu is visible!
1907             if (VDBG) log("- updateScreen: updating menu items...");
1908             boolean okToShowMenu = mInCallMenu.updateItems(mPhone);
1909             if (!okToShowMenu) {
1910                 // Uh oh: we were only trying to update the state of the
1911                 // menu items, but the logic in InCallMenu.updateItems()
1912                 // just decided the menu shouldn't be visible at all!
1913                 // (That's probably means that the call ended
1914                 // asynchronously while the menu was up.)
1915                 //
1916                 // So take the menu down ASAP.
1917                 if (VDBG) log("- updateScreen: Tried to update menu; now need to dismiss!");
1918                 // dismissMenu() has no effect if the menu is already closed.
1919                 dismissMenu(true);  // dismissImmediate = true
1920             }
1921         }
1922
1923         if (mInCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
1924             if (VDBG) log("- updateScreen: manage conference mode (NOT updating in-call UI)...");
1925             updateManageConferencePanelIfNecessary();
1926             return;
1927         } else if (mInCallScreenMode == InCallScreenMode.CALL_ENDED) {
1928             if (VDBG) log("- updateScreen: call ended state (NOT updating in-call UI)...");
1929             return;
1930         }
1931
1932         if (VDBG) log("- updateScreen: updating the in-call UI...");
1933         mCallCard.updateState(mPhone);
1934         updateDialpadVisibility();
1935         updateOnscreenAnswerUi();
1936         updateMenuButtonHint();
1937     }
1938
1939     /**
1940      * (Re)synchronizes the onscreen UI with the current state of the
1941      * Phone.
1942      *
1943      * @return InCallInitStatus.SUCCESS if we successfully updated the UI, or
1944      *    InCallInitStatus.PHONE_NOT_IN_USE if there was no phone state to sync
1945      *    with (ie. the phone was completely idle).  In the latter case, we
1946      *    shouldn't even be in the in-call UI in the first place, and it's
1947      *    the caller's responsibility to bail out of this activity (by
1948      *    calling finish()) if appropriate.
1949      */
1950     private InCallInitStatus syncWithPhoneState() {
1951         boolean updateSuccessful = false;
1952         if (DBG) log("syncWithPhoneState()...");
1953         if (VDBG) PhoneUtils.dumpCallState(mPhone);
1954         if (VDBG) dumpBluetoothState();
1955
1956         // Make sure the Phone is "in use".  (If not, we shouldn't be on
1957         // this screen in the first place.)
1958
1959         // Need to treat running MMI codes as a connection as well.
1960         // Do not check for getPendingMmiCodes when phone is a CDMA phone
1961         if (!mForegroundCall.isIdle() || !mBackgroundCall.isIdle() || !mRingingCall.isIdle()
1962             || mPhone.getPhoneName().equals("CDMA") || !mPhone.getPendingMmiCodes().isEmpty()) {
1963             if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");
1964             updateScreen();
1965             return InCallInitStatus.SUCCESS;
1966         }
1967
1968         if (DBG) log("syncWithPhoneState: phone is idle; we shouldn't be here!");
1969         return InCallInitStatus.PHONE_NOT_IN_USE;
1970     }
1971
1972     /**
1973      * Given the Intent we were initially launched with,
1974      * figure out the actual phone number we should dial.
1975      *
1976      * @return the phone number corresponding to the
1977      *   specified Intent, or null if the Intent is not
1978      *   a ACTION_CALL intent or if the intent's data is
1979      *   malformed or missing.
1980      *
1981      * @throws VoiceMailNumberMissingException if the intent
1982      *   contains a "voicemail" URI, but there's no voicemail
1983      *   number configured on the device.
1984      */
1985     private String getInitialNumber(Intent intent)
1986             throws PhoneUtils.VoiceMailNumberMissingException {
1987         String action = intent.getAction();
1988
1989         if (action == null) {
1990             return null;
1991         }
1992
1993         if (action != null && action.equals(Intent.ACTION_CALL) &&
1994                 intent.hasExtra(Intent.EXTRA_PHONE_NUMBER)) {
1995             return intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
1996         }
1997
1998         return PhoneUtils.getNumberFromIntent(this, mPhone, intent);
1999     }
2000
2001     /**
2002      * Make a call to whomever the intent tells us to.
2003      *
2004      * @param intent the Intent we were launched with
2005      * @return InCallInitStatus.SUCCESS if we successfully initiated an
2006      *    outgoing call.  If there was some kind of failure, return one of
2007      *    the other InCallInitStatus codes indicating what went wrong.
2008      */
2009     private InCallInitStatus placeCall(Intent intent) {
2010         if (VDBG) log("placeCall()...  intent = " + intent);
2011
2012         String number;
2013
2014         // Check the current ServiceState to make sure it's OK
2015         // to even try making a call.
2016         InCallInitStatus okToCallStatus = checkIfOkToInitiateOutgoingCall();
2017
2018         try {
2019             number = getInitialNumber(intent);
2020         } catch (PhoneUtils.VoiceMailNumberMissingException ex) {
2021             // If the call status is NOT in an acceptable state, it
2022             // may effect the way the voicemail number is being
2023             // retrieved.  Mask the VoiceMailNumberMissingException
2024             // with the underlying issue of the phone state.
2025             if (okToCallStatus != InCallInitStatus.SUCCESS) {
2026                 if (DBG) log("Voicemail number not reachable in current SIM card state.");
2027                 return okToCallStatus;
2028             }
2029             if (DBG) log("VoiceMailNumberMissingException from getInitialNumber()");
2030             return InCallInitStatus.VOICEMAIL_NUMBER_MISSING;
2031         }
2032
2033         if (number == null) {
2034             Log.w(LOG_TAG, "placeCall: couldn't get a phone number from Intent " + intent);
2035             return InCallInitStatus.NO_PHONE_NUMBER_SUPPLIED;
2036         }
2037
2038         boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(number);
2039         boolean isEmergencyIntent = Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction());
2040
2041         if (isEmergencyNumber && !isEmergencyIntent) {
2042             Log.e(LOG_TAG, "Non-CALL_EMERGENCY Intent " + intent
2043                     + " attempted to call emergency number " + number
2044                     + ".");
2045             return InCallInitStatus.CALL_FAILED;
2046         } else if (!isEmergencyNumber && isEmergencyIntent) {
2047             Log.e(LOG_TAG, "Received CALL_EMERGENCY Intent " + intent
2048                     + " with non-emergency number " + number
2049                     + " -- failing call.");
2050             return InCallInitStatus.CALL_FAILED;
2051         }
2052
2053         // need to make sure that the state is adjusted if we are ONLY
2054         // allowed to dial emergency numbers AND we encounter an
2055         // emergency number request.
2056         if (isEmergencyNumber && okToCallStatus == InCallInitStatus.EMERGENCY_ONLY) {
2057             okToCallStatus = InCallInitStatus.SUCCESS;
2058             if (DBG) log("Emergency number detected, changing state to: " + okToCallStatus);
2059         }
2060
2061         if (okToCallStatus != InCallInitStatus.SUCCESS) {
2062             // If this is an emergency call, we call the emergency call
2063             // handler activity to turn on the radio and do whatever else
2064             // is needed. For now, we finish the InCallScreen (since were
2065             // expecting a callback when the emergency call handler dictates
2066             // it) and just return the success state.
2067             if (isEmergencyNumber && (okToCallStatus == InCallInitStatus.POWER_OFF)) {
2068                 startActivity(intent.setClassName(this, EmergencyCallHandler.class.getName()));
2069                 if (DBG) log("placeCall: starting EmergencyCallHandler, finishing...");
2070                 finish();
2071                 return InCallInitStatus.SUCCESS;
2072             } else {
2073                 return okToCallStatus;
2074             }
2075         }
2076
2077         // We have a valid number, so try to actually place a call:
2078         //make sure we pass along the URI as a reference to the contact.
2079         int callStatus = PhoneUtils.placeCall(mPhone, number, intent.getData());
2080         switch (callStatus) {
2081             case PhoneUtils.CALL_STATUS_DIALED:
2082                 if (VDBG) log("placeCall: PhoneUtils.placeCall() succeeded for regular call '"
2083                              + number + "'.");
2084
2085                 // Any time we initiate a call, force the DTMF dialpad to
2086                 // close.  (We want to make sure the user can see the regular
2087                 // in-call UI while the new call is dialing, and when it
2088                 // first gets connected.)
2089                 mDialer.closeDialer(false);  // no "closing" animation
2090
2091                 // Also, in case a previous call was already active (i.e. if
2092                 // we just did "Add call"), clear out the "history" of DTMF
2093                 // digits you typed, to make sure it doesn't persist from the
2094                 // previous call to the new call.
2095                 // TODO: it would be more precise to do this when the actual
2096                 // phone state change happens (i.e. when a new foreground
2097                 // call appears and the previous call moves to the
2098                 // background), but the InCallScreen doesn't keep enough
2099                 // state right now to notice that specific transition in
2100                 // onPhoneStateChanged().
2101                 mDialer.clearDigits();
2102
2103                 PhoneApp app = PhoneApp.getInstance();
2104                 if (app.phone.getPhoneName().equals("CDMA")) {
2105                     // Start the 2 second timer for 3 Way CallerInfo
2106                     if (app.cdmaPhoneCallState.getCurrentCallState()
2107                             == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
2108                         //Unmute for the second MO call
2109                         PhoneUtils.setMuteInternal(mPhone, false);
2110
2111                         //Start the timer for displaying "Dialing" for second call
2112                         Message msg = Message.obtain(mHandler, THREEWAY_CALLERINFO_DISPLAY_DONE);
2113                         mHandler.sendMessageDelayed(msg, THREEWAY_CALLERINFO_DISPLAY_TIME);
2114
2115                         // Set the mThreeWayCallOrigStateDialing state to true
2116                         app.cdmaPhoneCallState.setThreeWayCallOrigState(true);
2117
2118                         //Update screen to show 3way dialing
2119                         updateScreen();
2120                     }
2121                 }
2122
2123                 return InCallInitStatus.SUCCESS;
2124             case PhoneUtils.CALL_STATUS_DIALED_MMI:
2125                 if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");
2126                 // The passed-in number was an MMI code, not a regular phone number!
2127                 // This isn't really a failure; the Dialer may have deliberately
2128                 // fired a ACTION_CALL intent to dial an MMI code, like for a
2129                 // USSD call.
2130                 //
2131                 // Presumably an MMI_INITIATE message will come in shortly
2132                 // (and we'll bring up the "MMI Started" dialog), or else
2133                 // an MMI_COMPLETE will come in (which will take us to a
2134                 // different Activity; see PhoneUtils.displayMMIComplete()).
2135                 return InCallInitStatus.DIALED_MMI;
2136             case PhoneUtils.CALL_STATUS_FAILED:
2137                 Log.w(LOG_TAG, "placeCall: PhoneUtils.placeCall() FAILED for number '"
2138                       + number + "'.");
2139                 // We couldn't successfully place the call; there was some
2140                 // failure in the telephony layer.
2141                 return InCallInitStatus.CALL_FAILED;
2142             default:
2143                 Log.w(LOG_TAG, "placeCall: unknown callStatus " + callStatus
2144                       + " from PhoneUtils.placeCall() for number '" + number + "'.");
2145                 return InCallInitStatus.SUCCESS;  // Try to continue anyway...
2146         }
2147     }
2148
2149     /**
2150      * Checks the current ServiceState to make sure it's OK
2151      * to try making an outgoing call to the specified number.
2152      *
2153      * @return InCallInitStatus.SUCCESS if it's OK to try calling the specified
2154      *    number.  If not, like if the radio is powered off or we have no
2155      *    signal, return one of the other InCallInitStatus codes indicating what
2156      *    the problem is.
2157      */
2158     private InCallInitStatus checkIfOkToInitiateOutgoingCall() {
2159         // Watch out: do NOT use PhoneStateIntentReceiver.getServiceState() here;
2160         // that's not guaranteed to be fresh.  To synchronously get the
2161         // CURRENT service state, ask the Phone object directly:
2162         int state = mPhone.getServiceState().getState();
2163         if (VDBG) log("checkIfOkToInitiateOutgoingCall: ServiceState = " + state);
2164
2165         switch (state) {
2166             case ServiceState.STATE_IN_SERVICE:
2167                 // Normal operation.  It's OK to make outgoing calls.
2168                 return InCallInitStatus.SUCCESS;
2169
2170
2171             case ServiceState.STATE_POWER_OFF:
2172                 // Radio is explictly powered off.
2173                 return InCallInitStatus.POWER_OFF;
2174
2175             case ServiceState.STATE_OUT_OF_SERVICE:
2176             case ServiceState.STATE_EMERGENCY_ONLY:
2177                 // The phone is registered, but locked. Only emergency
2178                 // numbers are allowed.
2179                 return InCallInitStatus.EMERGENCY_ONLY;
2180             default:
2181                 throw new IllegalStateException("Unexpected ServiceState: " + state);
2182         }
2183     }
2184
2185     private void handleMissingVoiceMailNumber() {
2186         if (DBG) log("handleMissingVoiceMailNumber");
2187
2188         final Message msg = Message.obtain(mHandler);
2189         msg.what = DONT_ADD_VOICEMAIL_NUMBER;
2190
2191         final Message msg2 = Message.obtain(mHandler);
2192         msg2.what = ADD_VOICEMAIL_NUMBER;
2193
2194         mMissingVoicemailDialog = new AlertDialog.Builder(this)
2195                 .setTitle(R.string.no_vm_number)
2196                 .setMessage(R.string.no_vm_number_msg)
2197                 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2198                         public void onClick(DialogInterface dialog, int which) {
2199                             if (VDBG) log("Missing voicemail AlertDialog: POSITIVE click...");
2200                             msg.sendToTarget();  // see dontAddVoiceMailNumber()
2201                             PhoneApp.getInstance().pokeUserActivity();
2202                         }})
2203                 .setNegativeButton(R.string.add_vm_number_str,
2204                                    new DialogInterface.OnClickListener() {
2205                         public void onClick(DialogInterface dialog, int which) {
2206                             if (VDBG) log("Missing voicemail AlertDialog: NEGATIVE click...");
2207                             msg2.sendToTarget();  // see addVoiceMailNumber()
2208                             PhoneApp.getInstance().pokeUserActivity();
2209                         }})
2210                 .setOnCancelListener(new OnCancelListener() {
2211                         public void onCancel(DialogInterface dialog) {
2212                             if (VDBG) log("Missing voicemail AlertDialog: CANCEL handler...");
2213                             msg.sendToTarget();  // see dontAddVoiceMailNumber()
2214                             PhoneApp.getInstance().pokeUserActivity();
2215                         }})
2216                 .create();
2217
2218         // When the dialog is up, completely hide the in-call UI
2219         // underneath (which is in a partially-constructed state).
2220         mMissingVoicemailDialog.getWindow().addFlags(
2221                 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
2222
2223         mMissingVoicemailDialog.show();
2224     }
2225
2226     private void addVoiceMailNumberPanel() {
2227         if (mMissingVoicemailDialog != null) {
2228             mMissingVoicemailDialog.dismiss();
2229             mMissingVoicemailDialog = null;
2230         }
2231         finish();
2232
2233         if (DBG) log("show vm setting");
2234
2235         // navigate to the Voicemail setting in the Call Settings activity.
2236         Intent intent = new Intent(CallFeaturesSetting.ACTION_ADD_VOICEMAIL);
2237         intent.setClass(this, CallFeaturesSetting.class);
2238         startActivity(intent);
2239     }
2240
2241     private void dontAddVoiceMailNumber() {
2242         if (mMissingVoicemailDialog != null) {
2243             mMissingVoicemailDialog.dismiss();
2244             mMissingVoicemailDialog = null;
2245         }
2246         if (DBG) log("dontAddVoiceMailNumber: finishing...");
2247         finish();
2248     }
2249
2250     /**
2251      * Do some delayed cleanup after a Phone call gets disconnected.
2252      *
2253      * This method gets called a couple of seconds after any DISCONNECT
2254      * event from the Phone; it's triggered by the
2255      * DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
2256      *
2257      * If the Phone is totally idle right now, that means we've already
2258      * shown the "call ended" state for a couple of seconds, and it's now
2259      * time to finish() this activity.
2260      *
2261      * If the Phone is *not* idle right now, that probably means that one
2262      * call ended but the other line is still in use.  In that case, we
2263      * *don't* exit the in-call screen, but we at least turn off the
2264      * backlight (which we turned on in onDisconnect().)
2265      */
2266     private void delayedCleanupAfterDisconnect() {
2267         if (VDBG) log("delayedCleanupAfterDisconnect()...  Phone state = " + mPhone.getState());
2268
2269         // Clean up any connections in the DISCONNECTED state.
2270         //
2271         // [Background: Even after a connection gets disconnected, its
2272         // Connection object still stays around, in the special
2273         // DISCONNECTED state.  This is necessary because we we need the
2274         // caller-id information from that Connection to properly draw the
2275         // "Call ended" state of the CallCard.
2276         //   But at this point we truly don't need that connection any
2277         // more, so tell the Phone that it's now OK to to clean up any
2278         // connections still in that state.]
2279         mPhone.clearDisconnected();
2280
2281         if (!phoneIsInUse()) {
2282             // Phone is idle!  We should exit this screen now.
2283             if (DBG) log("- delayedCleanupAfterDisconnect: phone is idle...");
2284
2285             // No need to re-enable keyguard or screen wake state here;
2286             // that happens in onPause() when we actually exit.
2287
2288             // And (finally!) exit from the in-call screen
2289             // (but not if we're already in the process of pausing...)
2290             if (mIsForegroundActivity) {
2291                 if (DBG) log("- delayedCleanupAfterDisconnect: finishing...");
2292
2293                 // If this is a call that was initiated by the user, and
2294                 // we're *not* in emergency mode, finish the call by
2295                 // taking the user to the Call Log.
2296                 // Otherwise we simply call finish(), which will take us
2297                 // back to wherever we came from.
2298                 if (mShowCallLogAfterDisconnect && !isPhoneStateRestricted()) {
2299                     if (VDBG) log("- Show Call Log after disconnect...");
2300                     final Intent intent = PhoneApp.createCallLogIntent();
2301                     startActivity(intent);
2302                     // Even in this case we still call finish() (below),
2303                     // to make sure we don't stay in the activity history.
2304                 }
2305
2306                 finish();
2307             }
2308         } else {
2309             // The phone is still in use.  Stay here in this activity, but
2310             // we don't need to keep the screen on.
2311             if (DBG) log("- delayedCleanupAfterDisconnect: staying on the InCallScreen...");
2312             if (DBG) PhoneUtils.dumpCallState(mPhone);
2313             // No need to re-enable keyguard or screen wake state here;
2314             // should be taken care of in onPhoneStateChanged();
2315         }
2316     }
2317
2318
2319     //
2320     // Callbacks for buttons / menu items.
2321     //
2322
2323     public void onClick(View view) {
2324         int id = view.getId();
2325         if (VDBG) log("onClick(View " + view + ", id " + id + ")...");
2326         if (VDBG && view instanceof InCallMenuItemView) {
2327             InCallMenuItemView item = (InCallMenuItemView) view;
2328             log("  ==> menu item! " + item);
2329         }
2330
2331         // Most menu items dismiss the menu immediately once you click
2332         // them.  But some items (the "toggle" buttons) are different:
2333         // they want the menu to stay visible for a second afterwards to
2334         // give you feedback about the state change.
2335         boolean dismissMenuImmediate = true;
2336         Context context = getApplicationContext();
2337
2338         switch (id) {
2339             case R.id.menuAnswerAndHold:
2340                 if (VDBG) log("onClick: AnswerAndHold...");
2341                 internalAnswerCall();  // Automatically holds the current active call
2342                 break;
2343
2344             case R.id.menuAnswerAndEnd:
2345                 if (VDBG) log("onClick: AnswerAndEnd...");
2346                 internalAnswerAndEnd();
2347                 break;
2348
2349             case R.id.menuAnswer:
2350                 if (DBG) log("onClick: Answer...");
2351                 internalAnswerCall();
2352                 break;
2353
2354             case R.id.menuIgnore:
2355                 if (DBG) log("onClick: Ignore...");
2356                 final CallNotifier notifier = PhoneApp.getInstance().notifier;
2357                 notifier.onCdmaCallWaitingReject();
2358                 break;
2359
2360             case R.id.menuSwapCalls:
2361                 if (VDBG) log("onClick: SwapCalls...");
2362                 internalSwapCalls();
2363                 break;
2364
2365             case R.id.menuMergeCalls:
2366                 if (VDBG) log("onClick: MergeCalls...");
2367                 PhoneUtils.mergeCalls(mPhone);
2368                 break;
2369
2370             case R.id.menuManageConference:
2371                 if (VDBG) log("onClick: ManageConference...");
2372                 // Show the Manage Conference panel.
2373                 setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
2374                 break;
2375
2376             case R.id.menuShowDialpad:
2377                 if (VDBG) log("onClick: Show/hide dialpad...");
2378                 if (mDialer.isOpened()) {
2379                     mDialer.closeDialer(true);  // do the "closing" animation
2380                 } else {
2381                     mDialer.openDialer(true);  // do the "opening" animation
2382                 }
2383                 break;
2384
2385             case R.id.manage_done:  // mButtonManageConferenceDone
2386                 if (VDBG) log("onClick: mButtonManageConferenceDone...");
2387                 // Hide the Manage Conference panel, return to NORMAL mode.
2388                 setInCallScreenMode(InCallScreenMode.NORMAL);
2389                 break;
2390
2391             case R.id.menuSpeaker:
2392                 if (VDBG) log("onClick: Speaker...");
2393                 // TODO: Turning on the speaker seems to enable the mic
2394                 //   whether or not the "mute" feature is active!
2395                 // Not sure if this is an feature of the telephony API
2396                 //   that I need to handle specially, or just a bug.
2397                 boolean newSpeakerState = !PhoneUtils.isSpeakerOn(context);
2398                 if (newSpeakerState && isBluetoothAvailable() && isBluetoothAudioConnected()) {
2399                     disconnectBluetoothAudio();
2400                 }
2401                 PhoneUtils.turnOnSpeaker(context, newSpeakerState);
2402
2403                 if (newSpeakerState) {
2404                     // The "touch lock" overlay is NEVER used when the speaker is on.
2405                     enableTouchLock(false);
2406                 } else {
2407                     // User just turned the speaker *off*.  If the dialpad
2408                     // is open, we need to start the timer that will
2409                     // eventually bring up the "touch lock" overlay.
2410                     if (mDialer.isOpened() && !isTouchLocked()) {
2411                         resetTouchLockTimer();
2412                     }
2413                 }
2414
2415                 // This is a "toggle" button; let the user see the new state for a moment.
2416                 dismissMenuImmediate = false;
2417                 break;
2418
2419             case R.id.menuBluetooth:
2420                 if (VDBG) log("onClick: Bluetooth...");
2421                 onBluetoothClick(context);
2422                 // This is a "toggle" button; let the user see the new state for a moment.
2423                 dismissMenuImmediate = false;
2424                 break;
2425
2426             case R.id.menuMute:
2427                 if (VDBG) log("onClick: Mute...");
2428                 boolean newMuteState = !PhoneUtils.getMute(mPhone);
2429                 PhoneUtils.setMute(mPhone, newMuteState);
2430                 // This is a "toggle" button; let the user see the new state for a moment.
2431                 dismissMenuImmediate = false;
2432                 break;
2433
2434             case R.id.menuHold:
2435                 if (VDBG) log("onClick: Hold...");
2436                 onHoldClick();
2437                 // This is a "toggle" button; let the user see the new state for a moment.
2438                 dismissMenuImmediate = false;
2439                 break;
2440
2441             case R.id.menuAddCall:
2442                 if (VDBG) log("onClick: AddCall...");
2443                 PhoneUtils.startNewCall(mPhone);  // Fires off a ACTION_DIAL intent
2444                 break;
2445
2446             case R.id.menuEndCall:
2447                 if (VDBG) log("onClick: EndCall...");
2448                 PhoneUtils.hangup(mPhone);
2449                 break;
2450
2451             default:
2452                 Log.w(LOG_TAG,
2453                       "Got click from unexpected View ID " + id + " (View = " + view + ")");
2454                 break;
2455         }
2456
2457         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
2458             // TODO: For now we care only about whether the user uses the
2459             // in-call buttons at all.  But in the future we may want to
2460             // log exactly which buttons are being clicked.  (Maybe just
2461             // call view.getText() here, and append that to the event value?)
2462             Checkin.logEvent(getContentResolver(),
2463                              Checkin.Events.Tag.PHONE_UI,
2464                              PHONE_UI_EVENT_BUTTON_CLICK);
2465         }
2466
2467         // If the user just clicked a "stateful" menu item (i.e. one of
2468         // the toggle buttons), we keep the menu onscreen briefly to
2469         // provide visual feedback.  Since we want the user to see the
2470         // *new* current state, force the menu items to update right now.
2471         //
2472         // Note that some toggle buttons ("Hold" in particular) do NOT
2473         // immediately change the state of the Phone.  In that case, the
2474         // updateItems() call below won't have any visible effect.
2475         // Instead, the menu will get updated by the updateScreen() call
2476         // that happens from onPhoneStateChanged().
2477
2478         if (!dismissMenuImmediate) {
2479             // TODO: mInCallMenu.updateItems() is a very big hammer; it
2480             // would be more efficient to update *only* the menu item(s)
2481             // we just changed.  (Doing it this way doesn't seem to cause
2482             // a noticeable performance problem, though.)
2483             if (VDBG) log("- onClick: updating menu to show 'new' current state...");
2484             boolean okToShowMenu = mInCallMenu.updateItems(mPhone);
2485             if (!okToShowMenu) {
2486                 // Uh oh.  All we tried to do was update the state of the
2487                 // menu items, but the logic in InCallMenu.updateItems()
2488                 // just decided the menu shouldn't be visible at all!
2489                 // (That probably means that the call ended asynchronously
2490                 // while the menu was up.)
2491                 //
2492                 // That's OK; just make sure to take the menu down ASAP.
2493                 if (VDBG) log("onClick: Tried to update menu, but now need to take it down!");
2494                 dismissMenuImmediate = true;
2495             }
2496         }
2497
2498         // Any menu item counts as explicit "user activity".
2499         PhoneApp.getInstance().pokeUserActivity();
2500
2501         // Finally, *any* action handled here closes the menu (either
2502         // immediately, or after a short delay).
2503         //
2504         // Note that some of the clicks we handle here aren't even menu
2505         // items in the first place, like the mButtonManageConferenceDone
2506         // button.  That's OK; if the menu is already closed, the
2507         // dismissMenu() call does nothing.
2508         dismissMenu(dismissMenuImmediate);
2509     }
2510
2511     private void onHoldClick() {
2512         if (VDBG) log("onHoldClick()...");
2513
2514         final boolean hasActiveCall = !mForegroundCall.isIdle();
2515         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
2516         if (VDBG) log("- hasActiveCall = " + hasActiveCall
2517                       + ", hasHoldingCall = " + hasHoldingCall);
2518         boolean newHoldState;
2519         boolean holdButtonEnabled;
2520         if (hasActiveCall && !hasHoldingCall) {
2521             // There's only one line in use, and that line is active.
2522             PhoneUtils.switchHoldingAndActive(mPhone);  // Really means "hold" in this state
2523             newHoldState = true;
2524             holdButtonEnabled = true;
2525         } else if (!hasActiveCall && hasHoldingCall) {
2526             // There's only one line in use, and that line is on hold.
2527             PhoneUtils.switchHoldingAndActive(mPhone);  // Really means "unhold" in this state
2528             newHoldState = false;
2529             holdButtonEnabled = true;
2530         } else {
2531             // Either zero or 2 lines are in use; "hold/unhold" is meaningless.
2532             newHoldState = false;
2533             holdButtonEnabled = false;
2534         }
2535         // TODO: we *could* now forcibly update the "Hold" button based on
2536         // "newHoldState" and "holdButtonEnabled".  But for now, do
2537         // nothing here, and instead let the menu get updated when the
2538         // onPhoneStateChanged() callback comes in.  (This seems to be
2539         // responsive enough.)
2540     }
2541
2542     private void onBluetoothClick(Context context) {
2543         if (VDBG) log("onBluetoothClick()...");
2544
2545         if (isBluetoothAvailable()) {
2546             // Toggle the bluetooth audio connection state:
2547             if (isBluetoothAudioConnected()) {
2548                 disconnectBluetoothAudio();
2549             } else {
2550                 // Manually turn the speaker phone off, instead of allowing the
2551                 // Bluetooth audio routing handle it.  This ensures that the rest
2552                 // of the speakerphone code is executed, and reciprocates the
2553                 // menuSpeaker code above in onClick().  The onClick() code
2554                 // disconnects the active bluetooth headsets when the
2555                 // speakerphone is turned on.
2556                 if (PhoneUtils.isSpeakerOn(context)) {
2557                     PhoneUtils.turnOnSpeaker(context, false);
2558                 }
2559
2560                 connectBluetoothAudio();
2561             }
2562         } else {
2563             // Bluetooth isn't available; the "Audio" button shouldn't have
2564             // been enabled in the first place!
2565             Log.w(LOG_TAG, "Got onBluetoothClick, but bluetooth is unavailable");
2566         }
2567     }
2568
2569     /**
2570      * Updates the "Press Menu for more options" hint based on the current
2571      * state of the Phone.
2572      */
2573     private void updateMenuButtonHint() {
2574         if (VDBG) log("updateMenuButtonHint()...");
2575         boolean hintVisible = true;
2576
2577         final boolean hasRingingCall = !mRingingCall.isIdle();
2578         final boolean hasActiveCall = !mForegroundCall.isIdle();
2579         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
2580
2581         // The hint is hidden only when there's no menu at all,
2582         // which only happens in a few specific cases:
2583         if (mInCallScreenMode == InCallScreenMode.CALL_ENDED) {
2584             // The "Call ended" state.
2585             hintVisible = false;
2586         } else if (hasRingingCall && !(hasActiveCall && !hasHoldingCall)) {
2587             // An incoming call where you *don't* have the option to
2588             // "answer & end" or "answer & hold".
2589             hintVisible = false;
2590         } else if (!phoneIsInUse()) {
2591             // Or if the phone is totally idle (like if an error dialog
2592             // is up, or an MMI is running.)
2593             hintVisible = false;
2594         }
2595         int hintVisibility = (hintVisible) ? View.VISIBLE : View.GONE;
2596
2597         // We actually have two separate "menu button hint" TextViews; one
2598         // used only in portrait mode (part of the CallCard) and one used
2599         // only in landscape mode (part of the InCallScreen.)
2600         TextView callCardMenuButtonHint = mCallCard.getMenuButtonHint();
2601         if (ConfigurationHelper.isLandscape()) {
2602             callCardMenuButtonHint.setVisibility(View.GONE);
2603             mMenuButtonHint.setVisibility(hintVisibility);
2604         } else {
2605             callCardMenuButtonHint.setVisibility(hintVisibility);
2606             mMenuButtonHint.setVisibility(View.GONE);
2607         }
2608
2609         // TODO: Consider hiding the hint(s) whenever the menu is onscreen!
2610         // (Currently, the menu is rendered on top of the hint, but the
2611         // menu is semitransparent so you can still see the hint
2612         // underneath, and the hint is *just* visible enough to be
2613         // distracting.)
2614     }
2615
2616     /**
2617      * Brings up UI to handle the various error conditions that
2618      * can occur when first initializing the in-call UI.
2619      * This is called from onResume() if we encountered
2620      * an error while processing our initial Intent.
2621      *
2622      * @param status one of the InCallInitStatus error codes.
2623      */
2624     private void handleStartupError(InCallInitStatus status) {
2625         if (DBG) log("handleStartupError(): status = " + status);
2626
2627         // NOTE that the regular Phone UI is in an uninitialized state at
2628         // this point, so we don't ever want the user to see it.
2629         // That means:
2630         // - Any cases here that need to go to some other activity should
2631         //   call startActivity() AND immediately call finish() on this one.
2632         // - Any cases here that bring up a Dialog must ensure that the
2633         //   Dialog handles both OK *and* cancel by calling finish() on this
2634         //   Activity.  (See showGenericErrorDialog() for an example.)
2635
2636         switch(status) {
2637
2638             case VOICEMAIL_NUMBER_MISSING:
2639                 // Bring up the "Missing Voicemail Number" dialog, which
2640                 // will ultimately take us to some other Activity (or else
2641                 // just bail out of this activity.)
2642                 handleMissingVoiceMailNumber();
2643                 break;
2644
2645             case POWER_OFF:
2646                 // Radio is explictly powered off.
2647
2648                 // TODO: This UI is ultra-simple for 1.0.  It would be nicer
2649                 // to bring up a Dialog instead with the option "turn on radio
2650                 // now".  If selected, we'd turn the radio on, wait for
2651                 // network registration to complete, and then make the call.
2652
2653                 showGenericErrorDialog(R.string.incall_error_power_off, true);
2654                 break;
2655
2656             case EMERGENCY_ONLY:
2657                 // Only emergency numbers are allowed, but we tried to dial
2658                 // a non-emergency number.
2659                 showGenericErrorDialog(R.string.incall_error_emergency_only, true);
2660                 break;
2661
2662             case PHONE_NOT_IN_USE:
2663                 // This error is handled directly in onResume() (by bailing
2664                 // out of the activity.)  We should never see it here.
2665                 Log.w(LOG_TAG,
2666                       "handleStartupError: unexpected PHONE_NOT_IN_USE status");
2667                 break;
2668
2669             case NO_PHONE_NUMBER_SUPPLIED:
2670                 // The supplied Intent didn't contain a valid phone number.
2671                 // TODO: Need UI spec for this failure case; for now just
2672                 // show a generic error.
2673                 showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied, true);
2674                 break;
2675
2676             case DIALED_MMI:
2677                 // Our initial phone number was actually an MMI sequence.
2678                 // There's no real "error" here, but we do bring up the
2679                 // a Toast (as requested of the New UI paradigm).
2680                 //
2681                 // In-call MMIs do not trigger the normal MMI Initiate
2682                 // Notifications, so we should notify the user here.
2683                 // Otherwise, the code in PhoneUtils.java should handle
2684                 // user notifications in the form of Toasts or Dialogs.
2685                 if (mPhone.getState() == Phone.State.OFFHOOK) {
2686                     Toast.makeText(this, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
2687                         .show();
2688                 }
2689                 break;
2690
2691             case CALL_FAILED:
2692                 // We couldn't successfully place the call; there was some
2693                 // failure in the telephony layer.
2694                 // TODO: Need UI spec for this failure case; for now just
2695                 // show a generic error.
2696                 showGenericErrorDialog(R.string.incall_error_call_failed, true);
2697                 break;
2698
2699             default:
2700                 Log.w(LOG_TAG, "handleStartupError: unexpected status code " + status);
2701                 showGenericErrorDialog(R.string.incall_error_call_failed, true);
2702                 break;
2703         }
2704     }
2705
2706     /**
2707      * Utility function to bring up a generic "error" dialog, and then bail
2708      * out of the in-call UI when the user hits OK (or the BACK button.)
2709      */
2710     private void showGenericErrorDialog(int resid, boolean isStartupError) {
2711         CharSequence msg = getResources().getText(resid);
2712         if (DBG) log("showGenericErrorDialog('" + msg + "')...");
2713
2714         // create the clicklistener and cancel listener as needed.
2715         DialogInterface.OnClickListener clickListener;
2716         OnCancelListener cancelListener;
2717         if (isStartupError) {
2718             clickListener = new DialogInterface.OnClickListener() {
2719                 public void onClick(DialogInterface dialog, int which) {
2720                     bailOutAfterErrorDialog();
2721                 }};
2722             cancelListener = new OnCancelListener() {
2723                 public void onCancel(DialogInterface dialog) {
2724                     bailOutAfterErrorDialog();
2725                 }};
2726         } else {
2727             clickListener = new DialogInterface.OnClickListener() {
2728                 public void onClick(DialogInterface dialog, int which) {
2729                     delayedCleanupAfterDisconnect();
2730                 }};
2731             cancelListener = new OnCancelListener() {
2732                 public void onCancel(DialogInterface dialog) {
2733                     delayedCleanupAfterDisconnect();
2734                 }};
2735         }
2736
2737         // TODO: Consider adding a setTitle() call here (with some generic
2738         // "failure" title?)
2739         mGenericErrorDialog = new AlertDialog.Builder(this)
2740                 .setMessage(msg)
2741                 .setPositiveButton(R.string.ok, clickListener)
2742                 .setOnCancelListener(cancelListener)
2743                 .create();
2744
2745         // When the dialog is up, completely hide the in-call UI
2746         // underneath (which is in a partially-constructed state).
2747         mGenericErrorDialog.getWindow().addFlags(
2748                 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
2749
2750         mGenericErrorDialog.show();
2751     }
2752
2753     private void bailOutAfterErrorDialog() {
2754         if (mGenericErrorDialog != null) {
2755             if (VDBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
2756             mGenericErrorDialog.dismiss();
2757             mGenericErrorDialog = null;
2758         }
2759         if (DBG) log("bailOutAfterErrorDialog(): finishing...");
2760         finish();
2761     }
2762
2763     /**
2764      * Dismisses (and nulls out) all persistent Dialogs managed
2765      * by the InCallScreen.  Useful if (a) we're about to bring up
2766      * a dialog and want to pre-empt any currently visible dialogs,
2767      * or (b) as a cleanup step when the Activity is going away.
2768      */
2769     private void dismissAllDialogs() {
2770         if (VDBG) log("dismissAllDialogs()...");
2771
2772         // Note it's safe to dismiss() a dialog that's already dismissed.
2773         // (Even if the AlertDialog object(s) below are still around, it's
2774         // possible that the actual dialog(s) may have already been
2775         // dismissed by the user.)
2776
2777         if (mMissingVoicemailDialog != null) {
2778             if (VDBG) log("- DISMISSING mMissingVoicemailDialog.");
2779             mMissingVoicemailDialog.dismiss();
2780             mMissingVoicemailDialog = null;
2781         }
2782         if (mMmiStartedDialog != null) {
2783             if (VDBG) log("- DISMISSING mMmiStartedDialog.");
2784             mMmiStartedDialog.dismiss();
2785             mMmiStartedDialog = null;
2786         }
2787         if (mGenericErrorDialog != null) {
2788             if (VDBG) log("- DISMISSING mGenericErrorDialog.");
2789             mGenericErrorDialog.dismiss();
2790             mGenericErrorDialog = null;
2791         }
2792         if (mSuppServiceFailureDialog != null) {
2793             if (VDBG) log("- DISMISSING mSuppServiceFailureDialog.");
2794             mSuppServiceFailureDialog.dismiss();
2795             mSuppServiceFailureDialog = null;
2796         }
2797         if (mWaitPromptDialog != null) {
2798             if (VDBG) log("- DISMISSING mWaitPromptDialog.");
2799             mWaitPromptDialog.dismiss();
2800             mWaitPromptDialog = null;
2801         }
2802         if (mWildPromptDialog != null) {
2803             if (VDBG) log("- DISMISSING mWildPromptDialog.");
2804             mWildPromptDialog.dismiss();
2805             mWildPromptDialog = null;
2806         }
2807     }
2808
2809
2810     //
2811     // Helper functions for answering incoming calls.
2812     //
2813
2814     /**
2815      * Answer the ringing call.
2816      */
2817     /* package */ void internalAnswerCall() {
2818         if (DBG) log("internalAnswerCall()...");
2819         // if (DBG) PhoneUtils.dumpCallState(mPhone);
2820         PhoneUtils.answerCall(mPhone);  // Automatically holds the current active call,
2821                                         // if there is one
2822     }
2823
2824     /**
2825      * Answer the ringing call *and* hang up the ongoing call.
2826      */
2827     /* package */ void internalAnswerAndEnd() {
2828         if (DBG) log("internalAnswerAndEnd()...");
2829         // if (DBG) PhoneUtils.dumpCallState(mPhone);
2830         PhoneUtils.answerAndEndActive(mPhone);
2831     }
2832
2833     /**
2834      * Answer the ringing call, in the special case where both lines
2835      * are already in use.
2836      *
2837      * We "answer incoming, end ongoing" in this case, according to the
2838      * current UI spec.
2839      */
2840     /* package */ void internalAnswerCallBothLinesInUse() {
2841         if (DBG) log("internalAnswerCallBothLinesInUse()...");
2842         // if (DBG) PhoneUtils.dumpCallState(mPhone);
2843
2844         PhoneUtils.answerAndEndActive(mPhone);
2845         // Alternatively, we could use
2846         //    PhoneUtils.answerAndEndHolding(mPhone);
2847         // here to end the on-hold call instead.
2848     }
2849
2850     /**
2851      * Hang up the ringing call (aka "Don't answer").
2852      */
2853     /* package */ void internalHangupRingingCall() {
2854         if (DBG) log("internalHangupRingingCall()...");
2855         PhoneUtils.hangupRingingCall(mPhone);
2856     }
2857
2858     /**
2859      * InCallScreen-specific wrapper around PhoneUtils.switchHoldingAndActive().
2860      */
2861     private void internalSwapCalls() {
2862         if (VDBG) log("internalSwapCalls()...");
2863
2864         // Any time we swap calls, force the DTMF dialpad to close.
2865         // (We want the regular in-call UI to be visible right now, so the
2866         // user can clearly see which call is now in the foreground.)
2867         mDialer.closeDialer(true);  // do the "closing" animation
2868
2869         // Also, clear out the "history" of DTMF digits you typed, to make
2870         // sure you don't see digits from call #1 while call #2 is active.
2871         // (Yes, this does mean that swapping calls twice will cause you
2872         // to lose any previous digits from the current call; see the TODO
2873         // comment on DTMFTwelvKeyDialer.clearDigits() for more info.)
2874         mDialer.clearDigits();
2875
2876         // Swap the fg and bg calls.
2877         PhoneUtils.switchHoldingAndActive(mPhone);
2878     }
2879
2880     //
2881     // "Manage conference" UI.
2882     //
2883     // TODO: There's a lot of code here, and this source file is already too large.
2884     // Consider moving all this code out to a separate class.
2885     //
2886
2887     private void initManageConferencePanel() {
2888         if (VDBG) log("initManageConferencePanel()...");
2889         if (mManageConferencePanel == null) {
2890             mManageConferencePanel = (ViewGroup) findViewById(R.id.manageConferencePanel);
2891
2892             // set up the Conference Call chronometer
2893             mConferenceTime = (Chronometer) findViewById(R.id.manageConferencePanelHeader);
2894             mConferenceTime.setFormat(getString(R.string.caller_manage_header));
2895
2896             // Create list of conference call widgets
2897             mConferenceCallList = new ViewGroup[MAX_CALLERS_IN_CONFERENCE];
2898             {
2899                 final int[] viewGroupIdList = {R.id.caller0, R.id.caller1, R.id.caller2,
2900                         R.id.caller3, R.id.caller4};
2901                 for (int i = 0; i < MAX_CALLERS_IN_CONFERENCE; i++) {
2902                     mConferenceCallList[i] = (ViewGroup) findViewById(viewGroupIdList[i]);
2903                 }
2904             }
2905
2906             mButtonManageConferenceDone =
2907                     (Button) findViewById(R.id.manage_done);
2908             mButtonManageConferenceDone.setOnClickListener(this);
2909         }
2910     }
2911
2912     /**
2913      * Sets the current high-level "mode" of the in-call UI.
2914      *
2915      * NOTE: if newMode is CALL_ENDED, the caller is responsible for
2916      * posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
2917      * sure the "call ended" state goes away after a couple of seconds.
2918      */
2919     private void setInCallScreenMode(InCallScreenMode newMode) {
2920         if (VDBG) log("setInCallScreenMode: " + newMode);
2921         mInCallScreenMode = newMode;
2922         switch (mInCallScreenMode) {
2923             case MANAGE_CONFERENCE:
2924                 if (!PhoneUtils.isConferenceCall(mForegroundCall)) {
2925                     Log.w(LOG_TAG, "MANAGE_CONFERENCE: no active conference call!");
2926                     // Hide the Manage Conference panel, return to NORMAL mode.
2927                     setInCallScreenMode(InCallScreenMode.NORMAL);
2928                     return;
2929                 }
2930                 List<Connection> connections = mForegroundCall.getConnections();
2931                 // There almost certainly will be > 1 connection,
2932                 // since isConferenceCall() just returned true.
2933                 if ((connections == null) || (connections.size() <= 1)) {
2934                     Log.w(LOG_TAG,
2935                           "MANAGE_CONFERENCE: Bogus TRUE from isConferenceCall(); connections = "
2936                           + connections);
2937                     // Hide the Manage Conference panel, return to NORMAL mode.
2938                     setInCallScreenMode(InCallScreenMode.NORMAL);
2939                     return;
2940                 }
2941
2942                 initManageConferencePanel();  // if necessary
2943                 updateManageConferencePanel(connections);
2944
2945                 // The "Manage conference" UI takes up the full main frame,
2946                 // replacing the inCallPanel and CallCard PopupWindow.
2947                 mManageConferencePanel.setVisibility(View.VISIBLE);
2948
2949                 // start the chronometer.
2950                 long callDuration = mForegroundCall.getEarliestConnection().getDurationMillis();
2951                 mConferenceTime.setBase(SystemClock.elapsedRealtime() - callDuration);
2952                 mConferenceTime.start();
2953
2954                 mInCallPanel.setVisibility(View.GONE);
2955                 mDialer.hideDTMFDisplay(true);
2956
2957                 break;
2958
2959             case CALL_ENDED:
2960                 // Display the CallCard (in the "Call ended" state)
2961                 // and hide all other UI.
2962
2963                 if (mManageConferencePanel != null) {
2964                     mManageConferencePanel.setVisibility(View.GONE);
2965                     // stop the timer if the panel is hidden.
2966                     mConferenceTime.stop();
2967                 }
2968                 updateMenuButtonHint();  // Hide the Menu button hint
2969
2970                 // Make sure the CallCard (which is a child of mInCallPanel) is visible.
2971                 mInCallPanel.setVisibility(View.VISIBLE);
2972                 mDialer.hideDTMFDisplay(false);
2973
2974                 break;
2975
2976             case NORMAL:
2977                 mInCallPanel.setVisibility(View.VISIBLE);
2978                 mDialer.hideDTMFDisplay(false);
2979                 if (mManageConferencePanel != null) {
2980                     mManageConferencePanel.setVisibility(View.GONE);
2981                     // stop the timer if the panel is hidden.
2982                     mConferenceTime.stop();
2983                 }
2984                 break;
2985         }
2986
2987         // Update the visibility of the DTMF dialer tab on any state
2988         // change.
2989         updateDialpadVisibility();
2990     }
2991
2992     /**
2993      * @return true if the "Manage conference" UI is currently visible.
2994      */
2995     /* package */ boolean isManageConferenceMode() {
2996         return (mInCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE);
2997     }
2998
2999     /**
3000      * Updates the "Manage conference" UI based on the specified List of
3001      * connections.
3002      *
3003      * @param connections the List of connections belonging to
3004      *        the current foreground call; size must be greater than 1
3005      *        (or it wouldn't be a conference call in the first place.)
3006      */
3007     private void updateManageConferencePanel(List<Connection> connections) {
3008         mNumCallersInConference = connections.size();
3009         if (VDBG) log("updateManageConferencePanel()... num connections in conference = "
3010                      + mNumCallersInConference);
3011
3012         // Can we give the user the option to separate out ("go private with") a single
3013         // caller from this conference?
3014         final boolean hasActiveCall = !mForegroundCall.isIdle();
3015         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
3016         boolean canSeparate = !(hasActiveCall && hasHoldingCall);
3017
3018         for (int i = 0; i < MAX_CALLERS_IN_CONFERENCE; i++) {
3019             if (i < mNumCallersInConference) {
3020                 // Fill in the row in the UI for this caller.
3021                 Connection connection = (Connection) connections.get(i);
3022                 updateManageConferenceRow(i, connection, canSeparate);
3023             } else {
3024                 // Blank out this row in the UI
3025                 updateManageConferenceRow(i, null, false);
3026             }
3027         }
3028     }
3029
3030     /**
3031      * Checks if the "Manage conference" UI needs to be updated.
3032      * If the state of the current conference call has changed
3033      * since our previous call to updateManageConferencePanel()),
3034      * do a fresh update.
3035      */
3036     private void updateManageConferencePanelIfNecessary() {
3037         if (VDBG) log("updateManageConferencePanel: mForegroundCall " + mForegroundCall + "...");
3038
3039         List<Connection> connections = mForegroundCall.getConnections();
3040         if (connections == null) {
3041             if (VDBG) log("==> no connections on foreground call!");
3042             // Hide the Manage Conference panel, return to NORMAL mode.
3043             setInCallScreenMode(InCallScreenMode.NORMAL);
3044             InCallInitStatus status = syncWithPhoneState();
3045             if (status != InCallInitStatus.SUCCESS) {
3046                 Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3047                 // We shouldn't even be in the in-call UI in the first
3048                 // place, so bail out:
3049                 if (DBG) log("updateManageConferencePanelIfNecessary: finishing...");
3050                 finish();
3051                 return;
3052             }
3053             return;
3054         }
3055
3056         int numConnections = connections.size();
3057         if (numConnections <= 1) {
3058             if (VDBG) log("==> foreground call no longer a conference!");
3059             // Hide the Manage Conference panel, return to NORMAL mode.
3060             setInCallScreenMode(InCallScreenMode.NORMAL);
3061             InCallInitStatus status = syncWithPhoneState();
3062             if (status != InCallInitStatus.SUCCESS) {
3063                 Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3064                 // We shouldn't even be in the in-call UI in the first
3065                 // place, so bail out:
3066                 if (DBG) log("updateManageConferencePanelIfNecessary: finishing...");
3067                 finish();
3068                 return;
3069             }
3070             return;
3071         }
3072         if (numConnections != mNumCallersInConference) {
3073             if (VDBG) log("==> Conference size has changed; need to rebuild UI!");
3074             updateManageConferencePanel(connections);
3075             return;
3076         }
3077     }
3078
3079     /**
3080      * Updates a single row of the "Manage conference" UI.  (One row in this
3081      * UI represents a single caller in the conference.)
3082      *
3083      * @param i the row to update
3084      * @param connection the Connection corresponding to this caller.
3085      *        If null, that means this is an "empty slot" in the conference,
3086      *        so hide this row in the UI.
3087      * @param canSeparate if true, show a "Separate" (i.e. "Private") button
3088      *        on this row in the UI.
3089      */
3090     private void updateManageConferenceRow(final int i,
3091                                            final Connection connection,
3092                                            boolean canSeparate) {
3093         if (VDBG) log("updateManageConferenceRow(" + i + ")...  connection = " + connection);
3094
3095         if (connection != null) {
3096             // Activate this row of the Manage conference panel:
3097             mConferenceCallList[i].setVisibility(View.VISIBLE);
3098
3099             // get the relevant children views
3100             ImageButton endButton = (ImageButton) mConferenceCallList[i].findViewById(
3101                     R.id.conferenceCallerDisconnect);
3102             ImageButton separateButton = (ImageButton) mConferenceCallList[i].findViewById(
3103                     R.id.conferenceCallerSeparate);
3104             TextView nameTextView = (TextView) mConferenceCallList[i].findViewById(
3105                     R.id.conferenceCallerName);
3106             TextView numberTextView = (TextView) mConferenceCallList[i].findViewById(
3107                     R.id.conferenceCallerNumber);
3108             TextView numberTypeTextView = (TextView) mConferenceCallList[i].findViewById(
3109                     R.id.conferenceCallerNumberType);
3110
3111             if (VDBG) log("- button: " + endButton + ", nameTextView: " + nameTextView);
3112
3113             // Hook up this row's buttons.
3114             View.OnClickListener endThisConnection = new View.OnClickListener() {
3115                     public void onClick(View v) {
3116                         endConferenceConnection(i, connection);
3117                         PhoneApp.getInstance().pokeUserActivity();
3118                     }
3119                 };
3120             endButton.setOnClickListener(endThisConnection);
3121             //
3122             if (canSeparate) {
3123                 View.OnClickListener separateThisConnection = new View.OnClickListener() {
3124                         public void onClick(View v) {
3125                             separateConferenceConnection(i, connection);
3126                             PhoneApp.getInstance().pokeUserActivity();
3127                         }
3128                     };
3129                 separateButton.setOnClickListener(separateThisConnection);
3130                 separateButton.setVisibility(View.VISIBLE);
3131             } else {
3132                 separateButton.setVisibility(View.INVISIBLE);
3133             }
3134
3135             // Name/number for this caller.
3136             // TODO: need to deal with private or blocked caller id?
3137             PhoneUtils.CallerInfoToken info = PhoneUtils.startGetCallerInfo(this, connection,
3138                     this, mConferenceCallList[i]);
3139
3140             // display the CallerInfo.
3141             displayCallerInfoForConferenceRow (info.currentInfo, nameTextView,
3142                     numberTypeTextView, numberTextView);
3143         } else {
3144             // Disable this row of the Manage conference panel:
3145             mConferenceCallList[i].setVisibility(View.GONE);
3146         }
3147     }
3148
3149     /**
3150      * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
3151      * refreshes the nameTextView when called.
3152      */
3153     public void onQueryComplete(int token, Object cookie, CallerInfo ci){
3154         if (VDBG) log("callerinfo query complete, updating UI.");
3155
3156         // get the viewgroup (conference call list item) and make it visible
3157         ViewGroup vg = (ViewGroup) cookie;
3158         vg.setVisibility(View.VISIBLE);
3159
3160         // update the list item with this information.
3161         displayCallerInfoForConferenceRow (ci,
3162                 (TextView) vg.findViewById(R.id.conferenceCallerName),
3163                 (TextView) vg.findViewById(R.id.conferenceCallerNumberType),
3164                 (TextView) vg.findViewById(R.id.conferenceCallerNumber));
3165     }
3166
3167     /**
3168      * Helper function to fill out the Conference Call(er) information
3169      * for each item in the "Manage Conference Call" list.
3170      */
3171     private final void displayCallerInfoForConferenceRow(CallerInfo ci, TextView nameTextView,
3172             TextView numberTypeTextView, TextView numberTextView) {
3173
3174         // gather the correct name and number information.
3175         String callerName = "";
3176         String callerNumber = "";
3177         String callerNumberType = "";
3178         if (ci != null) {
3179             callerName = ci.name;
3180             if (TextUtils.isEmpty(callerName)) {
3181                 callerName = ci.phoneNumber;
3182                 if (TextUtils.isEmpty(callerName)) {
3183                     callerName = getString(R.string.unknown);
3184                 }
3185             } else {
3186                 callerNumber = ci.phoneNumber;
3187                 callerNumberType = ci.phoneLabel;
3188             }
3189         }
3190
3191         // set the caller name
3192         nameTextView.setText(callerName);
3193
3194         // set the caller number in subscript, or make the field disappear.
3195         if (TextUtils.isEmpty(callerNumber)) {
3196             numberTextView.setVisibility(View.GONE);
3197             numberTypeTextView.setVisibility(View.GONE);
3198         } else {
3199             numberTextView.setVisibility(View.VISIBLE);
3200             numberTextView.setText(callerNumber);
3201             numberTypeTextView.setVisibility(View.VISIBLE);
3202             numberTypeTextView.setText(callerNumberType);
3203         }
3204     }
3205
3206     /**
3207      * Ends the specified connection on a conference call.  This method is
3208      * run (via a closure containing a row index and Connection) when the
3209      * user clicks the "End" button on a specific row in the Manage
3210      * conference UI.
3211      */
3212     private void endConferenceConnection(int i, Connection connection) {
3213         if (VDBG) log("===> ENDING conference connection " + i
3214                       + ": Connection " + connection);
3215         // The actual work of ending the connection:
3216         PhoneUtils.hangup(connection);
3217         // No need to manually update the "Manage conference" UI here;
3218         // that'll happen automatically very soon (when we get the
3219         // onDisconnect() callback triggered by this hangup() call.)
3220     }
3221
3222     /**
3223      * Separates out the specified connection on a conference call.  This
3224      * method is run (via a closure containing a row index and Connection)
3225      * when the user clicks the "Separate" (i.e. "Private") button on a
3226      * specific row in the Manage conference UI.
3227      */
3228     private void separateConferenceConnection(int i, Connection connection) {
3229         if (VDBG) log("===> SEPARATING conference connection " + i
3230                       + ": Connection " + connection);
3231
3232         PhoneUtils.separateCall(connection);
3233
3234         // Note that separateCall() automagically makes the
3235         // newly-separated call into the foreground call (which is the
3236         // desired UI), so there's no need to do any further
3237         // call-switching here.
3238         // There's also no need to manually update (or hide) the "Manage
3239         // conference" UI; that'll happen on its own in a moment (when we
3240         // get the phone state change event triggered by the call to
3241         // separateCall().)
3242     }
3243
3244     /**
3245      * Updates the visibility of the DTMF dialpad and the "sliding drawer"
3246      * handle, based on the current state of the phone and/or the current
3247      * InCallScreenMode.
3248      */
3249     private void updateDialpadVisibility() {
3250         //
3251         // (1) The dialpad itself:
3252         //
3253         // If an incoming call is ringing, make sure the dialpad is
3254         // closed.  (We do this to make sure we're not covering up the
3255         // "incoming call" UI, and especially to make sure that the "touch
3256         // lock" overlay won't appear.)
3257         if (mPhone.getState() == Phone.State.RINGING) {
3258             mDialer.closeDialer(false);  // don't do the "closing" animation
3259
3260             // Also, clear out the "history" of DTMF digits you may have typed
3261             // into the previous call (so you don't see the previous call's
3262             // digits if you answer this call and then bring up the dialpad.)
3263             //
3264             // TODO: it would be more precise to do this when you *answer* the
3265             // incoming call, rather than as soon as it starts ringing, but
3266             // the InCallScreen doesn't keep enough state right now to notice
3267             // that specific transition in onPhoneStateChanged().
3268             mDialer.clearDigits();
3269         }
3270
3271         //
3272         // (2) The "sliding drawer" handle:
3273         //
3274         // This handle should be visible only if it's OK to actually open
3275         // the dialpad.
3276         //
3277         if (mDialerDrawer != null) {
3278             int visibility = okToShowDialpad() ? View.VISIBLE : View.GONE;
3279             mDialerDrawer.setVisibility(visibility);
3280         }
3281     }
3282
3283     /**
3284      * @return true if the DTMF dialpad is currently visible.
3285      */
3286     /* package */ boolean isDialerOpened() {
3287         return (mDialer != null && mDialer.isOpened());
3288     }
3289
3290     /**
3291      * Called any time the DTMF dialpad is opened.
3292      * @see DTMFTwelveKeyDialer.onDialerOpen()
3293      */
3294     /* package */ void onDialerOpen() {
3295         if (VDBG) log("onDialerOpen()...");
3296
3297         // ANY time the dialpad becomes visible, start the timer that will
3298         // eventually bring up the "touch lock" overlay.
3299         resetTouchLockTimer();
3300
3301         // This counts as explicit "user activity".
3302         PhoneApp.getInstance().pokeUserActivity();
3303     }
3304
3305     /**
3306      * Called any time the DTMF dialpad is closed.
3307      * @see DTMFTwelveKeyDialer.onDialerClose()
3308      */
3309     /* package */ void onDialerClose() {
3310         if (VDBG) log("onDialerClose()...");
3311
3312         // Dismiss the "touch lock" overlay if it was visible.
3313         // (The overlay is only ever used on top of the dialpad).
3314         enableTouchLock(false);
3315
3316         // This counts as explicit "user activity".
3317         PhoneApp.getInstance().pokeUserActivity();
3318     }
3319
3320     /**
3321      * Get the DTMF dialer display field.
3322      */
3323     /* package */ EditText getDialerDisplay() {
3324         return mDTMFDisplay;
3325     }
3326
3327     /**
3328      * Determines when we can dial DTMF tones.
3329      */
3330     private boolean okToDialDTMFTones() {
3331         final boolean hasRingingCall = !mRingingCall.isIdle();
3332         final Call.State fgCallState = mForegroundCall.getState();
3333
3334         // We're allowed to send DTMF tones when there's an ACTIVE
3335         // foreground call, and not when an incoming call is ringing
3336         // (since DTMF tones are useless in that state), or if the
3337         // Manage Conference UI is visible (since the tab interferes
3338         // with the "Back to call" button.)
3339
3340         // We can also dial while in ALERTING state because there are
3341         // some connections that never update to an ACTIVE state (no
3342         // indication from the network).
3343         boolean canDial =
3344             (fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
3345             && !hasRingingCall
3346             && (mInCallScreenMode != InCallScreenMode.MANAGE_CONFERENCE);
3347
3348         if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
3349                 ", ringing state: " + hasRingingCall +
3350                 ", call screen mode: " + mInCallScreenMode +
3351                 ", result: " + canDial);
3352
3353         return canDial;
3354     }
3355
3356     /**
3357      * @return true if the in-call DTMF dialpad should be available to the
3358      *      user, given the current state of the phone and the in-call UI.
3359      *      (This is used to control the visiblity of the dialer's
3360      *      SlidingDrawer handle, and the enabledness of the "Show
3361      *      dialpad" menu item.)
3362      */
3363     /* package */ boolean okToShowDialpad() {
3364         // The dialpad is never used in landscape mode.  And even in
3365         // portrait mode, it's available only when it's OK to dial DTMF
3366         // tones given the current state of the current call.
3367         return !ConfigurationHelper.isLandscape() && okToDialDTMFTones();
3368     }
3369
3370
3371     /**
3372      * Initializes the onscreen "answer" UI on devices that need it.
3373      */
3374     private void initOnscreenAnswerUi() {
3375         // This UI is only used on devices with no hard CALL or SEND button.
3376
3377         // TODO: For now, explicitly enable this for sholes devices.
3378         // (Note PRODUCT_DEVICE is "sholes" for both sholes and voles builds.)
3379         boolean allowOnscreenAnswerUi =
3380                 "sholes".equals(SystemProperties.get("ro.product.device"));
3381         //
3382         // TODO: But ultimately we should either (a) use some framework API
3383         // to detect whether the current device has a hard SEND button, or
3384         // (b) have this depend on a product-specific resource flag in
3385         // config.xml, like the forthcoming "is_full_touch_ui" boolean.
3386
3387         if (DBG) log("initOnscreenAnswerUi: device '" + SystemProperties.get("ro.product.device")
3388                      + "', allowOnscreenAnswerUi = " + allowOnscreenAnswerUi);
3389
3390         if (allowOnscreenAnswerUi) {
3391             ViewStub stub = (ViewStub) findViewById(R.id.onscreenAnswerUiStub);
3392             mOnscreenAnswerUiContainer = stub.inflate();
3393
3394             mOnscreenAnswerButton = findViewById(R.id.onscreenAnswerButton);
3395             mOnscreenAnswerButton.setOnTouchListener(this);
3396         }
3397     }
3398
3399     /**
3400      * Updates the visibility of the onscreen "answer" UI.  On devices
3401      * with no hardware CALL button, this UI becomes visible while an
3402      * incoming call is ringing.
3403      *
3404      * TODO: This method should eventually be rolled into a more general
3405      * method to update *all* onscreen UI elements that need to be
3406      * different on different devices (depending on which hard buttons are
3407      * present and/or if we don't have to worry about false touches while
3408      * in-call.)
3409      */
3410     private void updateOnscreenAnswerUi() {
3411         if (mOnscreenAnswerUiContainer != null) {
3412             if (mPhone.getState() == Phone.State.RINGING) {
3413                 // A phone call is ringing *or* call waiting.
3414                 mOnscreenAnswerUiContainer.setVisibility(View.VISIBLE);
3415             } else {
3416                 mOnscreenAnswerUiContainer.setVisibility(View.GONE);
3417             }
3418         }
3419     }
3420
3421
3422     /**
3423      * Helper class to manage the (small number of) manual layout and UI
3424      * changes needed by the in-call UI when switching between landscape
3425      * and portrait mode.
3426      *
3427      * TODO: Ideally, all this information should come directly from
3428      * resources, with alternate sets of resources for for different
3429      * configurations (like alternate layouts under res/layout-land
3430      * or res/layout-finger.)
3431      *
3432      * But for now, we don't use any alternate resources.  Instead, the
3433      * resources under res/layout are hardwired for portrait mode, and we
3434      * use this class's applyConfigurationToLayout() method to reach into
3435      * our View hierarchy and manually patch up anything that needs to be
3436      * different for landscape mode.
3437      */
3438     /* package */ static class ConfigurationHelper {
3439         /** This class is never instantiated. */
3440         private ConfigurationHelper() {
3441         }
3442
3443         // "Configuration constants" set by initConfiguration()
3444         static int sOrientation = Configuration.ORIENTATION_UNDEFINED;
3445
3446         static boolean isLandscape() {
3447             return sOrientation == Configuration.ORIENTATION_LANDSCAPE;
3448         }
3449
3450         /**
3451          * Initializes the "Configuration constants" based on the
3452          * specified configuration.
3453          */
3454         static void initConfiguration(Configuration config) {
3455             if (VDBG) Log.d(LOG_TAG, "[InCallScreen.ConfigurationHelper] "
3456                            + "initConfiguration(" + config + ")...");
3457             sOrientation = config.orientation;
3458         }
3459
3460         /**
3461          * Updates the InCallScreen's View hierarchy, applying any
3462          * necessary changes given the current configuration.
3463          */
3464         static void applyConfigurationToLayout(InCallScreen inCallScreen) {
3465             if (sOrientation == Configuration.ORIENTATION_UNDEFINED) {
3466                 throw new IllegalStateException("need to call initConfiguration first");
3467             }
3468
3469             // find the landscape-only DTMF display field.
3470             inCallScreen.mDTMFDisplay = (EditText) inCallScreen.findViewById(R.id.dtmfDialerField);
3471
3472             // Our layout resources describe the *portrait mode* layout of
3473             // the Phone UI (see the TODO above in the doc comment for
3474             // the ConfigurationHelper class.)  So if we're in landscape
3475             // mode now, reach into our View hierarchy and update the
3476             // (few) layout params that need to be different.
3477             if (isLandscape()) {
3478                 // Update CallCard-related stuff
3479                 inCallScreen.mCallCard.updateForLandscapeMode();
3480
3481                 // No need to adjust the visiblity for mDTMFDisplay here because
3482                 // we're relying on the resources (layouts in layout-finger vs.
3483                 // layout-land-finger) to manage when mDTMFDisplay is shown.
3484             }
3485         }
3486     }
3487
3488     /**
3489      * @return true if we're in restricted / emergency dialing only mode.
3490      */
3491     public boolean isPhoneStateRestricted() {
3492         // TODO:  This needs to work IN TANDEM with the KeyGuardViewMediator Code.
3493         // Right now, it looks like the mInputRestricted flag is INTERNAL to the
3494         // KeyGuardViewMediator and SPECIFICALLY set to be FALSE while the emergency
3495         // phone call is being made, to allow for input into the InCallScreen.
3496         // Having the InCallScreen judge the state of the device from this flag
3497         // becomes meaningless since it is always false for us.  The mediator should
3498         // have an additional API to let this app know that it should be restricted.
3499         return ((mPhone.getServiceState().getState() == ServiceState.STATE_EMERGENCY_ONLY) ||
3500                 (mPhone.getServiceState().getState() == ServiceState.STATE_OUT_OF_SERVICE) ||
3501                 (PhoneApp.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()));
3502     }
3503
3504     //
3505     // In-call menu UI
3506     //
3507
3508     /**
3509      * Override onCreatePanelView(), in order to get complete control
3510      * over the UI that comes up when the user presses MENU.
3511      *
3512      * This callback allows me to return a totally custom View hierarchy
3513      * (with custom layout and custom "item" views) to be shown instead
3514      * of a standard android.view.Menu hierarchy.
3515      *
3516      * This gets called (with featureId == FEATURE_OPTIONS_PANEL) every
3517      * time we need to bring up the menu.  (And in cases where we return
3518      * non-null, that means that the "standard" menu callbacks
3519      * onCreateOptionsMenu() and onPrepareOptionsMenu() won't get called
3520      * at all.)
3521      */
3522     @Override
3523     public View onCreatePanelView(int featureId) {
3524         if (VDBG) log("onCreatePanelView(featureId = " + featureId + ")...");
3525
3526         // We only want this special behavior for the "options panel"
3527         // feature (i.e. the standard menu triggered by the MENU button.)
3528         if (featureId != Window.FEATURE_OPTIONS_PANEL) {
3529             return null;
3530         }
3531
3532         // TODO: May need to revisit the wake state here if this needs to be
3533         // tweaked.
3534
3535         // Make sure there are no pending messages to *dismiss* the menu.
3536         mHandler.removeMessages(DISMISS_MENU);
3537
3538         if (mInCallMenu == null) {
3539             if (VDBG) log("onCreatePanelView: creating mInCallMenu (first time)...");
3540             mInCallMenu = new InCallMenu(this);
3541             mInCallMenu.initMenu();
3542         }
3543
3544         boolean okToShowMenu = mInCallMenu.updateItems(mPhone);
3545         return okToShowMenu ? mInCallMenu.getView() : null;
3546     }
3547
3548     /**
3549      * Dismisses the menu panel (see onCreatePanelView().)
3550      *
3551      * @param dismissImmediate If true, hide the panel immediately.
3552      *            If false, leave the menu visible onscreen for
3553      *            a brief interval before dismissing it (so the
3554      *            user can see the state change resulting from
3555      *            his original click.)
3556      */
3557     /* package */ void dismissMenu(boolean dismissImmediate) {
3558         if (VDBG) log("dismissMenu(immediate = " + dismissImmediate + ")...");
3559
3560         if (dismissImmediate) {
3561             closeOptionsMenu();
3562         } else {
3563             mHandler.removeMessages(DISMISS_MENU);
3564             mHandler.sendEmptyMessageDelayed(DISMISS_MENU, MENU_DISMISS_DELAY);
3565             // This will result in a dismissMenu(true) call shortly.
3566         }
3567     }
3568
3569     /**
3570      * Override onPanelClosed() to capture the panel closing event,
3571      * allowing us to set the poke lock correctly whenever the option
3572      * menu panel goes away.
3573      */
3574     @Override
3575     public void onPanelClosed(int featureId, Menu menu) {
3576         if (VDBG) log("onPanelClosed(featureId = " + featureId + ")...");
3577
3578         // We only want this special behavior for the "options panel"
3579         // feature (i.e. the standard menu triggered by the MENU button.)
3580         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
3581             // TODO: May need to return to the original wake state here
3582             // if onCreatePanelView ends up changing the wake state.
3583         }
3584
3585         super.onPanelClosed(featureId, menu);
3586     }
3587
3588     //
3589     // Bluetooth helper methods.
3590     //
3591     // - BluetoothDevice is the Bluetooth system service
3592     //   (Context.BLUETOOTH_SERVICE).  If getSystemService() returns null
3593     //   then the device is not BT capable.  Use BluetoothDevice.isEnabled()
3594     //   to see if BT is enabled on the device.
3595     //
3596     // - BluetoothHeadset is the API for the control connection to a
3597     //   Bluetooth Headset.  This lets you completely connect/disconnect a
3598     //   headset (which we don't do from the Phone UI!) but also lets you
3599     //   get the address of the currently active headset and see whether
3600     //   it's currently connected.
3601     //
3602     // - BluetoothHandsfree is the API to control the audio connection to
3603     //   a bluetooth headset. We use this API to switch the headset on and
3604     //   off when the user presses the "Bluetooth" button.
3605     //   Our BluetoothHandsfree instance (mBluetoothHandsfree) is created
3606     //   by the PhoneApp and will be null if the device is not BT capable.
3607     //
3608
3609     /**
3610      * @return true if the Bluetooth on/off switch in the UI should be
3611      *         available to the user (i.e. if the device is BT-capable
3612      *         and a headset is connected.)
3613      */
3614     /* package */ boolean isBluetoothAvailable() {
3615         if (VDBG) log("isBluetoothAvailable()...");
3616         if (mBluetoothHandsfree == null) {
3617             // Device is not BT capable.
3618             if (VDBG) log("  ==> FALSE (not BT capable)");
3619             return false;
3620         }
3621
3622         // There's no need to ask the Bluetooth system service if BT is enabled:
3623         //
3624         //    BluetoothDevice bluetoothDevice =
3625         //            (BluetoothDevice) getSystemService(Context.BLUETOOTH_SERVICE);
3626         //    if ((bluetoothDevice == null) || !bluetoothDevice.isEnabled()) {
3627         //        if (DBG) log("  ==> FALSE (BT not enabled)");
3628         //        return false;
3629         //    }
3630         //    if (DBG) log("  - BT enabled!  device name " + bluetoothDevice.getName()
3631         //                 + ", address " + bluetoothDevice.getAddress());
3632         //
3633         // ...since we already have a BluetoothHeadset instance.  We can just
3634         // call isConnected() on that, and assume it'll be false if BT isn't
3635         // enabled at all.
3636
3637         // Check if there's a connected headset, using the BluetoothHeadset API.
3638         boolean isConnected = false;
3639         if (mBluetoothHeadset != null) {
3640             if (VDBG) log("  - headset state = " + mBluetoothHeadset.getState());
3641             String headsetAddress = mBluetoothHeadset.getHeadsetAddress();
3642             if (VDBG) log("  - headset address: " + headsetAddress);
3643             if (headsetAddress != null) {
3644                 isConnected = mBluetoothHeadset.isConnected(headsetAddress);
3645                 if (VDBG) log("  - isConnected: " + isConnected);
3646             }
3647         }
3648
3649         if (VDBG) log("  ==> " + isConnected);
3650         return isConnected;
3651     }
3652
3653     /**
3654      * @return true if a BT device is available, and its audio is currently connected.
3655      */
3656     /* package */ boolean isBluetoothAudioConnected() {
3657         if (mBluetoothHandsfree == null) {
3658             if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHandsfree)");
3659             return false;
3660         }
3661         boolean isAudioOn = mBluetoothHandsfree.isAudioOn();
3662         if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
3663         return isAudioOn;
3664     }
3665
3666     /**
3667      * Helper method used to control the state of the green LED in the
3668      * "Bluetooth" menu item.
3669      *
3670      * @return true if a BT device is available and its audio is currently connected,
3671      *              <b>or</b> if we issued a BluetoothHandsfree.userWantsAudioOn()
3672      *              call within the last 5 seconds (which presumably means
3673      *              that the BT audio connection is currently being set
3674      *              up, and will be connected soon.)
3675      */
3676     /* package */ boolean isBluetoothAudioConnectedOrPending() {
3677         if (isBluetoothAudioConnected()) {
3678             if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
3679             return true;
3680         }
3681
3682         // If we issued a userWantsAudioOn() call "recently enough", even
3683         // if BT isn't actually connected yet, let's still pretend BT is
3684         // on.  This is how we make the green LED in the menu item turn on
3685         // right away.
3686         if (mBluetoothConnectionPending) {
3687             long timeSinceRequest =
3688                     SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
3689             if (timeSinceRequest < 5000 /* 5 seconds */) {
3690                 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
3691                              + timeSinceRequest + " msec ago)");
3692                 return true;
3693             } else {
3694                 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
3695                              + timeSinceRequest + " msec ago)");
3696                 mBluetoothConnectionPending = false;
3697                 return false;
3698             }
3699         }
3700
3701         if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
3702         return false;
3703     }
3704
3705     /**
3706      * Posts a message to our handler saying to update the onscreen UI
3707      * based on a bluetooth headset state change.
3708      */
3709     /* package */ void updateBluetoothIndication() {
3710         if (VDBG) log("updateBluetoothIndication()...");
3711         // No need to look at the current state here; any UI elements that
3712         // care about the bluetooth state (i.e. the CallCard) get
3713         // the necessary state directly from PhoneApp.showBluetoothIndication().
3714         mHandler.removeMessages(BLUETOOTH_STATE_CHANGED);
3715         mHandler.sendEmptyMessage(BLUETOOTH_STATE_CHANGED);
3716     }
3717
3718     private void dumpBluetoothState() {
3719         log("============== dumpBluetoothState() =============");
3720         log("= isBluetoothAvailable: " + isBluetoothAvailable());
3721         log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
3722         log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
3723         log("= PhoneApp.showBluetoothIndication: "
3724             + PhoneApp.getInstance().showBluetoothIndication());
3725         log("=");
3726         if (mBluetoothHandsfree != null) {
3727             log("= BluetoothHandsfree.isAudioOn: " + mBluetoothHandsfree.isAudioOn());
3728             if (mBluetoothHeadset != null) {
3729                 String headsetAddress = mBluetoothHeadset.getHeadsetAddress();
3730                 log("= BluetoothHeadset.getHeadsetAddress: " + headsetAddress);
3731                 if (headsetAddress != null) {
3732                     log("= BluetoothHeadset.isConnected: "
3733                         + mBluetoothHeadset.isConnected(headsetAddress));
3734                 }
3735             } else {
3736                 log("= mBluetoothHeadset is null");
3737             }
3738         } else {
3739             log("= mBluetoothHandsfree is null; device is not BT capable");
3740         }
3741     }
3742
3743     /* package */ void connectBluetoothAudio() {
3744         if (VDBG) log("connectBluetoothAudio()...");
3745         if (mBluetoothHandsfree != null) {
3746             mBluetoothHandsfree.userWantsAudioOn();
3747         }
3748
3749         // Watch out: The bluetooth connection doesn't happen instantly;
3750         // the userWantsAudioOn() call returns instantly but does its real
3751         // work in another thread.  Also, in practice the BT connection
3752         // takes longer than MENU_DISMISS_DELAY to complete(!) so we need
3753         // a little trickery here to make the menu item's green LED update
3754         // instantly.
3755         // (See isBluetoothAudioConnectedOrPending() above.)
3756         mBluetoothConnectionPending = true;
3757         mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
3758     }
3759
3760     /* package */ void disconnectBluetoothAudio() {
3761         if (VDBG) log("disconnectBluetoothAudio()...");
3762         if (mBluetoothHandsfree != null) {
3763             mBluetoothHandsfree.userWantsAudioOff();
3764         }
3765         mBluetoothConnectionPending = false;
3766     }
3767
3768     //
3769     // "Touch lock" UI.
3770     //
3771     // When the DTMF dialpad is up, after a certain amount of idle time we
3772     // display an overlay graphic on top of the dialpad and "lock" the
3773     // touch UI.  (UI Rationale: We need *some* sort of screen lock, with
3774     // a fairly short timeout, to avoid false touches from the user's face
3775     // while in-call.  But we *don't* want to do this by turning off the
3776     // screen completely, since that's confusing (the user can't tell
3777     // what's going on) *and* it's fairly cumbersome to have to hit MENU
3778     // to bring the screen back, rather than using some gesture on the
3779     // touch screen.)
3780     //
3781     // The user can dismiss the touch lock overlay by double-tapping on
3782     // the central "lock" icon.  Also, the touch lock overlay will go away
3783     // by itself if the DTMF dialpad is dismissed for any reason, such as
3784     // the current call getting disconnected (see onDialerClose()).
3785     //
3786
3787     /**
3788      * Initializes the "touch lock" UI widgets.  We do this lazily
3789      * to avoid slowing down the initial launch of the InCallScreen.
3790      */
3791     private void initTouchLock() {
3792         if (VDBG) log("initTouchLock()...");
3793         if (mTouchLockOverlay != null) {
3794             Log.w(LOG_TAG, "initTouchLock: already initialized!");
3795             return;
3796         }
3797
3798         mTouchLockOverlay = (View) findViewById(R.id.touchLockOverlay);
3799         // Note mTouchLockOverlay's visibility is initially GONE.
3800         mTouchLockIcon = (View) findViewById(R.id.touchLockIcon);
3801
3802         // Handle touch events.  (Basically mTouchLockOverlay consumes and
3803         // discards any touch events it sees, and mTouchLockIcon listens
3804         // for the "double-tap to unlock" gesture.)
3805         mTouchLockOverlay.setOnTouchListener(this);
3806         mTouchLockIcon.setOnTouchListener(this);
3807
3808         mTouchLockFadeIn = AnimationUtils.loadAnimation(this, R.anim.touch_lock_fade_in);
3809     }
3810
3811     private boolean isTouchLocked() {
3812         return (mTouchLockOverlay != null) && (mTouchLockOverlay.getVisibility() == View.VISIBLE);
3813     }
3814
3815     /**
3816      * Enables or disables the "touch lock" overlay on top of the DTMF dialpad.
3817      *
3818      * If enable=true, bring up the overlay immediately using an animated
3819      * fade-in effect.  (Or do nothing if the overlay isn't appropriate
3820      * right now, like if the dialpad isn't up, or the speaker is on.)
3821      *
3822      * If enable=false, immediately take down the overlay.  (Or do nothing
3823      * if the overlay isn't actually up right now.)
3824      *
3825      * Note that with enable=false this method will *not* automatically
3826      * start the touch lock timer.  (So when taking down the overlay while
3827      * the dialer is still up, the caller is also responsible for calling
3828      * resetTouchLockTimer(), to make sure the overlay will get
3829      * (re-)enabled later.)
3830      *
3831      */
3832     private void enableTouchLock(boolean enable) {
3833         if (VDBG) log("enableTouchLock(" + enable + ")...");
3834         if (enable) {
3835             // The "touch lock" overlay is only ever used on top of the
3836             // DTMF dialpad.
3837             if (!mDialer.isOpened()) {
3838                 if (VDBG) log("enableTouchLock: dialpad isn't up, no need to lock screen.");
3839                 return;
3840             }
3841
3842             // Also, the "touch lock" overlay NEVER appears if the speaker is in use.
3843             if (PhoneUtils.isSpeakerOn(getApplicationContext())) {
3844                 if (VDBG) log("enableTouchLock: speaker is on, no need to lock screen.");
3845                 return;
3846             }
3847
3848             // Initialize the UI elements if necessary.
3849             if (mTouchLockOverlay == null) {
3850                 initTouchLock();
3851             }
3852
3853             // First take down the menu if it's up (since it's confusing
3854             // to see a touchable menu *above* the touch lock overlay.)
3855             // Note dismissMenu() has no effect if the menu is already closed.
3856             dismissMenu(true);  // dismissImmediate = true
3857
3858             // Bring up the touch lock overlay (with an animated fade)
3859             mTouchLockOverlay.setVisibility(View.VISIBLE);
3860             mTouchLockOverlay.startAnimation(mTouchLockFadeIn);
3861         } else {
3862             // TODO: it might be nice to immediately kill the animation if
3863             // we're in the middle of fading-in:
3864             //   if (mTouchLockFadeIn.hasStarted() && !mTouchLockFadeIn.hasEnded()) {
3865             //      mTouchLockOverlay.clearAnimation();
3866             //   }
3867             // but the fade-in is so quick that this probably isn't necessary.
3868
3869             // Take down the touch lock overlay (immediately)
3870             if (mTouchLockOverlay != null) mTouchLockOverlay.setVisibility(View.GONE);
3871         }
3872     }
3873
3874     /**
3875      * Schedule the "touch lock" overlay to begin fading in after a short
3876      * delay, but only if the DTMF dialpad is currently visible.
3877      *
3878      * (This is designed to be triggered on any user activity
3879      * while the dialpad is up but not locked, and also
3880      * whenever the user "unlocks" the touch lock overlay.)
3881      *
3882      * Calling this method supersedes any previous resetTouchLockTimer()
3883      * calls (i.e. we first clear any pending TOUCH_LOCK_TIMER messages.)
3884      */
3885     private void resetTouchLockTimer() {
3886         if (VDBG) log("resetTouchLockTimer()...");
3887         mHandler.removeMessages(TOUCH_LOCK_TIMER);
3888         if (mDialer.isOpened() && !isTouchLocked()) {
3889             // The touch lock delay value comes from Gservices; we use
3890             // the same value that's used for the PowerManager's
3891             // POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest possible
3892             // screen timeout behavior.)
3893
3894             // Do a fresh lookup each time, since Gservices values can
3895             // change on the fly.  (The Settings.Gservices helper class
3896             // caches these values so this call is usually cheap.)
3897             int touchLockDelay = Settings.Gservices.getInt(
3898                     getContentResolver(),
3899                     Settings.Gservices.SHORT_KEYLIGHT_DELAY_MS,
3900                     TOUCH_LOCK_DELAY_DEFAULT);
3901             mHandler.sendEmptyMessageDelayed(TOUCH_LOCK_TIMER, touchLockDelay);
3902         }
3903     }
3904
3905     /**
3906      * Handles the TOUCH_LOCK_TIMER event.
3907      * @see resetTouchLockTimer
3908      */
3909     private void touchLockTimerExpired() {
3910         // Ok, it's been long enough since we had any user activity with
3911         // the DTMF dialpad up.  If the dialpad is still up, start fading
3912         // in the "touch lock" overlay.
3913         enableTouchLock(true);
3914     }
3915
3916     // View.OnTouchListener implementation
3917     public boolean onTouch(View v, MotionEvent event) {
3918         if (VDBG) log ("onTouch(View " + v + ")...");
3919
3920         // Handle touch events on the "touch lock" overlay.
3921         if ((v == mTouchLockIcon) || (v == mTouchLockOverlay)) {
3922
3923             // TODO: move this big hunk of code to a helper function, or
3924             // even better out to a separate helper class containing all
3925             // the touch lock overlay code.
3926
3927             // We only care about these touches while the touch lock UI is
3928             // visible (including the time during the fade-in animation.)
3929             if (!isTouchLocked()) {
3930                 // Got an event from the touch lock UI, but we're not locked!
3931                 // (This was probably a touch-UP right after we unlocked.
3932                 // Ignore it.)
3933                 return false;
3934             }
3935
3936             // (v == mTouchLockIcon) means the user hit the lock icon in the
3937             // middle of the screen, and (v == mTouchLockOverlay) is a touch
3938             // anywhere else on the overlay.
3939
3940             if (v == mTouchLockIcon) {
3941                 // Direct hit on the "lock" icon.  Handle the double-tap gesture.
3942                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
3943                     long now = SystemClock.uptimeMillis();
3944                     if (VDBG) log("- touch lock icon: handling a DOWN event, t = " + now);
3945
3946                     // Look for the double-tap gesture:
3947                     if (now < mTouchLockLastTouchTime + ViewConfiguration.getDoubleTapTimeout()) {
3948                         if (VDBG) log("==> touch lock icon: DOUBLE-TAP!");
3949                         // This was the 2nd tap of a double-tap gesture.
3950                         // Take down the touch lock overlay, but post a
3951                         // message in the future to bring it back later.
3952                         enableTouchLock(false);
3953                         resetTouchLockTimer();
3954                         // This counts as explicit "user activity".
3955                         PhoneApp.getInstance().pokeUserActivity();
3956                     }
3957                 } else if (event.getAction() == MotionEvent.ACTION_UP) {
3958                     // Stash away the current time in case this is the first
3959                     // tap of a double-tap gesture.  (We measure the time from
3960                     // the first tap's UP to the second tap's DOWN.)
3961                     mTouchLockLastTouchTime = SystemClock.uptimeMillis();
3962                 }
3963
3964                 // And regardless of what just happened, we *always* consume
3965                 // touch events while the touch lock UI is (or was) visible.
3966                 return true;
3967
3968             } else {  // (v == mTouchLockOverlay)
3969                 // User touched the "background" area of the touch lock overlay.
3970
3971                 // TODO: If we're in the middle of the fade-in animation,
3972                 // consider making a touch *anywhere* immediately unlock the
3973                 // UI.  This could be risky, though, if the user tries to
3974                 // *double-tap* during the fade-in (in which case the 2nd tap
3975                 // might 't become a false touch on the dialpad!)
3976                 //
3977                 //if (event.getAction() == MotionEvent.ACTION_DOWN) {
3978                 //    if (DBG) log("- touch lock overlay background: handling a DOWN event.");
3979                 //
3980                 //    if (mTouchLockFadeIn.hasStarted() && !mTouchLockFadeIn.hasEnded()) {
3981                 //        // If we're still fading-in, a touch *anywhere* onscreen
3982                 //        // immediately unlocks.
3983                 //        if (DBG) log("==> touch lock: tap during fade-in!");
3984                 //
3985                 //        mTouchLockOverlay.clearAnimation();
3986                 //        enableTouchLock(false);
3987                 //        // ...but post a message in the future to bring it
3988                 //        // back later.
3989                 //        resetTouchLockTimer();
3990                 //    }
3991                 //}
3992
3993                 // And regardless of what just happened, we *always* consume
3994                 // touch events while the touch lock UI is (or was) visible.
3995                 return true;
3996             }
3997
3998         // Handle touch events on the onscreen "answer" button.
3999         } else if (v == mOnscreenAnswerButton) {
4000
4001             // TODO: this "double-tap detection" code is also duplicated
4002             // above (for mTouchLockIcon).  Instead, extract it out to a
4003             // helper class that can listen for double-taps on an
4004             // arbitrary View, or maybe even a whole new "DoubleTapButton"
4005             // widget.
4006
4007             // Look for the double-tap gesture.
4008             if (event.getAction() == MotionEvent.ACTION_DOWN) {
4009                 long now = SystemClock.uptimeMillis();
4010                 if (DBG) log("- onscreen answer button: handling a DOWN event, t = " + now);  // foo -- VDBG
4011
4012                 // Look for the double-tap gesture:
4013                 if (now < mOnscreenAnswerButtonLastTouchTime + ViewConfiguration.getDoubleTapTimeout()) {
4014                     if (DBG) log("==> onscreen answer button: DOUBLE-TAP!");
4015                     // This was the 2nd tap of the double-tap gesture: answer the call!
4016
4017                     final boolean hasRingingCall = !mRingingCall.isIdle();
4018                     if (hasRingingCall) {
4019                         final boolean hasActiveCall = !mForegroundCall.isIdle();
4020                         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
4021                         if (hasActiveCall && hasHoldingCall) {
4022                             if (DBG) log("onscreen answer button: ringing (both lines in use) ==> answer!");
4023                             internalAnswerCallBothLinesInUse();
4024                         } else {
4025                             if (DBG) log("onscreen answer button: ringing ==> answer!");
4026                             internalAnswerCall();  // Automatically holds the current active call,
4027                                                    // if there is one
4028                         }
4029                     } else {
4030                         // The ringing call presumably stopped just when
4031                         // the user was double-tapping.
4032                         if (DBG) log("onscreen answer button: no ringing call (any more); ignoring...");
4033                     }
4034                 }
4035                 // The onscreen "answer" button will go away as soon as
4036                 // the phone goes from ringing to offhook, since that
4037                 // state change will trigger an updateScreen() call.
4038                 // TODO: consider explicitly starting some fancier
4039                 // animation here, like fading out the "answer" button, or
4040                 // sliding it offscreen...
4041
4042             } else if (event.getAction() == MotionEvent.ACTION_UP) {
4043                 // Stash away the current time in case this is the first
4044                 // tap of a double-tap gesture.  (We measure the time from
4045                 // the first tap's UP to the second tap's DOWN.)
4046                 mOnscreenAnswerButtonLastTouchTime = SystemClock.uptimeMillis();
4047             }
4048
4049             // And regardless of what just happened, we *always*
4050             // consume touch events to this button.
4051             return true;
4052
4053         } else {
4054             Log.w(LOG_TAG, "onTouch: event from unexpected View: " + v);
4055             return false;
4056         }
4057     }
4058
4059     // Any user activity while the dialpad is up, but not locked, should
4060     // reset the touch lock timer back to the full delay amount.
4061     @Override
4062     public void onUserInteraction() {
4063         if (mDialer.isOpened() && !isTouchLocked()) {
4064             resetTouchLockTimer();
4065         }
4066     }
4067
4068
4069     private void log(String msg) {
4070         Log.d(LOG_TAG, msg);
4071     }
4072 }