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