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