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