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