• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadset;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.DialogInterface.OnCancelListener;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.res.Resources;
31 import android.graphics.Shader;
32 import android.graphics.Typeface;
33 import android.graphics.drawable.BitmapDrawable;
34 import android.graphics.drawable.Drawable;
35 import android.media.AudioManager;
36 import android.net.Uri;
37 import android.os.AsyncResult;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.Message;
41 import android.os.SystemClock;
42 import android.os.SystemProperties;
43 import android.provider.Checkin;
44 import android.provider.Settings;
45 import android.telephony.PhoneNumberUtils;
46 import android.telephony.ServiceState;
47 import android.text.TextUtils;
48 import android.text.method.DialerKeyListener;
49 import android.util.Log;
50 import android.view.KeyEvent;
51 import android.view.Menu;
52 import android.view.MotionEvent;
53 import android.view.View;
54 import android.view.ViewConfiguration;
55 import android.view.ViewGroup;
56 import android.view.Window;
57 import android.view.WindowManager;
58 import android.view.animation.Animation;
59 import android.view.animation.AnimationUtils;
60 import android.widget.EditText;
61 import android.widget.ImageView;
62 import android.widget.LinearLayout;
63 import android.widget.SlidingDrawer;
64 import android.widget.TextView;
65 import android.widget.Toast;
66 
67 import com.android.internal.telephony.Call;
68 import com.android.internal.telephony.Connection;
69 import com.android.internal.telephony.MmiCode;
70 import com.android.internal.telephony.Phone;
71 import com.android.phone.OtaUtils.CdmaOtaInCallScreenUiState;
72 import com.android.phone.OtaUtils.CdmaOtaScreenState;
73 
74 import java.util.List;
75 
76 /**
77  * Phone app "in call" screen.
78  */
79 public class InCallScreen extends Activity
80         implements View.OnClickListener, View.OnTouchListener {
81     private static final String LOG_TAG = "InCallScreen";
82 
83     private static final boolean DBG =
84             (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
85     private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);
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     // TODO: Should be EXTRA_SHOW_DIALPAD for consistency.
103     static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
104 
105     /**
106      * Intent extra to specify the package name of the gateway
107      * provider.  Used to get the name displayed in the in-call screen
108      * during the call setup. The value is a string.
109      */
110     // TODO: This extra is currently set by the gateway application as
111     // a temporary measure. Ultimately, the framework will securely
112     // set it.
113     /* package */ static final String EXTRA_GATEWAY_PROVIDER_PACKAGE =
114             "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
115 
116     /**
117      * Intent extra to specify the URI of the provider to place the
118      * call. The value is a string. It holds the gateway address
119      * (phone gateway URL should start with the 'tel:' scheme) that
120      * will actually be contacted to call the number passed in the
121      * intent URL or in the EXTRA_PHONE_NUMBER extra.
122      */
123     // TODO: Should the value be a Uri (Parcelable)? Need to make sure
124     // MMI code '#' don't get confused as URI fragments.
125     /* package */ static final String EXTRA_GATEWAY_URI =
126             "com.android.phone.extra.GATEWAY_URI";
127 
128     // Event values used with Checkin.Events.Tag.PHONE_UI events:
129     /** The in-call UI became active */
130     static final String PHONE_UI_EVENT_ENTER = "enter";
131     /** User exited the in-call UI */
132     static final String PHONE_UI_EVENT_EXIT = "exit";
133     /** User clicked one of the touchable in-call buttons */
134     static final String PHONE_UI_EVENT_BUTTON_CLICK = "button_click";
135 
136     // Amount of time (in msec) that we display the "Call ended" state.
137     // The "short" value is for calls ended by the local user, and the
138     // "long" value is for calls ended by the remote caller.
139     private static final int CALL_ENDED_SHORT_DELAY =  200;  // msec
140     private static final int CALL_ENDED_LONG_DELAY = 2000;  // msec
141 
142     // Amount of time (in msec) that we keep the in-call menu onscreen
143     // *after* the user changes the state of one of the toggle buttons.
144     private static final int MENU_DISMISS_DELAY =  1000;  // msec
145 
146     // Amount of time that we display the PAUSE alert Dialog showing the
147     // post dial string yet to be send out to the n/w
148     private static final int PAUSE_PROMPT_DIALOG_TIMEOUT = 2000;  //msec
149 
150     // The "touch lock" overlay timeout comes from Gservices; this is the default.
151     private static final int TOUCH_LOCK_DELAY_DEFAULT =  6000;  // msec
152 
153     // Amount of time for Displaying "Dialing" for 3way Calling origination
154     private static final int THREEWAY_CALLERINFO_DISPLAY_TIME = 3000; // msec
155 
156     // Amount of time that we display the provider's overlay if applicable.
157     private static final int PROVIDER_OVERLAY_TIMEOUT = 5000;  // msec
158 
159     // These are values for the settings of the auto retry mode:
160     // 0 = disabled
161     // 1 = enabled
162     // TODO (Moto):These constants don't really belong here,
163     // they should be moved to Settings where the value is being looked up in the first place
164     static final int AUTO_RETRY_OFF = 0;
165     static final int AUTO_RETRY_ON = 1;
166 
167     // Message codes; see mHandler below.
168     // Note message codes < 100 are reserved for the PhoneApp.
169     private static final int PHONE_STATE_CHANGED = 101;
170     private static final int PHONE_DISCONNECT = 102;
171     private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
172     private static final int POST_ON_DIAL_CHARS = 104;
173     private static final int WILD_PROMPT_CHAR_ENTERED = 105;
174     private static final int ADD_VOICEMAIL_NUMBER = 106;
175     private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
176     private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
177     private static final int SUPP_SERVICE_FAILED = 110;
178     private static final int DISMISS_MENU = 111;
179     private static final int ALLOW_SCREEN_ON = 112;
180     private static final int TOUCH_LOCK_TIMER = 113;
181     private static final int REQUEST_UPDATE_BLUETOOTH_INDICATION = 114;
182     private static final int PHONE_CDMA_CALL_WAITING = 115;
183     private static final int THREEWAY_CALLERINFO_DISPLAY_DONE = 116;
184     private static final int EVENT_OTA_PROVISION_CHANGE = 117;
185     private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
186     private static final int REQUEST_CLOSE_OTA_FAILURE_NOTICE = 119;
187     private static final int EVENT_PAUSE_DIALOG_COMPLETE = 120;
188     private static final int EVENT_HIDE_PROVIDER_OVERLAY = 121;  // Time to remove the overlay.
189     private static final int REQUEST_UPDATE_TOUCH_UI = 122;
190 
191     //following constants are used for OTA Call
192     public static final String ACTION_SHOW_ACTIVATION =
193            "com.android.phone.InCallScreen.SHOW_ACTIVATION";
194     public static final String OTA_NUMBER = "*228";
195     public static final String EXTRA_OTA_CALL = "android.phone.extra.OTA_CALL";
196 
197     // When InCallScreenMode is UNDEFINED set the default action
198     // to ACTION_UNDEFINED so if we are resumed the activity will
199     // know its undefined. In particular checkIsOtaCall will return
200     // false.
201     public static final String ACTION_UNDEFINED = "com.android.phone.InCallScreen.UNDEFINED";
202 
203     // High-level "modes" of the in-call UI.
204     private enum InCallScreenMode {
205         /**
206          * Normal in-call UI elements visible.
207          */
208         NORMAL,
209         /**
210          * "Manage conference" UI is visible, totally replacing the
211          * normal in-call UI.
212          */
213         MANAGE_CONFERENCE,
214         /**
215          * Non-interactive UI state.  Call card is visible,
216          * displaying information about the call that just ended.
217          */
218         CALL_ENDED,
219          /**
220          * Normal OTA in-call UI elements visible.
221          */
222         OTA_NORMAL,
223         /**
224          * OTA call ended UI visible, replacing normal OTA in-call UI.
225          */
226         OTA_ENDED,
227         /**
228          * Default state when not on call
229          */
230         UNDEFINED
231     }
232     private InCallScreenMode mInCallScreenMode = InCallScreenMode.UNDEFINED;
233 
234     // Possible error conditions that can happen on startup.
235     // These are returned as status codes from the various helper
236     // functions we call from onCreate() and/or onResume().
237     // See syncWithPhoneState() and checkIfOkToInitiateOutgoingCall() for details.
238     private enum InCallInitStatus {
239         SUCCESS,
240         VOICEMAIL_NUMBER_MISSING,
241         POWER_OFF,
242         EMERGENCY_ONLY,
243         PHONE_NOT_IN_USE,
244         NO_PHONE_NUMBER_SUPPLIED,
245         DIALED_MMI,
246         CALL_FAILED
247     }
248     private InCallInitStatus mInCallInitialStatus;  // see onResume()
249 
250     private boolean mRegisteredForPhoneStates;
251     private boolean mNeedShowCallLostDialog;
252 
253     private Phone mPhone;
254     private Call mForegroundCall;
255     private Call mBackgroundCall;
256     private Call mRingingCall;
257 
258     private BluetoothHandsfree mBluetoothHandsfree;
259     private BluetoothHeadset mBluetoothHeadset;
260     private boolean mBluetoothConnectionPending;
261     private long mBluetoothConnectionRequestTime;
262 
263     // Main in-call UI ViewGroups
264     private ViewGroup mMainFrame;
265     private ViewGroup mInCallPanel;
266 
267     // Main in-call UI elements:
268     private CallCard mCallCard;
269 
270     // UI controls:
271     private InCallControlState mInCallControlState;
272     private InCallMenu mInCallMenu;  // used on some devices
273     private InCallTouchUi mInCallTouchUi;  // used on some devices
274     private ManageConferenceUtils mManageConferenceUtils;
275 
276     // DTMF Dialer controller and its view:
277     private DTMFTwelveKeyDialer mDialer;
278     private DTMFTwelveKeyDialerView mDialerView;
279 
280     // TODO: Move these providers related fields in their own class.
281     // Optional overlay when a 3rd party provider is used.
282     private boolean mProviderOverlayVisible = false;
283     private CharSequence mProviderLabel;
284     private Drawable mProviderIcon;
285     private Uri mProviderGatewayUri;
286     // The formated address extracted from mProviderGatewayUri. User visible.
287     private String mProviderAddress;
288 
289     // For OTA Call
290     public OtaUtils otaUtils;
291 
292     private EditText mWildPromptText;
293 
294     // "Touch lock overlay" feature
295     private boolean mUseTouchLockOverlay;  // True if we use this feature on the current device
296     private View mTouchLockOverlay;  // The overlay over the whole screen
297     private View mTouchLockIcon;  // The "lock" icon in the middle of the screen
298     private Animation mTouchLockFadeIn;
299     private long mTouchLockLastTouchTime;  // in SystemClock.uptimeMillis() time base
300 
301     // Various dialogs we bring up (see dismissAllDialogs()).
302     // TODO: convert these all to use the "managed dialogs" framework.
303     //
304     // The MMI started dialog can actually be one of 2 items:
305     //   1. An alert dialog if the MMI code is a normal MMI
306     //   2. A progress dialog if the user requested a USSD
307     private Dialog mMmiStartedDialog;
308     private AlertDialog mMissingVoicemailDialog;
309     private AlertDialog mGenericErrorDialog;
310     private AlertDialog mSuppServiceFailureDialog;
311     private AlertDialog mWaitPromptDialog;
312     private AlertDialog mWildPromptDialog;
313     private AlertDialog mCallLostDialog;
314     private AlertDialog mPausePromptDialog;
315     // NOTE: if you add a new dialog here, be sure to add it to dismissAllDialogs() also.
316 
317     // TODO: If the Activity class ever provides an easy way to get the
318     // current "activity lifecycle" state, we can remove these flags.
319     private boolean mIsDestroyed = false;
320     private boolean mIsForegroundActivity = false;
321 
322     // For use with CDMA Pause/Wait dialogs
323     private String mPostDialStrAfterPause;
324     private boolean mPauseInProgress = false;
325 
326     // Flag indicating whether or not we should bring up the Call Log when
327     // exiting the in-call UI due to the Phone becoming idle.  (This is
328     // true if the most recently disconnected Call was initiated by the
329     // user, or false if it was an incoming call.)
330     // This flag is used by delayedCleanupAfterDisconnect(), and is set by
331     // onDisconnect() (which is the only place that either posts a
332     // DELAYED_CLEANUP_AFTER_DISCONNECT event *or* calls
333     // delayedCleanupAfterDisconnect() directly.)
334     private boolean mShowCallLogAfterDisconnect;
335 
336     private Handler mHandler = new Handler() {
337         @Override
338         public void handleMessage(Message msg) {
339             if (mIsDestroyed) {
340                 if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
341                 return;
342             }
343             if (!mIsForegroundActivity) {
344                 if (DBG) log("Handler: handling message " + msg + " while not in foreground");
345                 // Continue anyway; some of the messages below *want* to
346                 // be handled even if we're not the foreground activity
347                 // (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
348                 // should at least be safe to handle if we're not in the
349                 // foreground...
350             }
351 
352             PhoneApp app = PhoneApp.getInstance();
353             switch (msg.what) {
354                 case SUPP_SERVICE_FAILED:
355                     onSuppServiceFailed((AsyncResult) msg.obj);
356                     break;
357 
358                 case PHONE_STATE_CHANGED:
359                     onPhoneStateChanged((AsyncResult) msg.obj);
360                     break;
361 
362                 case PHONE_DISCONNECT:
363                     onDisconnect((AsyncResult) msg.obj);
364                     break;
365 
366                 case EVENT_HEADSET_PLUG_STATE_CHANGED:
367                     // Update the in-call UI, since some UI elements (in
368                     // particular the "Speaker" menu button) change state
369                     // depending on whether a headset is plugged in.
370                     // TODO: A full updateScreen() is overkill here, since
371                     // the value of PhoneApp.isHeadsetPlugged() only affects a
372                     // single menu item.  (But even a full updateScreen()
373                     // is still pretty cheap, so let's keep this simple
374                     // for now.)
375                     if (!isBluetoothAudioConnected()) {
376                         if (msg.arg1 != 1){
377                             // If the state is "not connected", restore the speaker state.
378                             // We ONLY want to do this on the wired headset connect /
379                             // disconnect events for now though, so we're only triggering
380                             // on EVENT_HEADSET_PLUG_STATE_CHANGED.
381                             PhoneUtils.restoreSpeakerMode(InCallScreen.this);
382                         } else {
383                             // If the state is "connected", force the speaker off without
384                             // storing the state.
385                             PhoneUtils.turnOnSpeaker(InCallScreen.this, false, false);
386                             // If the dialpad is open, we need to start the timer that will
387                             // eventually bring up the "touch lock" overlay.
388                             if (mDialer.isOpened() && !isTouchLocked()) {
389                                 resetTouchLockTimer();
390                             }
391                         }
392                     }
393                     updateScreen();
394                     break;
395 
396                 case PhoneApp.MMI_INITIATE:
397                     onMMIInitiate((AsyncResult) msg.obj);
398                     break;
399 
400                 case PhoneApp.MMI_CANCEL:
401                     onMMICancel();
402                     break;
403 
404                 // handle the mmi complete message.
405                 // since the message display class has been replaced with
406                 // a system dialog in PhoneUtils.displayMMIComplete(), we
407                 // should finish the activity here to close the window.
408                 case PhoneApp.MMI_COMPLETE:
409                     // Check the code to see if the request is ready to
410                     // finish, this includes any MMI state that is not
411                     // PENDING.
412                     MmiCode mmiCode = (MmiCode) ((AsyncResult) msg.obj).result;
413                     // if phone is a CDMA phone display feature code completed message
414                     int phoneType = mPhone.getPhoneType();
415                     if (phoneType == Phone.PHONE_TYPE_CDMA) {
416                         PhoneUtils.displayMMIComplete(mPhone, app, mmiCode, null, null);
417                     } else if (phoneType == Phone.PHONE_TYPE_GSM) {
418                         if (mmiCode.getState() != MmiCode.State.PENDING) {
419                             if (DBG) log("Got MMI_COMPLETE, finishing InCallScreen...");
420                             endInCallScreenSession();
421                         }
422                     }
423                     break;
424 
425                 case POST_ON_DIAL_CHARS:
426                     handlePostOnDialChars((AsyncResult) msg.obj, (char) msg.arg1);
427                     break;
428 
429                 case ADD_VOICEMAIL_NUMBER:
430                     addVoiceMailNumberPanel();
431                     break;
432 
433                 case DONT_ADD_VOICEMAIL_NUMBER:
434                     dontAddVoiceMailNumber();
435                     break;
436 
437                 case DELAYED_CLEANUP_AFTER_DISCONNECT:
438                     delayedCleanupAfterDisconnect();
439                     break;
440 
441                 case DISMISS_MENU:
442                     // dismissMenu() has no effect if the menu is already closed.
443                     dismissMenu(true);  // dismissImmediate = true
444                     break;
445 
446                 case ALLOW_SCREEN_ON:
447                     if (VDBG) log("ALLOW_SCREEN_ON message...");
448                     // Undo our previous call to preventScreenOn(true).
449                     // (Note this will cause the screen to turn on
450                     // immediately, if it's currently off because of a
451                     // prior preventScreenOn(true) call.)
452                     app.preventScreenOn(false);
453                     break;
454 
455                 case TOUCH_LOCK_TIMER:
456                     if (VDBG) log("TOUCH_LOCK_TIMER...");
457                     touchLockTimerExpired();
458                     break;
459 
460                 case REQUEST_UPDATE_BLUETOOTH_INDICATION:
461                     if (VDBG) log("REQUEST_UPDATE_BLUETOOTH_INDICATION...");
462                     // The bluetooth headset state changed, so some UI
463                     // elements may need to update.  (There's no need to
464                     // look up the current state here, since any UI
465                     // elements that care about the bluetooth state get it
466                     // directly from PhoneApp.showBluetoothIndication().)
467                     updateScreen();
468                     break;
469 
470                 case PHONE_CDMA_CALL_WAITING:
471                     if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
472                     Connection cn = mRingingCall.getLatestConnection();
473 
474                     // Only proceed if we get a valid connection object
475                     if (cn != null) {
476                         // Finally update screen with Call waiting info and request
477                         // screen to wake up
478                         updateScreen();
479                         app.updateWakeState();
480                     }
481                     break;
482 
483                 case THREEWAY_CALLERINFO_DISPLAY_DONE:
484                     if (DBG) log("Received THREEWAY_CALLERINFO_DISPLAY_DONE event ...");
485                     if (app.cdmaPhoneCallState.getCurrentCallState()
486                             == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
487                         // Set the mThreeWayCallOrigStateDialing state to true
488                         app.cdmaPhoneCallState.setThreeWayCallOrigState(false);
489 
490                         //Finally update screen with with the current on going call
491                         updateScreen();
492                     }
493                     break;
494 
495                 case EVENT_OTA_PROVISION_CHANGE:
496                     if (otaUtils != null) {
497                         otaUtils.onOtaProvisionStatusChanged((AsyncResult) msg.obj);
498                     }
499                     break;
500 
501                 case REQUEST_CLOSE_SPC_ERROR_NOTICE:
502                     if (otaUtils != null) {
503                         otaUtils.onOtaCloseSpcNotice();
504                     }
505                     break;
506 
507                 case REQUEST_CLOSE_OTA_FAILURE_NOTICE:
508                     if (otaUtils != null) {
509                         otaUtils.onOtaCloseFailureNotice();
510                     }
511                     break;
512 
513                 case EVENT_PAUSE_DIALOG_COMPLETE:
514                     if (mPausePromptDialog != null) {
515                         if (DBG) log("- DISMISSING mPausePromptDialog.");
516                         mPausePromptDialog.dismiss();  // safe even if already dismissed
517                         mPausePromptDialog = null;
518                     }
519                     break;
520 
521                 case EVENT_HIDE_PROVIDER_OVERLAY:
522                     mProviderOverlayVisible = false;
523                     updateProviderOverlay();  // Clear the overlay.
524                     break;
525 
526                 case REQUEST_UPDATE_TOUCH_UI:
527                     updateInCallTouchUi();
528                     break;
529             }
530         }
531     };
532 
533     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
534             @Override
535             public void onReceive(Context context, Intent intent) {
536                 String action = intent.getAction();
537                 if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
538                     // Listen for ACTION_HEADSET_PLUG broadcasts so that we
539                     // can update the onscreen UI when the headset state changes.
540                     // if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
541                     // if (DBG) log("==> intent: " + intent);
542                     // if (DBG) log("    state: " + intent.getIntExtra("state", 0));
543                     // if (DBG) log("    name: " + intent.getStringExtra("name"));
544                     // send the event and add the state as an argument.
545                     Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
546                             intent.getIntExtra("state", 0), 0);
547                     mHandler.sendMessage(message);
548                 }
549             }
550         };
551 
552 
553     @Override
onCreate(Bundle icicle)554     protected void onCreate(Bundle icicle) {
555         if (DBG) log("onCreate()...  this = " + this);
556 
557         Profiler.callScreenOnCreate();
558 
559         super.onCreate(icicle);
560 
561         final PhoneApp app = PhoneApp.getInstance();
562         app.setInCallScreenInstance(this);
563 
564         setPhone(app.phone);  // Sets mPhone and mForegroundCall/mBackgroundCall/mRingingCall
565 
566         mBluetoothHandsfree = app.getBluetoothHandsfree();
567         if (VDBG) log("- mBluetoothHandsfree: " + mBluetoothHandsfree);
568 
569         if (mBluetoothHandsfree != null) {
570             // The PhoneApp only creates a BluetoothHandsfree instance in the
571             // first place if BluetoothAdapter.getDefaultAdapter()
572             // succeeds.  So at this point we know the device is BT-capable.
573             mBluetoothHeadset = new BluetoothHeadset(this, null);
574             if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
575         }
576 
577         requestWindowFeature(Window.FEATURE_NO_TITLE);
578 
579         // Inflate everything in incall_screen.xml and add it to the screen.
580         setContentView(R.layout.incall_screen);
581 
582         initInCallScreen();
583 
584         // Create the dtmf dialer.  The dialer view we use depends on the
585         // current platform:
586         //
587         // - On non-prox-sensor devices, it's the dialpad contained inside
588         //   a SlidingDrawer widget (see dtmf_twelve_key_dialer.xml).
589         //
590         // - On "full touch UI" devices, it's the compact non-sliding
591         //   dialpad that appears on the upper half of the screen,
592         //   above the main cluster of InCallTouchUi buttons
593         //   (see non_drawer_dialpad.xml).
594         //
595         // TODO: These should both be ViewStubs, and right here we should
596         // inflate one or the other.  (Also, while doing that, let's also
597         // move this block of code over to initInCallScreen().)
598         //
599         SlidingDrawer dialerDrawer;
600         if (isTouchUiEnabled()) {
601             // This is a "full touch" device.
602             mDialerView = (DTMFTwelveKeyDialerView) findViewById(R.id.non_drawer_dtmf_dialer);
603             if (DBG) log("- Full touch device!  Found dialerView: " + mDialerView);
604             dialerDrawer = null;  // No SlidingDrawer used on this device.
605         } else {
606             // Use the old-style dialpad contained within the SlidingDrawer.
607             mDialerView = (DTMFTwelveKeyDialerView) findViewById(R.id.dtmf_dialer);
608             if (DBG) log("- Using SlidingDrawer-based dialpad.  Found dialerView: " + mDialerView);
609             dialerDrawer = (SlidingDrawer) findViewById(R.id.dialer_container);
610             if (DBG) log("  ...and the SlidingDrawer: " + dialerDrawer);
611         }
612         // Sanity-check that (regardless of the device) at least the
613         // dialer view is present:
614         if (mDialerView == null) {
615             Log.e(LOG_TAG, "onCreate: couldn't find dialerView", new IllegalStateException());
616         }
617         // Finally, create the DTMFTwelveKeyDialer instance.
618         mDialer = new DTMFTwelveKeyDialer(this, mDialerView, dialerDrawer);
619 
620         registerForPhoneStates();
621 
622         // No need to change wake state here; that happens in onResume() when we
623         // are actually displayed.
624 
625         // Handle the Intent we were launched with, but only if this is the
626         // the very first time we're being launched (ie. NOT if we're being
627         // re-initialized after previously being shut down.)
628         // Once we're up and running, any future Intents we need
629         // to handle will come in via the onNewIntent() method.
630         if (icicle == null) {
631             if (DBG) log("onCreate(): this is our very first launch, checking intent...");
632 
633             // Stash the result code from internalResolveIntent() in the
634             // mInCallInitialStatus field.  If it's an error code, we'll
635             // handle it in onResume().
636             mInCallInitialStatus = internalResolveIntent(getIntent());
637             if (DBG) log("onCreate(): mInCallInitialStatus = " + mInCallInitialStatus);
638             if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
639                 Log.w(LOG_TAG, "onCreate: status " + mInCallInitialStatus
640                       + " from internalResolveIntent()");
641                 // See onResume() for the actual error handling.
642             }
643         } else {
644             mInCallInitialStatus = InCallInitStatus.SUCCESS;
645         }
646 
647         // The "touch lock overlay" feature is used only on devices that
648         // *don't* use a proximity sensor to turn the screen off while in-call.
649         mUseTouchLockOverlay = !app.proximitySensorModeEnabled();
650 
651         Profiler.callScreenCreated();
652         if (DBG) log("onCreate(): exit");
653     }
654 
655     /**
656      * Sets the Phone object used internally by the InCallScreen.
657      *
658      * In normal operation this is called from onCreate(), and the
659      * passed-in Phone object comes from the PhoneApp.
660      * For testing, test classes can use this method to
661      * inject a test Phone instance.
662      */
setPhone(Phone phone)663     /* package */ void setPhone(Phone phone) {
664         mPhone = phone;
665         // Hang onto the three Call objects too; they're singletons that
666         // are constant (and never null) for the life of the Phone.
667         mForegroundCall = mPhone.getForegroundCall();
668         mBackgroundCall = mPhone.getBackgroundCall();
669         mRingingCall = mPhone.getRingingCall();
670     }
671 
672     @Override
onResume()673     protected void onResume() {
674         if (DBG) log("onResume()...");
675         super.onResume();
676 
677         mIsForegroundActivity = true;
678 
679         final PhoneApp app = PhoneApp.getInstance();
680 
681         // Disable the keyguard the entire time the InCallScreen is
682         // active.  (This is necessary only for the case of receiving an
683         // incoming call while the device is locked; we need to disable
684         // the keyguard so you can answer the call and use the in-call UI,
685         // but we always re-enable the keyguard as soon as you leave this
686         // screen (see onPause().))
687         app.disableKeyguard();
688         app.disableStatusBar();
689 
690         // Touch events are never considered "user activity" while the
691         // InCallScreen is active, so that unintentional touches won't
692         // prevent the device from going to sleep.
693         app.setIgnoreTouchUserActivity(true);
694 
695         // Disable the status bar "window shade" the entire time we're on
696         // the in-call screen.
697         NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(false);
698 
699         // Listen for broadcast intents that might affect the onscreen UI.
700         registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
701 
702         // Keep a "dialer session" active when we're in the foreground.
703         // (This is needed to play DTMF tones.)
704         mDialer.startDialerSession();
705 
706         // Check for any failures that happened during onCreate() or onNewIntent().
707         if (DBG) log("- onResume: initial status = " + mInCallInitialStatus);
708         if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
709             if (DBG) log("- onResume: failure during startup: " + mInCallInitialStatus);
710 
711             // Don't bring up the regular Phone UI!  Instead bring up
712             // something more specific to let the user deal with the
713             // problem.
714             handleStartupError(mInCallInitialStatus);
715 
716             // But it *is* OK to continue with the rest of onResume(),
717             // since any further setup steps (like updateScreen() and the
718             // CallCard setup) will fall back to a "blank" state if the
719             // phone isn't in use.
720             mInCallInitialStatus = InCallInitStatus.SUCCESS;
721         }
722 
723         // Set the volume control handler while we are in the foreground.
724         final boolean bluetoothConnected = isBluetoothAudioConnected();
725 
726         if (bluetoothConnected) {
727             setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
728         } else {
729             setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
730         }
731 
732         takeKeyEvents(true);
733 
734         boolean phoneIsCdma = (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA);
735 
736         if (phoneIsCdma) {
737             initOtaState();
738         }
739 
740         if ((mInCallScreenMode != InCallScreenMode.OTA_NORMAL) &&
741                 (mInCallScreenMode != InCallScreenMode.OTA_ENDED)) {
742             // Always start off in NORMAL mode
743             setInCallScreenMode(InCallScreenMode.NORMAL);
744         }
745 
746         // Before checking the state of the phone, clean up any
747         // connections in the DISCONNECTED state.
748         // (The DISCONNECTED state is used only to drive the "call ended"
749         // UI; it's totally useless when *entering* the InCallScreen.)
750         mPhone.clearDisconnected();
751 
752         InCallInitStatus status = syncWithPhoneState();
753         if (status != InCallInitStatus.SUCCESS) {
754             if (DBG) log("- syncWithPhoneState failed! status = " + status);
755             // Couldn't update the UI, presumably because the phone is totally
756             // idle.  But don't endInCallScreenSession immediately, since we might still
757             // have an error dialog up that the user needs to see.
758             // (And in that case, the error dialog is responsible for calling
759             // endInCallScreenSession when the user dismisses it.)
760         } else if (phoneIsCdma) {
761             if (mInCallScreenMode == InCallScreenMode.OTA_NORMAL ||
762                     mInCallScreenMode == InCallScreenMode.OTA_ENDED) {
763                 mDialer.setHandleVisible(false);
764                 if (mInCallPanel != null) mInCallPanel.setVisibility(View.GONE);
765                 updateScreen();
766                 return;
767             }
768         }
769 
770         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
771             // InCallScreen is now active.
772             Checkin.logEvent(getContentResolver(),
773                              Checkin.Events.Tag.PHONE_UI,
774                              PHONE_UI_EVENT_ENTER);
775         }
776 
777         // Update the poke lock and wake lock when we move to
778         // the foreground.
779         //
780         // But we need to do something special if we're coming
781         // to the foreground while an incoming call is ringing:
782         if (mPhone.getState() == Phone.State.RINGING) {
783             // If the phone is ringing, we *should* already be holding a
784             // full wake lock (which we would have acquired before
785             // firing off the intent that brought us here; see
786             // PhoneUtils.showIncomingCallUi().)
787             //
788             // We also called preventScreenOn(true) at that point, to
789             // avoid cosmetic glitches while we were being launched.
790             // So now we need to post an ALLOW_SCREEN_ON message to
791             // (eventually) undo the prior preventScreenOn(true) call.
792             //
793             // (In principle we shouldn't do this until after our first
794             // layout/draw pass.  But in practice, the delay caused by
795             // simply waiting for the end of the message queue is long
796             // enough to avoid any flickering of the lock screen before
797             // the InCallScreen comes up.)
798             if (VDBG) log("- posting ALLOW_SCREEN_ON message...");
799             mHandler.removeMessages(ALLOW_SCREEN_ON);
800             mHandler.sendEmptyMessage(ALLOW_SCREEN_ON);
801 
802             // TODO: There ought to be a more elegant way of doing this,
803             // probably by having the PowerManager and ActivityManager
804             // work together to let apps request that the screen on/off
805             // state be synchronized with the Activity lifecycle.
806             // (See bug 1648751.)
807         } else {
808             // The phone isn't ringing; this is either an outgoing call, or
809             // we're returning to a call in progress.  There *shouldn't* be
810             // any prior preventScreenOn(true) call that we need to undo,
811             // but let's do this just to be safe:
812             app.preventScreenOn(false);
813         }
814         app.updateWakeState();
815 
816         // The "touch lock" overlay is NEVER visible when we resume.
817         // (In particular, this check ensures that we won't still be
818         // locked after the user wakes up the screen by pressing MENU.)
819         enableTouchLock(false);
820         // ...but if the dialpad is open we DO need to start the timer
821         // that will eventually bring up the "touch lock" overlay.
822         if (mDialer.isOpened()) resetTouchLockTimer();
823 
824         // Restore the mute state if the last mute state change was NOT
825         // done by the user.
826         if (app.getRestoreMuteOnInCallResume()) {
827             PhoneUtils.restoreMuteState(mPhone);
828             app.setRestoreMuteOnInCallResume(false);
829         }
830 
831         Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
832         if (VDBG) log("onResume() done.");
833     }
834 
835     // onPause is guaranteed to be called when the InCallScreen goes
836     // in the background.
837     @Override
onPause()838     protected void onPause() {
839         if (DBG) log("onPause()...");
840         super.onPause();
841 
842         mIsForegroundActivity = false;
843 
844         // Force a clear of the provider overlay' frame. Since the
845         // overlay is removed using a timed message, it is
846         // possible we missed it if the prev call was interrupted.
847         mProviderOverlayVisible = false;
848         updateProviderOverlay();
849 
850         final PhoneApp app = PhoneApp.getInstance();
851 
852         // A safety measure to disable proximity sensor in case call failed
853         // and the telephony state did not change.
854         app.setBeginningCall(false);
855 
856         // Make sure the "Manage conference" chronometer is stopped when
857         // we move away from the foreground.
858         mManageConferenceUtils.stopConferenceTime();
859 
860         // as a catch-all, make sure that any dtmf tones are stopped
861         // when the UI is no longer in the foreground.
862         mDialer.onDialerKeyUp(null);
863 
864         // Release any "dialer session" resources, now that we're no
865         // longer in the foreground.
866         mDialer.stopDialerSession();
867 
868         // If the device is put to sleep as the phone call is ending,
869         // we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
870         // event gets handled AFTER the device goes to sleep and wakes
871         // up again.
872 
873         // This is because it is possible for a sleep command
874         // (executed with the End Call key) to come during the 2
875         // seconds that the "Call Ended" screen is up.  Sleep then
876         // pauses the device (including the cleanup event) and
877         // resumes the event when it wakes up.
878 
879         // To fix this, we introduce a bit of code that pushes the UI
880         // to the background if we pause and see a request to
881         // DELAYED_CLEANUP_AFTER_DISCONNECT.
882 
883         // Note: We can try to finish directly, by:
884         //  1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
885         //  2. Calling delayedCleanupAfterDisconnect directly
886 
887         // However, doing so can cause problems between the phone
888         // app and the keyguard - the keyguard is trying to sleep at
889         // the same time that the phone state is changing.  This can
890         // end up causing the sleep request to be ignored.
891         if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)
892                 && mPhone.getState() != Phone.State.RINGING) {
893             if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
894             endInCallScreenSession();
895         }
896 
897         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
898             // InCallScreen is no longer active.
899             Checkin.logEvent(getContentResolver(),
900                              Checkin.Events.Tag.PHONE_UI,
901                              PHONE_UI_EVENT_EXIT);
902         }
903 
904         // Clean up the menu, in case we get paused while the menu is up
905         // for some reason.
906         dismissMenu(true);  // dismiss immediately
907 
908         // Dismiss any dialogs we may have brought up, just to be 100%
909         // sure they won't still be around when we get back here.
910         dismissAllDialogs();
911 
912         // Re-enable the status bar (which we disabled in onResume().)
913         NotificationMgr.getDefault().getStatusBarMgr().enableExpandedView(true);
914 
915         // Unregister for broadcast intents.  (These affect the visible UI
916         // of the InCallScreen, so we only care about them while we're in the
917         // foreground.)
918         unregisterReceiver(mReceiver);
919 
920         // Re-enable "user activity" for touch events.
921         // We actually do this slightly *after* onPause(), to work around a
922         // race condition where a touch can come in after we've paused
923         // but before the device actually goes to sleep.
924         // TODO: The PowerManager itself should prevent this from happening.
925         mHandler.postDelayed(new Runnable() {
926                 public void run() {
927                     app.setIgnoreTouchUserActivity(false);
928                 }
929             }, 500);
930 
931         // The keyguard was disabled the entire time the InCallScreen was
932         // active (see onResume()).  Re-enable it now.
933         app.reenableKeyguard();
934         app.reenableStatusBar();
935 
936         // Make sure we revert the poke lock and wake lock when we move to
937         // the background.
938         app.updateWakeState();
939     }
940 
941     @Override
onStop()942     protected void onStop() {
943         if (DBG) log("onStop()...");
944         super.onStop();
945 
946         stopTimer();
947 
948         Phone.State state = mPhone.getState();
949         if (DBG) log("onStop: state = " + state);
950 
951         if (state == Phone.State.IDLE) {
952             final PhoneApp app = PhoneApp.getInstance();
953             // when OTA Activation, OTA Success/Failure dialog or OTA SPC
954             // failure dialog is running, do not destroy inCallScreen. Because call
955             // is already ended and dialog will not get redrawn on slider event.
956             if ((app.cdmaOtaProvisionData != null) && (app.cdmaOtaScreenState != null)
957                     && ((app.cdmaOtaScreenState.otaScreenState !=
958                             CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION)
959                         && (app.cdmaOtaScreenState.otaScreenState !=
960                             CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG)
961                         && (!app.cdmaOtaProvisionData.inOtaSpcState))) {
962                 // we don't want the call screen to remain in the activity history
963                 // if there are not active or ringing calls.
964                 if (DBG) log("- onStop: calling finish() to clear activity history...");
965                 moveTaskToBack(true);
966                 if (otaUtils != null) {
967                     otaUtils.cleanOtaScreen(true);
968                 }
969             }
970         }
971     }
972 
973     @Override
onDestroy()974     protected void onDestroy() {
975         if (DBG) log("onDestroy()...");
976         super.onDestroy();
977 
978         // Set the magic flag that tells us NOT to handle any handler
979         // messages that come in asynchronously after we get destroyed.
980         mIsDestroyed = true;
981 
982         final PhoneApp app = PhoneApp.getInstance();
983         app.setInCallScreenInstance(null);
984 
985         // Clear out the InCallScreen references in various helper objects
986         // (to let them know we've been destroyed).
987         if (mInCallMenu != null) {
988             mInCallMenu.clearInCallScreenReference();
989         }
990         if (mCallCard != null) {
991             mCallCard.setInCallScreenInstance(null);
992         }
993         if (mInCallTouchUi != null) {
994             mInCallTouchUi.setInCallScreenInstance(null);
995         }
996 
997         mDialer.clearInCallScreenReference();
998         mDialer = null;
999 
1000         unregisterForPhoneStates();
1001         // No need to change wake state here; that happens in onPause() when we
1002         // are moving out of the foreground.
1003 
1004         if (mBluetoothHeadset != null) {
1005             mBluetoothHeadset.close();
1006             mBluetoothHeadset = null;
1007         }
1008 
1009         // Dismiss all dialogs, to be absolutely sure we won't leak any of
1010         // them while changing orientation.
1011         dismissAllDialogs();
1012     }
1013 
1014     /**
1015      * Dismisses the in-call screen.
1016      *
1017      * We never *really* finish() the InCallScreen, since we don't want to
1018      * get destroyed and then have to be re-created from scratch for the
1019      * next call.  Instead, we just move ourselves to the back of the
1020      * activity stack.
1021      *
1022      * This also means that we'll no longer be reachable via the BACK
1023      * button (since moveTaskToBack() puts us behind the Home app, but the
1024      * home app doesn't allow the BACK key to move you any farther down in
1025      * the history stack.)
1026      *
1027      * (Since the Phone app itself is never killed, this basically means
1028      * that we'll keep a single InCallScreen instance around for the
1029      * entire uptime of the device.  This noticeably improves the UI
1030      * responsiveness for incoming calls.)
1031      */
1032     @Override
finish()1033     public void finish() {
1034         if (DBG) log("finish()...");
1035         moveTaskToBack(true);
1036     }
1037 
1038     /**
1039      * End the current in call screen session.
1040      *
1041      * This must be called when an InCallScreen session has
1042      * complete so that the next invocation via an onResume will
1043      * not be in an old state.
1044      */
endInCallScreenSession()1045     public void endInCallScreenSession() {
1046         if (DBG) log("endInCallScreenSession()...");
1047         moveTaskToBack(true);
1048         setInCallScreenMode(InCallScreenMode.UNDEFINED);
1049     }
1050 
isForegroundActivity()1051     /* package */ boolean isForegroundActivity() {
1052         return mIsForegroundActivity;
1053     }
1054 
registerForPhoneStates()1055     private void registerForPhoneStates() {
1056         if (!mRegisteredForPhoneStates) {
1057             mPhone.registerForPreciseCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
1058             mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
1059             int phoneType = mPhone.getPhoneType();
1060             if (phoneType == Phone.PHONE_TYPE_GSM) {
1061                 mPhone.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
1062 
1063                 // register for the MMI complete message.  Upon completion,
1064                 // PhoneUtils will bring up a system dialog instead of the
1065                 // message display class in PhoneUtils.displayMMIComplete().
1066                 // We'll listen for that message too, so that we can finish
1067                 // the activity at the same time.
1068                 mPhone.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
1069             } else if (phoneType == Phone.PHONE_TYPE_CDMA) {
1070                 if (DBG) log("Registering for Call Waiting.");
1071                 mPhone.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
1072             } else {
1073                 throw new IllegalStateException("Unexpected phone type: " + phoneType);
1074             }
1075 
1076             mPhone.setOnPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
1077             mPhone.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
1078             if (phoneType == Phone.PHONE_TYPE_CDMA) {
1079                 mPhone.registerForCdmaOtaStatusChange(mHandler, EVENT_OTA_PROVISION_CHANGE, null);
1080             }
1081             mRegisteredForPhoneStates = true;
1082         }
1083     }
1084 
unregisterForPhoneStates()1085     private void unregisterForPhoneStates() {
1086         mPhone.unregisterForPreciseCallStateChanged(mHandler);
1087         mPhone.unregisterForDisconnect(mHandler);
1088         mPhone.unregisterForMmiInitiate(mHandler);
1089         mPhone.unregisterForCallWaiting(mHandler);
1090         mPhone.setOnPostDialCharacter(null, POST_ON_DIAL_CHARS, null);
1091         mPhone.unregisterForCdmaOtaStatusChange(mHandler);
1092         mRegisteredForPhoneStates = false;
1093     }
1094 
updateAfterRadioTechnologyChange()1095     /* package */ void updateAfterRadioTechnologyChange() {
1096         if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
1097         // Unregister for all events from the old obsolete phone
1098         unregisterForPhoneStates();
1099 
1100         // (Re)register for all events relevant to the new active phone
1101         registerForPhoneStates();
1102     }
1103 
1104     @Override
onNewIntent(Intent intent)1105     protected void onNewIntent(Intent intent) {
1106         if (DBG) log("onNewIntent: intent=" + intent);
1107 
1108         // We're being re-launched with a new Intent.  Since we keep
1109         // around a single InCallScreen instance for the life of the phone
1110         // process (see finish()), this sequence will happen EVERY time
1111         // there's a new incoming or outgoing call except for the very
1112         // first time the InCallScreen gets created.  This sequence will
1113         // also happen if the InCallScreen is already in the foreground
1114         // (e.g. getting a new ACTION_CALL intent while we were already
1115         // using the other line.)
1116 
1117         // Stash away the new intent so that we can get it in the future
1118         // by calling getIntent().  (Otherwise getIntent() will return the
1119         // original Intent from when we first got created!)
1120         setIntent(intent);
1121 
1122         // Activities are always paused before receiving a new intent, so
1123         // we can count on our onResume() method being called next.
1124 
1125         // Just like in onCreate(), handle this intent, and stash the
1126         // result code from internalResolveIntent() in the
1127         // mInCallInitialStatus field.  If it's an error code, we'll
1128         // handle it in onResume().
1129         mInCallInitialStatus = internalResolveIntent(intent);
1130         if (mInCallInitialStatus != InCallInitStatus.SUCCESS) {
1131             Log.w(LOG_TAG, "onNewIntent: status " + mInCallInitialStatus
1132                   + " from internalResolveIntent()");
1133             // See onResume() for the actual error handling.
1134         }
1135     }
1136 
internalResolveIntent(Intent intent)1137     /* package */ InCallInitStatus internalResolveIntent(Intent intent) {
1138         if (intent == null || intent.getAction() == null) {
1139             return InCallInitStatus.SUCCESS;
1140         }
1141 
1142         checkIsOtaCall(intent);
1143 
1144         String action = intent.getAction();
1145         if (DBG) log("internalResolveIntent: action=" + action);
1146 
1147         // The calls to setRestoreMuteOnInCallResume() inform the phone
1148         // that we're dealing with new connections (either a placing an
1149         // outgoing call or answering an incoming one, and NOT handling
1150         // an aborted "Add Call" request), so we should let the mute state
1151         // be handled by the PhoneUtils phone state change handler.
1152         final PhoneApp app = PhoneApp.getInstance();
1153         // If OTA Activation is configured for Power up scenario, then
1154         // InCallScreen UI started with Intent of ACTION_SHOW_ACTIVATION
1155         // to show OTA Activation screen at power up.
1156         if ((action.equals(ACTION_SHOW_ACTIVATION))
1157                 && ((mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA))) {
1158             setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
1159             if ((app.cdmaOtaProvisionData != null)
1160                     && (!app.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
1161                 app.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
1162                 app.cdmaOtaScreenState.otaScreenState =
1163                         CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
1164             }
1165             return InCallInitStatus.SUCCESS;
1166         } else if (action.equals(Intent.ACTION_ANSWER)) {
1167             internalAnswerCall();
1168             app.setRestoreMuteOnInCallResume(false);
1169             return InCallInitStatus.SUCCESS;
1170         } else if (action.equals(Intent.ACTION_CALL)
1171                 || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
1172             app.setRestoreMuteOnInCallResume(false);
1173 
1174             // If a provider is used, extract the info to build the
1175             // overlay and route the call.  The overlay will be
1176             // displayed the first time updateScreen is called.
1177             if (PhoneUtils.hasPhoneProviderExtras(intent)) {
1178                 mProviderLabel = PhoneUtils.getProviderLabel(this, intent);
1179                 mProviderIcon = PhoneUtils.getProviderIcon(this, intent);
1180                 mProviderGatewayUri = PhoneUtils.getProviderGatewayUri(intent);
1181                 mProviderAddress = PhoneUtils.formatProviderUri(mProviderGatewayUri);
1182                 mProviderOverlayVisible = true;
1183 
1184                 if (TextUtils.isEmpty(mProviderLabel) || null == mProviderIcon ||
1185                     null == mProviderGatewayUri || TextUtils.isEmpty(mProviderAddress)) {
1186                     clearProvider();
1187                 }
1188             } else {
1189                 clearProvider();
1190             }
1191             InCallInitStatus status = placeCall(intent);
1192             if (status == InCallInitStatus.SUCCESS) {
1193                 // Notify the phone app that a call is beginning so it can
1194                 // enable the proximity sensor
1195                 app.setBeginningCall(true);
1196             }
1197             return status;
1198         } else if (action.equals(intent.ACTION_MAIN)) {
1199             // The MAIN action is used to bring up the in-call screen without
1200             // doing any other explicit action, like when you return to the
1201             // current call after previously bailing out of the in-call UI.
1202             // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
1203             // dialpad should be initially visible.  If the extra isn't
1204             // present at all, we just leave the dialpad in its previous state.
1205 
1206             if ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL)
1207                     || (mInCallScreenMode == InCallScreenMode.OTA_ENDED)) {
1208                 // If in OTA Call, update the OTA UI
1209                 updateScreen();
1210                 return InCallInitStatus.SUCCESS;
1211             }
1212             if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
1213                 boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
1214                 if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
1215                 if (showDialpad) {
1216                     mDialer.openDialer(false);  // no "opening" animation
1217                 } else {
1218                     mDialer.closeDialer(false);  // no "closing" animation
1219                 }
1220             }
1221             return InCallInitStatus.SUCCESS;
1222         } else if (action.equals(ACTION_UNDEFINED)) {
1223             return InCallInitStatus.SUCCESS;
1224         } else {
1225             Log.w(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
1226             // But continue the best we can (basically treating this case
1227             // like ACTION_MAIN...)
1228             return InCallInitStatus.SUCCESS;
1229         }
1230     }
1231 
stopTimer()1232     private void stopTimer() {
1233         if (mCallCard != null) mCallCard.stopTimer();
1234     }
1235 
initInCallScreen()1236     private void initInCallScreen() {
1237         if (VDBG) log("initInCallScreen()...");
1238 
1239         // Have the WindowManager filter out touch events that are "too fat".
1240         getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
1241 
1242         mMainFrame = (ViewGroup) findViewById(R.id.mainFrame);
1243         mInCallPanel = (ViewGroup) findViewById(R.id.inCallPanel);
1244 
1245         // Initialize the CallCard.
1246         mCallCard = (CallCard) findViewById(R.id.callCard);
1247         if (VDBG) log("  - mCallCard = " + mCallCard);
1248         mCallCard.setInCallScreenInstance(this);
1249 
1250         // Onscreen touch UI elements (used on some platforms)
1251         initInCallTouchUi();
1252 
1253         // Helper class to keep track of enabledness/state of UI controls
1254         mInCallControlState = new InCallControlState(this, mPhone);
1255 
1256         // Helper class to run the "Manage conference" UI
1257         mManageConferenceUtils = new ManageConferenceUtils(this, mPhone);
1258     }
1259 
1260     /**
1261      * Returns true if the phone is "in use", meaning that at least one line
1262      * is active (ie. off hook or ringing or dialing).  Conversely, a return
1263      * value of false means there's currently no phone activity at all.
1264      */
phoneIsInUse()1265     private boolean phoneIsInUse() {
1266         return mPhone.getState() != Phone.State.IDLE;
1267     }
1268 
handleDialerKeyDown(int keyCode, KeyEvent event)1269     private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
1270         if (VDBG) log("handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
1271 
1272         // As soon as the user starts typing valid dialable keys on the
1273         // keyboard (presumably to type DTMF tones) we start passing the
1274         // key events to the DTMFDialer's onDialerKeyDown.  We do so
1275         // only if the okToDialDTMFTones() conditions pass.
1276         if (okToDialDTMFTones()) {
1277             return mDialer.onDialerKeyDown(event);
1278 
1279             // TODO: If the dialpad isn't currently visible, maybe
1280             // consider automatically bringing it up right now?
1281             // (Just to make sure the user sees the digits widget...)
1282             // But this probably isn't too critical since it's awkward to
1283             // use the hard keyboard while in-call in the first place,
1284             // especially now that the in-call UI is portrait-only...
1285         }
1286 
1287         return false;
1288     }
1289 
1290     @Override
onBackPressed()1291     public void onBackPressed() {
1292         if (DBG) log("onBackPressed()...");
1293 
1294         // To consume this BACK press, the code here should just do
1295         // something and return.  Otherwise, call super.onBackPressed() to
1296         // get the default implementation (which simply finishes the
1297         // current activity.)
1298 
1299         if (!mRingingCall.isIdle()) {
1300             // While an incoming call is ringing, BACK behaves just like
1301             // ENDCALL: it stops the ringing and rejects the current call.
1302             // (This is only enabled on some platforms, though.)
1303             if (getResources().getBoolean(R.bool.allow_back_key_to_reject_incoming_call)) {
1304                 if (DBG) log("BACK key while ringing: reject the call");
1305                 internalHangupRingingCall();
1306 
1307                 // Don't consume the key; instead let the BACK event *also*
1308                 // get handled normally by the framework (which presumably
1309                 // will cause us to exit out of this activity.)
1310                 super.onBackPressed();
1311                 return;
1312             } else {
1313                 // The BACK key is disabled; don't reject the call, but
1314                 // *do* consume the keypress (otherwise we'll exit out of
1315                 // this activity.)
1316                 if (DBG) log("BACK key while ringing: ignored");
1317                 return;
1318             }
1319         }
1320 
1321         // BACK is also used to exit out of any "special modes" of the
1322         // in-call UI:
1323 
1324         if (mDialer.isOpened()) {
1325             // Take down the "touch lock" overlay *immediately* to let the
1326             // user clearly see the DTMF dialpad's closing animation.
1327             enableTouchLock(false);
1328 
1329             mDialer.closeDialer(true);  // do the "closing" animation
1330             return;
1331         }
1332 
1333         if (mInCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
1334             // Hide the Manage Conference panel, return to NORMAL mode.
1335             setInCallScreenMode(InCallScreenMode.NORMAL);
1336             return;
1337         }
1338 
1339         // Nothing special to do.  Fall back to the default behavior.
1340         super.onBackPressed();
1341     }
1342 
1343     /**
1344      * Handles the green CALL key while in-call.
1345      * @return true if we consumed the event.
1346      */
handleCallKey()1347     private boolean handleCallKey() {
1348         // The green CALL button means either "Answer", "Unhold", or
1349         // "Swap calls", or can be a no-op, depending on the current state
1350         // of the Phone.
1351 
1352         final boolean hasRingingCall = !mRingingCall.isIdle();
1353         final boolean hasActiveCall = !mForegroundCall.isIdle();
1354         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
1355 
1356         int phoneType = mPhone.getPhoneType();
1357         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1358             // The green CALL button means either "Answer", "Swap calls/On Hold", or
1359             // "Add to 3WC", depending on the current state of the Phone.
1360 
1361             PhoneApp app = PhoneApp.getInstance();
1362             CdmaPhoneCallState.PhoneCallState currCallState =
1363                 app.cdmaPhoneCallState.getCurrentCallState();
1364             if (hasRingingCall) {
1365                 //Scenario 1: Accepting the First Incoming and Call Waiting call
1366                 if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
1367                 internalAnswerCall();  // Automatically holds the current active call,
1368                                        // if there is one
1369             } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1370                     && (hasActiveCall)) {
1371                 //Scenario 2: Merging 3Way calls
1372                 if (DBG) log("answerCall: Merge 3-way call scenario");
1373                 // Merge calls
1374                 PhoneUtils.mergeCalls(mPhone);
1375             } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1376                 //Scenario 3: Switching between two Call waiting calls or drop the latest
1377                 // connection if in a 3Way merge scenario
1378                 if (DBG) log("answerCall: Switch btwn 2 calls scenario");
1379                 internalSwapCalls();
1380             }
1381         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1382             if (hasRingingCall) {
1383                 // If an incoming call is ringing, the CALL button is actually
1384                 // handled by the PhoneWindowManager.  (We do this to make
1385                 // sure that we'll respond to the key even if the InCallScreen
1386                 // hasn't come to the foreground yet.)
1387                 //
1388                 // We'd only ever get here in the extremely rare case that the
1389                 // incoming call started ringing *after*
1390                 // PhoneWindowManager.interceptKeyTq() but before the event
1391                 // got here, or else if the PhoneWindowManager had some
1392                 // problem connecting to the ITelephony service.
1393                 Log.w(LOG_TAG, "handleCallKey: incoming call is ringing!"
1394                       + " (PhoneWindowManager should have handled this key.)");
1395                 // But go ahead and handle the key as normal, since the
1396                 // PhoneWindowManager presumably did NOT handle it:
1397 
1398                 // There's an incoming ringing call: CALL means "Answer".
1399                 internalAnswerCall();
1400             } else if (hasActiveCall && hasHoldingCall) {
1401                 // Two lines are in use: CALL means "Swap calls".
1402                 if (DBG) log("handleCallKey: both lines in use ==> swap calls.");
1403                 internalSwapCalls();
1404             } else if (hasHoldingCall) {
1405                 // There's only one line in use, AND it's on hold.
1406                 // In this case CALL is a shortcut for "unhold".
1407                 if (DBG) log("handleCallKey: call on hold ==> unhold.");
1408                 PhoneUtils.switchHoldingAndActive(mPhone);  // Really means "unhold" in this state
1409             } else {
1410                 // The most common case: there's only one line in use, and
1411                 // it's an active call (i.e. it's not on hold.)
1412                 // In this case CALL is a no-op.
1413                 // (This used to be a shortcut for "add call", but that was a
1414                 // bad idea because "Add call" is so infrequently-used, and
1415                 // because the user experience is pretty confusing if you
1416                 // inadvertently trigger it.)
1417                 if (VDBG) log("handleCallKey: call in foregound ==> ignoring.");
1418                 // But note we still consume this key event; see below.
1419             }
1420         } else {
1421             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1422         }
1423 
1424         // We *always* consume the CALL key, since the system-wide default
1425         // action ("go to the in-call screen") is useless here.
1426         return true;
1427     }
1428 
isKeyEventAcceptableDTMF(KeyEvent event)1429     boolean isKeyEventAcceptableDTMF (KeyEvent event) {
1430         return (mDialer != null && mDialer.isKeyEventAcceptable(event));
1431     }
1432 
1433     /**
1434      * Overriden to track relevant focus changes.
1435      *
1436      * If a key is down and some time later the focus changes, we may
1437      * NOT recieve the keyup event; logically the keyup event has not
1438      * occured in this window.  This issue is fixed by treating a focus
1439      * changed event as an interruption to the keydown, making sure
1440      * that any code that needs to be run in onKeyUp is ALSO run here.
1441      *
1442      * Note, this focus change event happens AFTER the in-call menu is
1443      * displayed, so mIsMenuDisplayed should always be correct by the
1444      * time this method is called in the framework, please see:
1445      * {@link onCreatePanelView}, {@link onOptionsMenuClosed}
1446      */
1447     @Override
onWindowFocusChanged(boolean hasFocus)1448     public void onWindowFocusChanged(boolean hasFocus) {
1449         // the dtmf tones should no longer be played
1450         if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
1451         if (!hasFocus && mDialer != null) {
1452             if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
1453             mDialer.onDialerKeyUp(null);
1454         }
1455     }
1456 
1457     @Override
dispatchKeyEvent(KeyEvent event)1458     public boolean dispatchKeyEvent(KeyEvent event) {
1459         // if (DBG) log("dispatchKeyEvent(event " + event + ")...");
1460 
1461         // Intercept some events before they get dispatched to our views.
1462         switch (event.getKeyCode()) {
1463             case KeyEvent.KEYCODE_DPAD_CENTER:
1464             case KeyEvent.KEYCODE_DPAD_UP:
1465             case KeyEvent.KEYCODE_DPAD_DOWN:
1466             case KeyEvent.KEYCODE_DPAD_LEFT:
1467             case KeyEvent.KEYCODE_DPAD_RIGHT:
1468                 // Disable DPAD keys and trackball clicks if the touch lock
1469                 // overlay is up, since "touch lock" really means "disable
1470                 // the DTMF dialpad" (rather than only disabling touch events.)
1471                 if (mDialer.isOpened() && isTouchLocked()) {
1472                     if (DBG) log("- ignoring DPAD event while touch-locked...");
1473                     return true;
1474                 }
1475                 break;
1476 
1477             default:
1478                 break;
1479         }
1480 
1481         return super.dispatchKeyEvent(event);
1482     }
1483 
1484     @Override
onKeyUp(int keyCode, KeyEvent event)1485     public boolean onKeyUp(int keyCode, KeyEvent event) {
1486         // if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
1487 
1488         // push input to the dialer.
1489         if ((mDialer != null) && (mDialer.onDialerKeyUp(event))){
1490             return true;
1491         } else if (keyCode == KeyEvent.KEYCODE_CALL) {
1492             // Always consume CALL to be sure the PhoneWindow won't do anything with it
1493             return true;
1494         }
1495         return super.onKeyUp(keyCode, event);
1496     }
1497 
1498     @Override
onKeyDown(int keyCode, KeyEvent event)1499     public boolean onKeyDown(int keyCode, KeyEvent event) {
1500         // if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
1501 
1502         switch (keyCode) {
1503             case KeyEvent.KEYCODE_CALL:
1504                 boolean handled = handleCallKey();
1505                 if (!handled) {
1506                     Log.w(LOG_TAG, "InCallScreen should always handle KEYCODE_CALL in onKeyDown");
1507                 }
1508                 // Always consume CALL to be sure the PhoneWindow won't do anything with it
1509                 return true;
1510 
1511             // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
1512             // The standard system-wide handling of the ENDCALL key
1513             // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
1514             // already implements exactly what the UI spec wants,
1515             // namely (1) "hang up" if there's a current active call,
1516             // or (2) "don't answer" if there's a current ringing call.
1517 
1518             case KeyEvent.KEYCODE_CAMERA:
1519                 // Disable the CAMERA button while in-call since it's too
1520                 // easy to press accidentally.
1521                 return true;
1522 
1523             case KeyEvent.KEYCODE_VOLUME_UP:
1524             case KeyEvent.KEYCODE_VOLUME_DOWN:
1525                 if (mPhone.getState() == Phone.State.RINGING) {
1526                     // If an incoming call is ringing, the VOLUME buttons are
1527                     // actually handled by the PhoneWindowManager.  (We do
1528                     // this to make sure that we'll respond to them even if
1529                     // the InCallScreen hasn't come to the foreground yet.)
1530                     //
1531                     // We'd only ever get here in the extremely rare case that the
1532                     // incoming call started ringing *after*
1533                     // PhoneWindowManager.interceptKeyTq() but before the event
1534                     // got here, or else if the PhoneWindowManager had some
1535                     // problem connecting to the ITelephony service.
1536                     Log.w(LOG_TAG, "VOLUME key: incoming call is ringing!"
1537                           + " (PhoneWindowManager should have handled this key.)");
1538                     // But go ahead and handle the key as normal, since the
1539                     // PhoneWindowManager presumably did NOT handle it:
1540 
1541                     final CallNotifier notifier = PhoneApp.getInstance().notifier;
1542                     if (notifier.isRinging()) {
1543                         // ringer is actually playing, so silence it.
1544                         PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
1545                         if (DBG) log("VOLUME key: silence ringer");
1546                         notifier.silenceRinger();
1547                     }
1548 
1549                     // As long as an incoming call is ringing, we always
1550                     // consume the VOLUME keys.
1551                     return true;
1552                 }
1553                 break;
1554 
1555             case KeyEvent.KEYCODE_MENU:
1556                 // Special case for the MENU key: if the "touch lock"
1557                 // overlay is up (over the DTMF dialpad), allow MENU to
1558                 // dismiss the overlay just as if you had double-tapped
1559                 // the onscreen icon.
1560                 // (We do this because MENU is normally used to bring the
1561                 // UI back after the screen turns off, and the touch lock
1562                 // overlay "feels" very similar to the screen going off.
1563                 // This is also here to be "backward-compatibile" with the
1564                 // 1.0 behavior, where you *needed* to hit MENU to bring
1565                 // back the dialpad after 6 seconds of idle time.)
1566                 if (mDialer.isOpened() && isTouchLocked()) {
1567                     if (VDBG) log("- allowing MENU to dismiss touch lock overlay...");
1568                     // Take down the touch lock overlay, but post a
1569                     // message in the future to bring it back later.
1570                     enableTouchLock(false);
1571                     resetTouchLockTimer();
1572                     return true;
1573                 }
1574                 break;
1575 
1576             case KeyEvent.KEYCODE_MUTE:
1577                 PhoneUtils.setMute(mPhone, !PhoneUtils.getMute(mPhone));
1578                 return true;
1579 
1580             // Various testing/debugging features, enabled ONLY when VDBG == true.
1581             case KeyEvent.KEYCODE_SLASH:
1582                 if (VDBG) {
1583                     log("----------- InCallScreen View dump --------------");
1584                     // Dump starting from the top-level view of the entire activity:
1585                     Window w = this.getWindow();
1586                     View decorView = w.getDecorView();
1587                     decorView.debug();
1588                     return true;
1589                 }
1590                 break;
1591             case KeyEvent.KEYCODE_EQUALS:
1592                 if (VDBG) {
1593                     log("----------- InCallScreen call state dump --------------");
1594                     PhoneUtils.dumpCallState(mPhone);
1595                     return true;
1596                 }
1597                 break;
1598             case KeyEvent.KEYCODE_GRAVE:
1599                 if (VDBG) {
1600                     // Placeholder for other misc temp testing
1601                     log("------------ Temp testing -----------------");
1602                     return true;
1603                 }
1604                 break;
1605         }
1606 
1607         if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
1608             return true;
1609         }
1610 
1611         return super.onKeyDown(keyCode, event);
1612     }
1613 
1614     /**
1615      * Handle a failure notification for a supplementary service
1616      * (i.e. conference, switch, separate, transfer, etc.).
1617      */
onSuppServiceFailed(AsyncResult r)1618     void onSuppServiceFailed(AsyncResult r) {
1619         Phone.SuppService service = (Phone.SuppService) r.result;
1620         if (DBG) log("onSuppServiceFailed: " + service);
1621 
1622         int errorMessageResId;
1623         switch (service) {
1624             case SWITCH:
1625                 // Attempt to switch foreground and background/incoming calls failed
1626                 // ("Failed to switch calls")
1627                 errorMessageResId = R.string.incall_error_supp_service_switch;
1628                 break;
1629 
1630             case SEPARATE:
1631                 // Attempt to separate a call from a conference call
1632                 // failed ("Failed to separate out call")
1633                 errorMessageResId = R.string.incall_error_supp_service_separate;
1634                 break;
1635 
1636             case TRANSFER:
1637                 // Attempt to connect foreground and background calls to
1638                 // each other (and hanging up user's line) failed ("Call
1639                 // transfer failed")
1640                 errorMessageResId = R.string.incall_error_supp_service_transfer;
1641                 break;
1642 
1643             case CONFERENCE:
1644                 // Attempt to add a call to conference call failed
1645                 // ("Conference call failed")
1646                 errorMessageResId = R.string.incall_error_supp_service_conference;
1647                 break;
1648 
1649             case REJECT:
1650                 // Attempt to reject an incoming call failed
1651                 // ("Call rejection failed")
1652                 errorMessageResId = R.string.incall_error_supp_service_reject;
1653                 break;
1654 
1655             case HANGUP:
1656                 // Attempt to release a call failed ("Failed to release call(s)")
1657                 errorMessageResId = R.string.incall_error_supp_service_hangup;
1658                 break;
1659 
1660             case UNKNOWN:
1661             default:
1662                 // Attempt to use a service we don't recognize or support
1663                 // ("Unsupported service" or "Selected service failed")
1664                 errorMessageResId = R.string.incall_error_supp_service_unknown;
1665                 break;
1666         }
1667 
1668         // mSuppServiceFailureDialog is a generic dialog used for any
1669         // supp service failure, and there's only ever have one
1670         // instance at a time.  So just in case a previous dialog is
1671         // still around, dismiss it.
1672         if (mSuppServiceFailureDialog != null) {
1673             if (DBG) log("- DISMISSING mSuppServiceFailureDialog.");
1674             mSuppServiceFailureDialog.dismiss();  // It's safe to dismiss() a dialog
1675                                                   // that's already dismissed.
1676             mSuppServiceFailureDialog = null;
1677         }
1678 
1679         mSuppServiceFailureDialog = new AlertDialog.Builder(this)
1680                 .setMessage(errorMessageResId)
1681                 .setPositiveButton(R.string.ok, null)
1682                 .setCancelable(true)
1683                 .create();
1684         mSuppServiceFailureDialog.getWindow().addFlags(
1685                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1686         mSuppServiceFailureDialog.show();
1687     }
1688 
1689     /**
1690      * Something has changed in the phone's state.  Update the UI.
1691      */
onPhoneStateChanged(AsyncResult r)1692     private void onPhoneStateChanged(AsyncResult r) {
1693         if (DBG) log("onPhoneStateChanged()...");
1694 
1695         // There's nothing to do here if we're not the foreground activity.
1696         // (When we *do* eventually come to the foreground, we'll do a
1697         // full update then.)
1698         if (!mIsForegroundActivity) {
1699             if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
1700             return;
1701         }
1702 
1703         updateScreen();
1704 
1705         // Make sure we update the poke lock and wake lock when certain
1706         // phone state changes occur.
1707         PhoneApp.getInstance().updateWakeState();
1708     }
1709 
1710     /**
1711      * Updates the UI after a phone connection is disconnected, as follows:
1712      *
1713      * - If this was a missed or rejected incoming call, and no other
1714      *   calls are active, dismiss the in-call UI immediately.  (The
1715      *   CallNotifier will still create a "missed call" notification if
1716      *   necessary.)
1717      *
1718      * - With any other disconnect cause, if the phone is now totally
1719      *   idle, display the "Call ended" state for a couple of seconds.
1720      *
1721      * - Or, if the phone is still in use, stay on the in-call screen
1722      *   (and update the UI to reflect the current state of the Phone.)
1723      *
1724      * @param r r.result contains the connection that just ended
1725      */
onDisconnect(AsyncResult r)1726     private void onDisconnect(AsyncResult r) {
1727         Connection c = (Connection) r.result;
1728         Connection.DisconnectCause cause = c.getDisconnectCause();
1729         if (DBG) log("onDisconnect: " + c + ", cause=" + cause);
1730 
1731         boolean currentlyIdle = !phoneIsInUse();
1732         int autoretrySetting = AUTO_RETRY_OFF;
1733         boolean phoneIsCdma = (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA);
1734         if (phoneIsCdma) {
1735             // Get the Auto-retry setting only if Phone State is IDLE,
1736             // else let it stay as AUTO_RETRY_OFF
1737             if (currentlyIdle) {
1738                 autoretrySetting = android.provider.Settings.System.getInt(mPhone.getContext().
1739                         getContentResolver(), android.provider.Settings.System.CALL_AUTO_RETRY, 0);
1740             }
1741         }
1742 
1743         // for OTA Call, only if in OTA NORMAL mode, handle OTA END scenario
1744         final PhoneApp app = PhoneApp.getInstance();
1745         if ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL)
1746                 && ((app.cdmaOtaProvisionData != null)
1747                 && (!app.cdmaOtaProvisionData.inOtaSpcState))) {
1748             setInCallScreenMode(InCallScreenMode.OTA_ENDED);
1749             updateScreen();
1750             return;
1751         } else if ((mInCallScreenMode == InCallScreenMode.OTA_ENDED)
1752                 || ((app.cdmaOtaProvisionData != null) && app.cdmaOtaProvisionData.inOtaSpcState)) {
1753            if (DBG) log("onDisconnect: OTA Call end already handled");
1754            return;
1755         }
1756 
1757         // Any time a call disconnects, clear out the "history" of DTMF
1758         // digits you typed (to make sure it doesn't persist from one call
1759         // to the next.)
1760         mDialer.clearDigits();
1761 
1762         // Under certain call disconnected states, we want to alert the user
1763         // with a dialog instead of going through the normal disconnect
1764         // routine.
1765         if (cause == Connection.DisconnectCause.CALL_BARRED) {
1766             showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
1767             return;
1768         } else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
1769             showGenericErrorDialog(R.string.callFailed_fdn_only, false);
1770             return;
1771         } else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
1772             showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
1773             return;
1774         } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
1775             showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
1776             return;
1777         } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
1778             showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
1779             return;
1780         }
1781 
1782         if (phoneIsCdma) {
1783             Call.State callState = PhoneApp.getInstance().notifier.getPreviousCdmaCallState();
1784             if ((callState == Call.State.ACTIVE)
1785                     && (cause != Connection.DisconnectCause.INCOMING_MISSED)
1786                     && (cause != Connection.DisconnectCause.NORMAL)
1787                     && (cause != Connection.DisconnectCause.LOCAL)
1788                     && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1789                 showCallLostDialog();
1790             } else if ((callState == Call.State.DIALING || callState == Call.State.ALERTING)
1791                         && (cause != Connection.DisconnectCause.INCOMING_MISSED)
1792                         && (cause != Connection.DisconnectCause.NORMAL)
1793                         && (cause != Connection.DisconnectCause.LOCAL)
1794                         && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1795 
1796                     if (mNeedShowCallLostDialog) {
1797                         // Show the dialog now since the call that just failed was a retry.
1798                         showCallLostDialog();
1799                         mNeedShowCallLostDialog = false;
1800                     } else {
1801                         if (autoretrySetting == AUTO_RETRY_OFF) {
1802                             // Show the dialog for failed call if Auto Retry is OFF in Settings.
1803                             showCallLostDialog();
1804                             mNeedShowCallLostDialog = false;
1805                         } else {
1806                             // Set the mNeedShowCallLostDialog flag now, so we'll know to show
1807                             // the dialog if *this* call fails.
1808                             mNeedShowCallLostDialog = true;
1809                         }
1810                     }
1811             }
1812         }
1813 
1814         // Explicitly clean up up any DISCONNECTED connections
1815         // in a conference call.
1816         // [Background: Even after a connection gets disconnected, its
1817         // Connection object still stays around for a few seconds, in the
1818         // DISCONNECTED state.  With regular calls, this state drives the
1819         // "call ended" UI.  But when a single person disconnects from a
1820         // conference call there's no "call ended" state at all; in that
1821         // case we blow away any DISCONNECTED connections right now to make sure
1822         // the UI updates instantly to reflect the current state.]
1823         Call call = c.getCall();
1824         if (call != null) {
1825             // We only care about situation of a single caller
1826             // disconnecting from a conference call.  In that case, the
1827             // call will have more than one Connection (including the one
1828             // that just disconnected, which will be in the DISCONNECTED
1829             // state) *and* at least one ACTIVE connection.  (If the Call
1830             // has *no* ACTIVE connections, that means that the entire
1831             // conference call just ended, so we *do* want to show the
1832             // "Call ended" state.)
1833             List<Connection> connections = call.getConnections();
1834             if (connections != null && connections.size() > 1) {
1835                 for (Connection conn : connections) {
1836                     if (conn.getState() == Call.State.ACTIVE) {
1837                         // This call still has at least one ACTIVE connection!
1838                         // So blow away any DISCONNECTED connections
1839                         // (including, presumably, the one that just
1840                         // disconnected from this conference call.)
1841 
1842                         // We also force the wake state to refresh, just in
1843                         // case the disconnected connections are removed
1844                         // before the phone state change.
1845                         if (VDBG) log("- Still-active conf call; clearing DISCONNECTED...");
1846                         app.updateWakeState();
1847                         mPhone.clearDisconnected();  // This happens synchronously.
1848                         break;
1849                     }
1850                 }
1851             }
1852         }
1853 
1854         // Retrieve the emergency call retry count from this intent, in
1855         // case we need to retry the call again.
1856         int emergencyCallRetryCount = getIntent().getIntExtra(
1857                 EmergencyCallHandler.EMERGENCY_CALL_RETRY_KEY,
1858                 EmergencyCallHandler.INITIAL_ATTEMPT);
1859 
1860         // Note: see CallNotifier.onDisconnect() for some other behavior
1861         // that might be triggered by a disconnect event, like playing the
1862         // busy/congestion tone.
1863 
1864         // Keep track of whether this call was user-initiated or not.
1865         // (This affects where we take the user next; see delayedCleanupAfterDisconnect().)
1866         mShowCallLogAfterDisconnect = !c.isIncoming();
1867 
1868         // We bail out immediately (and *don't* display the "call ended"
1869         // state at all) in a couple of cases, including those where we
1870         // are waiting for the radio to finish powering up for an
1871         // emergency call:
1872         boolean bailOutImmediately =
1873                 ((cause == Connection.DisconnectCause.INCOMING_MISSED)
1874                  || (cause == Connection.DisconnectCause.INCOMING_REJECTED)
1875                  || ((cause == Connection.DisconnectCause.OUT_OF_SERVICE)
1876                          && (emergencyCallRetryCount > 0)))
1877                 && currentlyIdle;
1878 
1879         if (bailOutImmediately) {
1880             if (VDBG) log("- onDisconnect: bailOutImmediately...");
1881             // Exit the in-call UI!
1882             // (This is basically the same "delayed cleanup" we do below,
1883             // just with zero delay.  Since the Phone is currently idle,
1884             // this call is guaranteed to immediately finish this activity.)
1885             delayedCleanupAfterDisconnect();
1886 
1887             // Retry the call, by resending the intent to the emergency
1888             // call handler activity.
1889             if ((cause == Connection.DisconnectCause.OUT_OF_SERVICE)
1890                     && (emergencyCallRetryCount > 0)) {
1891                 startActivity(getIntent()
1892                         .setClassName(this, EmergencyCallHandler.class.getName()));
1893             }
1894         } else {
1895             if (VDBG) log("- onDisconnect: delayed bailout...");
1896             // Stay on the in-call screen for now.  (Either the phone is
1897             // still in use, or the phone is idle but we want to display
1898             // the "call ended" state for a couple of seconds.)
1899 
1900             // Force a UI update in case we need to display anything
1901             // special given this connection's DisconnectCause (see
1902             // CallCard.getCallFailedString()).
1903             updateScreen();
1904 
1905             // Display the special "Call ended" state when the phone is idle
1906             // but there's still a call in the DISCONNECTED state:
1907             if (currentlyIdle
1908                 && ((mForegroundCall.getState() == Call.State.DISCONNECTED)
1909                     || (mBackgroundCall.getState() == Call.State.DISCONNECTED))) {
1910                 if (VDBG) log("- onDisconnect: switching to 'Call ended' state...");
1911                 setInCallScreenMode(InCallScreenMode.CALL_ENDED);
1912             }
1913 
1914             // Some other misc cleanup that we do if the call that just
1915             // disconnected was the foreground call.
1916             final boolean hasActiveCall = !mForegroundCall.isIdle();
1917             if (!hasActiveCall) {
1918                 if (VDBG) log("- onDisconnect: cleaning up after FG call disconnect...");
1919 
1920                 // Dismiss any dialogs which are only meaningful for an
1921                 // active call *and* which become moot if the call ends.
1922                 if (mWaitPromptDialog != null) {
1923                     if (VDBG) log("- DISMISSING mWaitPromptDialog.");
1924                     mWaitPromptDialog.dismiss();  // safe even if already dismissed
1925                     mWaitPromptDialog = null;
1926                 }
1927                 if (mWildPromptDialog != null) {
1928                     if (VDBG) log("- DISMISSING mWildPromptDialog.");
1929                     mWildPromptDialog.dismiss();  // safe even if already dismissed
1930                     mWildPromptDialog = null;
1931                 }
1932                 if (mPausePromptDialog != null) {
1933                     if (DBG) log("- DISMISSING mPausePromptDialog.");
1934                     mPausePromptDialog.dismiss();  // safe even if already dismissed
1935                     mPausePromptDialog = null;
1936                 }
1937             }
1938 
1939             // Updating the screen wake state is done in onPhoneStateChanged().
1940 
1941 
1942             // CDMA: We only clean up if the Phone state is IDLE as we might receive an
1943             // onDisconnect for a Call Collision case (rare but possible).
1944             // For Call collision cases i.e. when the user makes an out going call
1945             // and at the same time receives an Incoming Call, the Incoming Call is given
1946             // higher preference. At this time framework sends a disconnect for the Out going
1947             // call connection hence we should *not* bring down the InCallScreen as the Phone
1948             // State would be RINGING
1949             if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
1950                 if (!currentlyIdle) {
1951                     // Clean up any connections in the DISCONNECTED state.
1952                     // This is necessary cause in CallCollision the foreground call might have
1953                     // connections in DISCONNECTED state which needs to be cleared.
1954                     mPhone.clearDisconnected();
1955 
1956                     // The phone is still in use.  Stay here in this activity.
1957                     // But we don't need to keep the screen on.
1958                     if (DBG) log("onDisconnect: Call Collision case - staying on InCallScreen.");
1959                     if (DBG) PhoneUtils.dumpCallState(mPhone);
1960                     return;
1961                 }
1962             }
1963 
1964             // Finally, arrange for delayedCleanupAfterDisconnect() to get
1965             // called after a short interval (during which we display the
1966             // "call ended" state.)  At that point, if the
1967             // Phone is idle, we'll finish out of this activity.
1968             int callEndedDisplayDelay =
1969                     (cause == Connection.DisconnectCause.LOCAL)
1970                     ? CALL_ENDED_SHORT_DELAY : CALL_ENDED_LONG_DELAY;
1971             mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
1972             mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
1973                                              callEndedDisplayDelay);
1974         }
1975 
1976         // Remove 3way timer (only meaningful for CDMA)
1977         mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
1978     }
1979 
1980     /**
1981      * Brings up the "MMI Started" dialog.
1982      */
onMMIInitiate(AsyncResult r)1983     private void onMMIInitiate(AsyncResult r) {
1984         if (VDBG) log("onMMIInitiate()...  AsyncResult r = " + r);
1985 
1986         // Watch out: don't do this if we're not the foreground activity,
1987         // mainly since in the Dialog.show() might fail if we don't have a
1988         // valid window token any more...
1989         // (Note that this exact sequence can happen if you try to start
1990         // an MMI code while the radio is off or out of service.)
1991         if (!mIsForegroundActivity) {
1992             if (VDBG) log("Activity not in foreground! Bailing out...");
1993             return;
1994         }
1995 
1996         // Also, if any other dialog is up right now (presumably the
1997         // generic error dialog displaying the "Starting MMI..."  message)
1998         // take it down before bringing up the real "MMI Started" dialog
1999         // in its place.
2000         dismissAllDialogs();
2001 
2002         MmiCode mmiCode = (MmiCode) r.result;
2003         if (VDBG) log("  - MmiCode: " + mmiCode);
2004 
2005         Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
2006         mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
2007                                                           message, mMmiStartedDialog);
2008     }
2009 
2010     /**
2011      * Handles an MMI_CANCEL event, which is triggered by the button
2012      * (labeled either "OK" or "Cancel") on the "MMI Started" dialog.
2013      * @see onMMIInitiate
2014      * @see PhoneUtils.cancelMmiCode
2015      */
onMMICancel()2016     private void onMMICancel() {
2017         if (VDBG) log("onMMICancel()...");
2018 
2019         // First of all, cancel the outstanding MMI code (if possible.)
2020         PhoneUtils.cancelMmiCode(mPhone);
2021 
2022         // Regardless of whether the current MMI code was cancelable, the
2023         // PhoneApp will get an MMI_COMPLETE event very soon, which will
2024         // take us to the MMI Complete dialog (see
2025         // PhoneUtils.displayMMIComplete().)
2026         //
2027         // But until that event comes in, we *don't* want to stay here on
2028         // the in-call screen, since we'll be visible in a
2029         // partially-constructed state as soon as the "MMI Started" dialog
2030         // gets dismissed.  So let's forcibly bail out right now.
2031         if (DBG) log("onMMICancel: finishing InCallScreen...");
2032         endInCallScreenSession();
2033     }
2034 
2035     /**
2036      * Handles the POST_ON_DIAL_CHARS message from the Phone
2037      * (see our call to mPhone.setOnPostDialCharacter() above.)
2038      *
2039      * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle
2040      * "dialable" key events here in the InCallScreen: we do directly to the
2041      * Dialer UI instead.  Similarly, we may now need to go directly to the
2042      * Dialer to handle POST_ON_DIAL_CHARS too.
2043      */
handlePostOnDialChars(AsyncResult r, char ch)2044     private void handlePostOnDialChars(AsyncResult r, char ch) {
2045         Connection c = (Connection) r.result;
2046 
2047         if (c != null) {
2048             Connection.PostDialState state =
2049                     (Connection.PostDialState) r.userObj;
2050 
2051             if (VDBG) log("handlePostOnDialChar: state = " +
2052                     state + ", ch = " + ch);
2053 
2054             int phoneType = mPhone.getPhoneType();
2055             switch (state) {
2056                 case STARTED:
2057                     if (phoneType == Phone.PHONE_TYPE_CDMA) {
2058                         mDialer.stopLocalToneCdma();
2059                         if (mPauseInProgress) {
2060                             showPausePromptDialogCDMA(c, mPostDialStrAfterPause);
2061                         }
2062                         mPauseInProgress = false;
2063                         mDialer.startLocalToneCdma(ch);
2064                     }
2065                     // TODO: is this needed, now that you can't actually
2066                     // type DTMF chars or dial directly from here?
2067                     // If so, we'd need to yank you out of the in-call screen
2068                     // here too (and take you to the 12-key dialer in "in-call" mode.)
2069                     // displayPostDialedChar(ch);
2070                     break;
2071 
2072                 case WAIT:
2073                     //if (DBG) log("show wait prompt...");
2074                     String postDialStr = c.getRemainingPostDialString();
2075                     if (phoneType == Phone.PHONE_TYPE_CDMA) {
2076                         mDialer.stopLocalToneCdma();
2077                         showWaitPromptDialogCDMA(c, postDialStr);
2078                     } else if (phoneType == Phone.PHONE_TYPE_GSM) {
2079                         showWaitPromptDialog(c, postDialStr);
2080                     } else {
2081                         throw new IllegalStateException("Unexpected phone type: " + phoneType);
2082                     }
2083                     break;
2084 
2085                 case WILD:
2086                     //if (DBG) log("prompt user to replace WILD char");
2087                     showWildPromptDialog(c);
2088                     break;
2089 
2090                 case COMPLETE:
2091                     if (phoneType == Phone.PHONE_TYPE_CDMA) {
2092                         mDialer.stopLocalToneCdma();
2093                     }
2094                     break;
2095 
2096                 case PAUSE:
2097                     if (phoneType == Phone.PHONE_TYPE_CDMA) {
2098                         mPostDialStrAfterPause = c.getRemainingPostDialString();
2099                         mDialer.stopLocalToneCdma();
2100                         mPauseInProgress = true;
2101                     }
2102                     break;
2103 
2104                 default:
2105                     break;
2106             }
2107         }
2108     }
2109 
showWaitPromptDialog(final Connection c, String postDialStr)2110     private void showWaitPromptDialog(final Connection c, String postDialStr) {
2111         Resources r = getResources();
2112         StringBuilder buf = new StringBuilder();
2113         buf.append(r.getText(R.string.wait_prompt_str));
2114         buf.append(postDialStr);
2115 
2116         if (mWaitPromptDialog != null) {
2117             if (VDBG) log("- DISMISSING mWaitPromptDialog.");
2118             mWaitPromptDialog.dismiss();  // safe even if already dismissed
2119             mWaitPromptDialog = null;
2120         }
2121 
2122         mWaitPromptDialog = new AlertDialog.Builder(this)
2123                 .setMessage(buf.toString())
2124                 .setPositiveButton(R.string.send_button, new DialogInterface.OnClickListener() {
2125                         public void onClick(DialogInterface dialog, int whichButton) {
2126                             if (VDBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
2127                             c.proceedAfterWaitChar();
2128                             PhoneApp.getInstance().pokeUserActivity();
2129                         }
2130                     })
2131                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
2132                         public void onCancel(DialogInterface dialog) {
2133                             if (VDBG) log("handle POST_DIAL_CANCELED!");
2134                             c.cancelPostDial();
2135                             PhoneApp.getInstance().pokeUserActivity();
2136                         }
2137                     })
2138                 .create();
2139         mWaitPromptDialog.getWindow().addFlags(
2140                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2141         mWaitPromptDialog.show();
2142     }
2143 
2144     /**
2145      * Processes the CDMA specific requirements of a WAIT character in a
2146      * dial string.
2147      *
2148      * Pop up an alert dialog with OK and Cancel buttons to allow user to
2149      * Accept or Reject the WAIT inserted as part of the Dial string.
2150      */
showWaitPromptDialogCDMA(final Connection c, String postDialStr)2151     private void showWaitPromptDialogCDMA(final Connection c, String postDialStr) {
2152         Resources r = getResources();
2153         StringBuilder buf = new StringBuilder();
2154         buf.append(r.getText(R.string.wait_prompt_str));
2155         buf.append(postDialStr);
2156 
2157         if (mWaitPromptDialog != null) {
2158             if (DBG) log("- DISMISSING mWaitPromptDialog.");
2159             mWaitPromptDialog.dismiss();  // safe even if already dismissed
2160             mWaitPromptDialog = null;
2161         }
2162 
2163         mWaitPromptDialog = new AlertDialog.Builder(this)
2164                 .setMessage(buf.toString())
2165                 .setPositiveButton(R.string.pause_prompt_yes,
2166                     new DialogInterface.OnClickListener() {
2167                         public void onClick(DialogInterface dialog, int whichButton) {
2168                             if (DBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
2169                             c.proceedAfterWaitChar();
2170                         }
2171                     })
2172                 .setNegativeButton(R.string.pause_prompt_no, new DialogInterface.OnClickListener() {
2173                         public void onClick(DialogInterface dialog, int whichButton) {
2174                             if (DBG) log("handle POST_DIAL_CANCELED!");
2175                             c.cancelPostDial();
2176                         }
2177                     })
2178                 .create();
2179         mWaitPromptDialog.getWindow().addFlags(
2180                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2181         mWaitPromptDialog.show();
2182     }
2183 
2184     /**
2185      * Pop up an alert dialog which waits for 2 seconds for each P (Pause) Character entered
2186      * as part of the Dial String.
2187      */
showPausePromptDialogCDMA(final Connection c, String postDialStrAfterPause)2188     private void showPausePromptDialogCDMA(final Connection c, String postDialStrAfterPause) {
2189         Resources r = getResources();
2190         StringBuilder buf = new StringBuilder();
2191         buf.append(r.getText(R.string.pause_prompt_str));
2192         buf.append(postDialStrAfterPause);
2193 
2194         if (mPausePromptDialog != null) {
2195             if (DBG) log("- DISMISSING mPausePromptDialog.");
2196             mPausePromptDialog.dismiss();  // safe even if already dismissed
2197             mPausePromptDialog = null;
2198         }
2199 
2200         mPausePromptDialog = new AlertDialog.Builder(this)
2201                 .setMessage(buf.toString())
2202                 .create();
2203         mPausePromptDialog.show();
2204         // 2 second timer
2205         Message msg = Message.obtain(mHandler, EVENT_PAUSE_DIALOG_COMPLETE);
2206         mHandler.sendMessageDelayed(msg, PAUSE_PROMPT_DIALOG_TIMEOUT);
2207     }
2208 
createWildPromptView()2209     private View createWildPromptView() {
2210         LinearLayout result = new LinearLayout(this);
2211         result.setOrientation(LinearLayout.VERTICAL);
2212         result.setPadding(5, 5, 5, 5);
2213 
2214         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
2215                         ViewGroup.LayoutParams.FILL_PARENT,
2216                         ViewGroup.LayoutParams.WRAP_CONTENT);
2217 
2218         TextView promptMsg = new TextView(this);
2219         promptMsg.setTextSize(14);
2220         promptMsg.setTypeface(Typeface.DEFAULT_BOLD);
2221         promptMsg.setText(getResources().getText(R.string.wild_prompt_str));
2222 
2223         result.addView(promptMsg, lp);
2224 
2225         mWildPromptText = new EditText(this);
2226         mWildPromptText.setKeyListener(DialerKeyListener.getInstance());
2227         mWildPromptText.setMovementMethod(null);
2228         mWildPromptText.setTextSize(14);
2229         mWildPromptText.setMaxLines(1);
2230         mWildPromptText.setHorizontallyScrolling(true);
2231         mWildPromptText.setBackgroundResource(android.R.drawable.editbox_background);
2232 
2233         LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
2234                         ViewGroup.LayoutParams.FILL_PARENT,
2235                         ViewGroup.LayoutParams.WRAP_CONTENT);
2236         lp2.setMargins(0, 3, 0, 0);
2237 
2238         result.addView(mWildPromptText, lp2);
2239 
2240         return result;
2241     }
2242 
showWildPromptDialog(final Connection c)2243     private void showWildPromptDialog(final Connection c) {
2244         View v = createWildPromptView();
2245 
2246         if (mWildPromptDialog != null) {
2247             if (VDBG) log("- DISMISSING mWildPromptDialog.");
2248             mWildPromptDialog.dismiss();  // safe even if already dismissed
2249             mWildPromptDialog = null;
2250         }
2251 
2252         mWildPromptDialog = new AlertDialog.Builder(this)
2253                 .setView(v)
2254                 .setPositiveButton(
2255                         R.string.send_button,
2256                         new DialogInterface.OnClickListener() {
2257                             public void onClick(DialogInterface dialog, int whichButton) {
2258                                 if (VDBG) log("handle WILD_PROMPT_CHAR_ENTERED, proceed...");
2259                                 String replacement = null;
2260                                 if (mWildPromptText != null) {
2261                                     replacement = mWildPromptText.getText().toString();
2262                                     mWildPromptText = null;
2263                                 }
2264                                 c.proceedAfterWildChar(replacement);
2265                                 PhoneApp.getInstance().pokeUserActivity();
2266                             }
2267                         })
2268                 .setOnCancelListener(
2269                         new DialogInterface.OnCancelListener() {
2270                             public void onCancel(DialogInterface dialog) {
2271                                 if (VDBG) log("handle POST_DIAL_CANCELED!");
2272                                 c.cancelPostDial();
2273                                 PhoneApp.getInstance().pokeUserActivity();
2274                             }
2275                         })
2276                 .create();
2277         mWildPromptDialog.getWindow().addFlags(
2278                 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2279         mWildPromptDialog.show();
2280 
2281         mWildPromptText.requestFocus();
2282     }
2283 
2284     /**
2285      * Updates the state of the in-call UI based on the current state of
2286      * the Phone.
2287      */
updateScreen()2288     private void updateScreen() {
2289         if (DBG) log("updateScreen()...");
2290 
2291         // Don't update anything if we're not in the foreground (there's
2292         // no point updating our UI widgets since we're not visible!)
2293         // Also note this check also ensures we won't update while we're
2294         // in the middle of pausing, which could cause a visible glitch in
2295         // the "activity ending" transition.
2296         if (!mIsForegroundActivity) {
2297             if (DBG) log("- updateScreen: not the foreground Activity! Bailing out...");
2298             return;
2299         }
2300 
2301         // Update the state of the in-call menu items.
2302         if (mInCallMenu != null) {
2303             // TODO: do this only if the menu is visible!
2304             if (DBG) log("- updateScreen: updating menu items...");
2305             boolean okToShowMenu = mInCallMenu.updateItems(mPhone);
2306             if (!okToShowMenu) {
2307                 // Uh oh: we were only trying to update the state of the
2308                 // menu items, but the logic in InCallMenu.updateItems()
2309                 // just decided the menu shouldn't be visible at all!
2310                 // (That's probably means that the call ended
2311                 // asynchronously while the menu was up.)
2312                 //
2313                 // So take the menu down ASAP.
2314                 if (DBG) log("- updateScreen: Tried to update menu; now need to dismiss!");
2315                 // dismissMenu() has no effect if the menu is already closed.
2316                 dismissMenu(true);  // dismissImmediate = true
2317             }
2318         }
2319 
2320         final PhoneApp app = PhoneApp.getInstance();
2321 
2322         if (mInCallScreenMode == InCallScreenMode.OTA_NORMAL) {
2323             if (DBG) log("- updateScreen: OTA call state NORMAL...");
2324             if (otaUtils != null) {
2325                 if (DBG) log("- updateScreen: otaUtils is not null, call otaShowProperScreen");
2326                 otaUtils.otaShowProperScreen();
2327             }
2328             return;
2329         } else if (mInCallScreenMode == InCallScreenMode.OTA_ENDED) {
2330             if (DBG) log("- updateScreen: OTA call ended state ...");
2331             // Wake up the screen when we get notification, good or bad.
2332             PhoneApp.getInstance().wakeUpScreen();
2333             if (app.cdmaOtaScreenState.otaScreenState
2334                 == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION) {
2335                 if (DBG) log("- updateScreen: OTA_STATUS_ACTIVATION");
2336                 if (otaUtils != null) {
2337                     if (DBG) log("- updateScreen: otaUtils is not null, "
2338                                   + "call otaShowActivationScreen");
2339                     otaUtils.otaShowActivateScreen();
2340                 }
2341             } else {
2342                 if (DBG) log("- updateScreen: OTA Call end state for Dialogs");
2343                 if (otaUtils != null) {
2344                     if (DBG) log("- updateScreen: Show OTA Success Failure dialog");
2345                     otaUtils.otaShowSuccessFailure();
2346                 }
2347             }
2348             return;
2349         } else if (mInCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
2350             if (DBG) log("- updateScreen: manage conference mode (NOT updating in-call UI)...");
2351             updateManageConferencePanelIfNecessary();
2352             return;
2353         } else if (mInCallScreenMode == InCallScreenMode.CALL_ENDED) {
2354             if (DBG) log("- updateScreen: call ended state (NOT updating in-call UI)...");
2355             // Actually we do need to update one thing: the background.
2356             updateInCallBackground();
2357             return;
2358         }
2359 
2360         if (DBG) log("- updateScreen: updating the in-call UI...");
2361         mCallCard.updateState(mPhone);
2362         updateDialpadVisibility();
2363         updateInCallTouchUi();
2364         updateProviderOverlay();
2365         updateMenuButtonHint();
2366         updateInCallBackground();
2367 
2368         // Forcibly take down all dialog if an incoming call is ringing.
2369         if (!mRingingCall.isIdle()) {
2370             dismissAllDialogs();
2371         } else {
2372             // Wait prompt dialog is not currently up.  But it *should* be
2373             // up if the FG call has a connection in the WAIT state and
2374             // the phone isn't ringing.
2375             String postDialStr = null;
2376             List<Connection> fgConnections = mForegroundCall.getConnections();
2377             int phoneType = mPhone.getPhoneType();
2378             if (phoneType == Phone.PHONE_TYPE_CDMA) {
2379                 Connection fgLatestConnection = mForegroundCall.getLatestConnection();
2380                 if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState() ==
2381                         CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
2382                     for (Connection cn : fgConnections) {
2383                         if ((cn != null) && (cn.getPostDialState() ==
2384                                 Connection.PostDialState.WAIT)) {
2385                             cn.cancelPostDial();
2386                         }
2387                     }
2388                 } else if ((fgLatestConnection != null)
2389                      && (fgLatestConnection.getPostDialState() == Connection.PostDialState.WAIT)) {
2390                     if(DBG) log("show the Wait dialog for CDMA");
2391                     postDialStr = fgLatestConnection.getRemainingPostDialString();
2392                     showWaitPromptDialogCDMA(fgLatestConnection, postDialStr);
2393                 }
2394             } else if (phoneType == Phone.PHONE_TYPE_GSM) {
2395                 for (Connection cn : fgConnections) {
2396                     if ((cn != null) && (cn.getPostDialState() == Connection.PostDialState.WAIT)) {
2397                         postDialStr = cn.getRemainingPostDialString();
2398                         showWaitPromptDialog(cn, postDialStr);
2399                     }
2400                 }
2401             } else {
2402                 throw new IllegalStateException("Unexpected phone type: " + phoneType);
2403             }
2404         }
2405     }
2406 
2407     /**
2408      * (Re)synchronizes the onscreen UI with the current state of the
2409      * Phone.
2410      *
2411      * @return InCallInitStatus.SUCCESS if we successfully updated the UI, or
2412      *    InCallInitStatus.PHONE_NOT_IN_USE if there was no phone state to sync
2413      *    with (ie. the phone was completely idle).  In the latter case, we
2414      *    shouldn't even be in the in-call UI in the first place, and it's
2415      *    the caller's responsibility to bail out of this activity by
2416      *    calling endInCallScreenSession if appropriate.
2417      */
syncWithPhoneState()2418     private InCallInitStatus syncWithPhoneState() {
2419         boolean updateSuccessful = false;
2420         if (DBG) log("syncWithPhoneState()...");
2421         if (DBG) PhoneUtils.dumpCallState(mPhone);
2422         if (VDBG) dumpBluetoothState();
2423 
2424         // Make sure the Phone is "in use".  (If not, we shouldn't be on
2425         // this screen in the first place.)
2426 
2427         // Need to treat running MMI codes as a connection as well.
2428         // Do not check for getPendingMmiCodes when phone is a CDMA phone
2429         int phoneType = mPhone.getPhoneType();
2430 
2431         if ((phoneType == Phone.PHONE_TYPE_CDMA)
2432                 && ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL)
2433                 || (mInCallScreenMode == InCallScreenMode.OTA_ENDED))) {
2434             // Even when OTA Call ends, need to show OTA End UI,
2435             // so return Success to allow UI update.
2436             return InCallInitStatus.SUCCESS;
2437         }
2438 
2439         if ((phoneType == Phone.PHONE_TYPE_CDMA)
2440                 || !mForegroundCall.isIdle() || !mBackgroundCall.isIdle() || !mRingingCall.isIdle()
2441                 || !mPhone.getPendingMmiCodes().isEmpty()) {
2442             if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");
2443             updateScreen();
2444             return InCallInitStatus.SUCCESS;
2445         }
2446 
2447         if (DBG) log("syncWithPhoneState: phone is idle; we shouldn't be here!");
2448         return InCallInitStatus.PHONE_NOT_IN_USE;
2449     }
2450 
2451     /**
2452      * Given the Intent we were initially launched with,
2453      * figure out the actual phone number we should dial.
2454      *
2455      * @return the phone number corresponding to the
2456      *   specified Intent, or null if the Intent is not
2457      *   an ACTION_CALL intent or if the intent's data is
2458      *   malformed or missing.
2459      *
2460      * @throws VoiceMailNumberMissingException if the intent
2461      *   contains a "voicemail" URI, but there's no voicemail
2462      *   number configured on the device.
2463      */
getInitialNumber(Intent intent)2464     private String getInitialNumber(Intent intent)
2465             throws PhoneUtils.VoiceMailNumberMissingException {
2466         String action = intent.getAction();
2467 
2468         if (action == null) {
2469             return null;
2470         }
2471 
2472         if (action != null && action.equals(Intent.ACTION_CALL) &&
2473                 intent.hasExtra(Intent.EXTRA_PHONE_NUMBER)) {
2474             return intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
2475         }
2476 
2477         return PhoneUtils.getNumberFromIntent(this, mPhone, intent);
2478     }
2479 
2480     /**
2481      * Make a call to whomever the intent tells us to.
2482      *
2483      * @param intent the Intent we were launched with
2484      * @return InCallInitStatus.SUCCESS if we successfully initiated an
2485      *    outgoing call.  If there was some kind of failure, return one of
2486      *    the other InCallInitStatus codes indicating what went wrong.
2487      */
placeCall(Intent intent)2488     private InCallInitStatus placeCall(Intent intent) {
2489         if (VDBG) log("placeCall()...  intent = " + intent);
2490 
2491         String number;
2492 
2493         // Check the current ServiceState to make sure it's OK
2494         // to even try making a call.
2495         InCallInitStatus okToCallStatus = checkIfOkToInitiateOutgoingCall();
2496 
2497         try {
2498             number = getInitialNumber(intent);
2499         } catch (PhoneUtils.VoiceMailNumberMissingException ex) {
2500             // If the call status is NOT in an acceptable state, it
2501             // may effect the way the voicemail number is being
2502             // retrieved.  Mask the VoiceMailNumberMissingException
2503             // with the underlying issue of the phone state.
2504             if (okToCallStatus != InCallInitStatus.SUCCESS) {
2505                 if (DBG) log("Voicemail number not reachable in current SIM card state.");
2506                 return okToCallStatus;
2507             }
2508             if (DBG) log("VoiceMailNumberMissingException from getInitialNumber()");
2509             return InCallInitStatus.VOICEMAIL_NUMBER_MISSING;
2510         }
2511 
2512         if (number == null) {
2513             Log.w(LOG_TAG, "placeCall: couldn't get a phone number from Intent " + intent);
2514             return InCallInitStatus.NO_PHONE_NUMBER_SUPPLIED;
2515         }
2516 
2517         boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(number);
2518         boolean isEmergencyIntent = Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction());
2519 
2520         if (isEmergencyNumber && !isEmergencyIntent) {
2521             Log.e(LOG_TAG, "Non-CALL_EMERGENCY Intent " + intent
2522                     + " attempted to call emergency number " + number
2523                     + ".");
2524             return InCallInitStatus.CALL_FAILED;
2525         } else if (!isEmergencyNumber && isEmergencyIntent) {
2526             Log.e(LOG_TAG, "Received CALL_EMERGENCY Intent " + intent
2527                     + " with non-emergency number " + number
2528                     + " -- failing call.");
2529             return InCallInitStatus.CALL_FAILED;
2530         }
2531 
2532         // need to make sure that the state is adjusted if we are ONLY
2533         // allowed to dial emergency numbers AND we encounter an
2534         // emergency number request.
2535         if (isEmergencyNumber && okToCallStatus == InCallInitStatus.EMERGENCY_ONLY) {
2536             okToCallStatus = InCallInitStatus.SUCCESS;
2537             if (DBG) log("Emergency number detected, changing state to: " + okToCallStatus);
2538         }
2539 
2540         if (okToCallStatus != InCallInitStatus.SUCCESS) {
2541             // If this is an emergency call, we call the emergency call
2542             // handler activity to turn on the radio and do whatever else
2543             // is needed. For now, we finish the InCallScreen (since were
2544             // expecting a callback when the emergency call handler dictates
2545             // it) and just return the success state.
2546             if (isEmergencyNumber && (okToCallStatus == InCallInitStatus.POWER_OFF)) {
2547                 startActivity(intent.setClassName(this, EmergencyCallHandler.class.getName()));
2548                 if (DBG) log("placeCall: starting EmergencyCallHandler, finishing InCallScreen...");
2549                 endInCallScreenSession();
2550                 return InCallInitStatus.SUCCESS;
2551             } else {
2552                 return okToCallStatus;
2553             }
2554         }
2555 
2556         final PhoneApp app = PhoneApp.getInstance();
2557 
2558         if ((mPhone.isOtaSpNumber(number)) && (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA)) {
2559             if (DBG) log("placeCall: isOtaSpNumber() returns true");
2560             setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
2561             if (app.cdmaOtaProvisionData != null) {
2562                 app.cdmaOtaProvisionData.isOtaCallCommitted = false;
2563             }
2564         }
2565 
2566         mNeedShowCallLostDialog = false;
2567 
2568         // We have a valid number, so try to actually place a call:
2569         // make sure we pass along the intent's URI which is a
2570         // reference to the contact. We may have a provider gateway
2571         // phone number to use for the outgoing call.
2572         int callStatus;
2573         Uri contactUri = intent.getData();
2574 
2575         if (null != mProviderGatewayUri &&
2576             !(isEmergencyNumber || isEmergencyIntent) &&
2577             PhoneUtils.isRoutableViaGateway(number)) {  // Filter out MMI, OTA and other codes.
2578 
2579             callStatus = PhoneUtils.placeCallVia(
2580                 this, mPhone, number, contactUri, mProviderGatewayUri);
2581         } else {
2582             callStatus = PhoneUtils.placeCall(mPhone, number, contactUri);
2583         }
2584 
2585         switch (callStatus) {
2586             case PhoneUtils.CALL_STATUS_DIALED:
2587                 if (VDBG) log("placeCall: PhoneUtils.placeCall() succeeded for regular call '"
2588                              + number + "'.");
2589 
2590                 if (mInCallScreenMode == InCallScreenMode.OTA_NORMAL) {
2591                     app.cdmaOtaScreenState.otaScreenState =
2592                             CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING;
2593                     updateScreen();
2594                 }
2595 
2596                 // Any time we initiate a call, force the DTMF dialpad to
2597                 // close.  (We want to make sure the user can see the regular
2598                 // in-call UI while the new call is dialing, and when it
2599                 // first gets connected.)
2600                 mDialer.closeDialer(false);  // no "closing" animation
2601 
2602                 // Also, in case a previous call was already active (i.e. if
2603                 // we just did "Add call"), clear out the "history" of DTMF
2604                 // digits you typed, to make sure it doesn't persist from the
2605                 // previous call to the new call.
2606                 // TODO: it would be more precise to do this when the actual
2607                 // phone state change happens (i.e. when a new foreground
2608                 // call appears and the previous call moves to the
2609                 // background), but the InCallScreen doesn't keep enough
2610                 // state right now to notice that specific transition in
2611                 // onPhoneStateChanged().
2612                 mDialer.clearDigits();
2613 
2614                 if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
2615                     // Start the 2 second timer for 3 Way CallerInfo
2616                     if (app.cdmaPhoneCallState.getCurrentCallState()
2617                             == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
2618                         //Unmute for the second MO call
2619                         PhoneUtils.setMuteInternal(mPhone, false);
2620 
2621                         //Start the timer for displaying "Dialing" for second call
2622                         Message msg = Message.obtain(mHandler, THREEWAY_CALLERINFO_DISPLAY_DONE);
2623                         mHandler.sendMessageDelayed(msg, THREEWAY_CALLERINFO_DISPLAY_TIME);
2624 
2625                         // Set the mThreeWayCallOrigStateDialing state to true
2626                         app.cdmaPhoneCallState.setThreeWayCallOrigState(true);
2627 
2628                         //Update screen to show 3way dialing
2629                         updateScreen();
2630                     }
2631                 }
2632 
2633                 return InCallInitStatus.SUCCESS;
2634             case PhoneUtils.CALL_STATUS_DIALED_MMI:
2635                 if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");
2636                 // The passed-in number was an MMI code, not a regular phone number!
2637                 // This isn't really a failure; the Dialer may have deliberately
2638                 // fired an ACTION_CALL intent to dial an MMI code, like for a
2639                 // USSD call.
2640                 //
2641                 // Presumably an MMI_INITIATE message will come in shortly
2642                 // (and we'll bring up the "MMI Started" dialog), or else
2643                 // an MMI_COMPLETE will come in (which will take us to a
2644                 // different Activity; see PhoneUtils.displayMMIComplete()).
2645                 return InCallInitStatus.DIALED_MMI;
2646             case PhoneUtils.CALL_STATUS_FAILED:
2647                 Log.w(LOG_TAG, "placeCall: PhoneUtils.placeCall() FAILED for number '"
2648                       + number + "'.");
2649                 // We couldn't successfully place the call; there was some
2650                 // failure in the telephony layer.
2651                 return InCallInitStatus.CALL_FAILED;
2652             default:
2653                 Log.w(LOG_TAG, "placeCall: unknown callStatus " + callStatus
2654                       + " from PhoneUtils.placeCall() for number '" + number + "'.");
2655                 return InCallInitStatus.SUCCESS;  // Try to continue anyway...
2656         }
2657     }
2658 
2659     /**
2660      * Checks the current ServiceState to make sure it's OK
2661      * to try making an outgoing call to the specified number.
2662      *
2663      * @return InCallInitStatus.SUCCESS if it's OK to try calling the specified
2664      *    number.  If not, like if the radio is powered off or we have no
2665      *    signal, return one of the other InCallInitStatus codes indicating what
2666      *    the problem is.
2667      */
checkIfOkToInitiateOutgoingCall()2668     private InCallInitStatus checkIfOkToInitiateOutgoingCall() {
2669         // Watch out: do NOT use PhoneStateIntentReceiver.getServiceState() here;
2670         // that's not guaranteed to be fresh.  To synchronously get the
2671         // CURRENT service state, ask the Phone object directly:
2672         int state = mPhone.getServiceState().getState();
2673         if (VDBG) log("checkIfOkToInitiateOutgoingCall: ServiceState = " + state);
2674 
2675         switch (state) {
2676             case ServiceState.STATE_IN_SERVICE:
2677                 // Normal operation.  It's OK to make outgoing calls.
2678                 return InCallInitStatus.SUCCESS;
2679 
2680 
2681             case ServiceState.STATE_POWER_OFF:
2682                 // Radio is explictly powered off.
2683                 return InCallInitStatus.POWER_OFF;
2684 
2685             case ServiceState.STATE_OUT_OF_SERVICE:
2686             case ServiceState.STATE_EMERGENCY_ONLY:
2687                 // The phone is registered, but locked. Only emergency
2688                 // numbers are allowed.
2689                 return InCallInitStatus.EMERGENCY_ONLY;
2690             default:
2691                 throw new IllegalStateException("Unexpected ServiceState: " + state);
2692         }
2693     }
2694 
handleMissingVoiceMailNumber()2695     private void handleMissingVoiceMailNumber() {
2696         if (DBG) log("handleMissingVoiceMailNumber");
2697 
2698         final Message msg = Message.obtain(mHandler);
2699         msg.what = DONT_ADD_VOICEMAIL_NUMBER;
2700 
2701         final Message msg2 = Message.obtain(mHandler);
2702         msg2.what = ADD_VOICEMAIL_NUMBER;
2703 
2704         mMissingVoicemailDialog = new AlertDialog.Builder(this)
2705                 .setTitle(R.string.no_vm_number)
2706                 .setMessage(R.string.no_vm_number_msg)
2707                 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2708                         public void onClick(DialogInterface dialog, int which) {
2709                             if (VDBG) log("Missing voicemail AlertDialog: POSITIVE click...");
2710                             msg.sendToTarget();  // see dontAddVoiceMailNumber()
2711                             PhoneApp.getInstance().pokeUserActivity();
2712                         }})
2713                 .setNegativeButton(R.string.add_vm_number_str,
2714                                    new DialogInterface.OnClickListener() {
2715                         public void onClick(DialogInterface dialog, int which) {
2716                             if (VDBG) log("Missing voicemail AlertDialog: NEGATIVE click...");
2717                             msg2.sendToTarget();  // see addVoiceMailNumber()
2718                             PhoneApp.getInstance().pokeUserActivity();
2719                         }})
2720                 .setOnCancelListener(new OnCancelListener() {
2721                         public void onCancel(DialogInterface dialog) {
2722                             if (VDBG) log("Missing voicemail AlertDialog: CANCEL handler...");
2723                             msg.sendToTarget();  // see dontAddVoiceMailNumber()
2724                             PhoneApp.getInstance().pokeUserActivity();
2725                         }})
2726                 .create();
2727 
2728         // When the dialog is up, completely hide the in-call UI
2729         // underneath (which is in a partially-constructed state).
2730         mMissingVoicemailDialog.getWindow().addFlags(
2731                 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
2732 
2733         mMissingVoicemailDialog.show();
2734     }
2735 
addVoiceMailNumberPanel()2736     private void addVoiceMailNumberPanel() {
2737         if (mMissingVoicemailDialog != null) {
2738             mMissingVoicemailDialog.dismiss();
2739             mMissingVoicemailDialog = null;
2740         }
2741         if (DBG) log("addVoiceMailNumberPanel: finishing InCallScreen...");
2742         endInCallScreenSession();
2743 
2744         if (DBG) log("show vm setting");
2745 
2746         // navigate to the Voicemail setting in the Call Settings activity.
2747         Intent intent = new Intent(CallFeaturesSetting.ACTION_ADD_VOICEMAIL);
2748         intent.setClass(this, CallFeaturesSetting.class);
2749         startActivity(intent);
2750     }
2751 
dontAddVoiceMailNumber()2752     private void dontAddVoiceMailNumber() {
2753         if (mMissingVoicemailDialog != null) {
2754             mMissingVoicemailDialog.dismiss();
2755             mMissingVoicemailDialog = null;
2756         }
2757         if (DBG) log("dontAddVoiceMailNumber: finishing InCallScreen...");
2758         endInCallScreenSession();
2759     }
2760 
2761     /**
2762      * Do some delayed cleanup after a Phone call gets disconnected.
2763      *
2764      * This method gets called a couple of seconds after any DISCONNECT
2765      * event from the Phone; it's triggered by the
2766      * DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
2767      *
2768      * If the Phone is totally idle right now, that means we've already
2769      * shown the "call ended" state for a couple of seconds, and it's now
2770      * time to endInCallScreenSession this activity.
2771      *
2772      * If the Phone is *not* idle right now, that probably means that one
2773      * call ended but the other line is still in use.  In that case, we
2774      * *don't* exit the in-call screen, but we at least turn off the
2775      * backlight (which we turned on in onDisconnect().)
2776      */
delayedCleanupAfterDisconnect()2777     private void delayedCleanupAfterDisconnect() {
2778         if (VDBG) log("delayedCleanupAfterDisconnect()...  Phone state = " + mPhone.getState());
2779 
2780         // Clean up any connections in the DISCONNECTED state.
2781         //
2782         // [Background: Even after a connection gets disconnected, its
2783         // Connection object still stays around, in the special
2784         // DISCONNECTED state.  This is necessary because we we need the
2785         // caller-id information from that Connection to properly draw the
2786         // "Call ended" state of the CallCard.
2787         //   But at this point we truly don't need that connection any
2788         // more, so tell the Phone that it's now OK to to clean up any
2789         // connections still in that state.]
2790         mPhone.clearDisconnected();
2791 
2792         if (!phoneIsInUse()) {
2793             // Phone is idle!  We should exit this screen now.
2794             if (DBG) log("- delayedCleanupAfterDisconnect: phone is idle...");
2795 
2796             // No need to re-enable keyguard or screen wake state here;
2797             // that happens in onPause() when we actually exit.
2798 
2799             // And (finally!) exit from the in-call screen
2800             // (but not if we're already in the process of pausing...)
2801             if (mIsForegroundActivity) {
2802                 if (DBG) log("- delayedCleanupAfterDisconnect: finishing InCallScreen...");
2803 
2804                 // If this is a call that was initiated by the user, and
2805                 // we're *not* in emergency mode, finish the call by
2806                 // taking the user to the Call Log.
2807                 // Otherwise we simply call endInCallScreenSession, which will take us
2808                 // back to wherever we came from.
2809                 if (mShowCallLogAfterDisconnect && !isPhoneStateRestricted()) {
2810                     if (VDBG) log("- Show Call Log after disconnect...");
2811                     final Intent intent = PhoneApp.createCallLogIntent();
2812                     intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
2813                     startActivity(intent);
2814                     // Even in this case we still call endInCallScreenSession (below),
2815                     // to make sure we don't stay in the activity history.
2816                 }
2817 
2818                 endInCallScreenSession();
2819             }
2820         } else {
2821             // The phone is still in use.  Stay here in this activity, but
2822             // we don't need to keep the screen on.
2823             if (DBG) log("- delayedCleanupAfterDisconnect: staying on the InCallScreen...");
2824             if (DBG) PhoneUtils.dumpCallState(mPhone);
2825             // No need to re-enable keyguard or screen wake state here;
2826             // should be taken care of in onPhoneStateChanged();
2827         }
2828     }
2829 
2830 
2831     //
2832     // Callbacks for buttons / menu items.
2833     //
2834 
onClick(View view)2835     public void onClick(View view) {
2836         int id = view.getId();
2837         if (VDBG) log("onClick(View " + view + ", id " + id + ")...");
2838         if (VDBG && view instanceof InCallMenuItemView) {
2839             InCallMenuItemView item = (InCallMenuItemView) view;
2840             log("  ==> menu item! " + item);
2841         }
2842 
2843         // Most menu items dismiss the menu immediately once you click
2844         // them.  But some items (the "toggle" buttons) are different:
2845         // they want the menu to stay visible for a second afterwards to
2846         // give you feedback about the state change.
2847         boolean dismissMenuImmediate = true;
2848 
2849         switch (id) {
2850             case R.id.menuAnswerAndHold:
2851                 if (VDBG) log("onClick: AnswerAndHold...");
2852                 internalAnswerCall();  // Automatically holds the current active call
2853                 break;
2854 
2855             case R.id.menuAnswerAndEnd:
2856                 if (VDBG) log("onClick: AnswerAndEnd...");
2857                 internalAnswerAndEnd();
2858                 break;
2859 
2860             case R.id.menuAnswer:
2861                 if (DBG) log("onClick: Answer...");
2862                 internalAnswerCall();
2863                 break;
2864 
2865             case R.id.menuIgnore:
2866                 if (DBG) log("onClick: Ignore...");
2867                 internalHangupRingingCall();
2868                 break;
2869 
2870             case R.id.menuSwapCalls:
2871                 if (DBG) log("onClick: SwapCalls...");
2872                 internalSwapCalls();
2873                 break;
2874 
2875             case R.id.menuMergeCalls:
2876                 if (VDBG) log("onClick: MergeCalls...");
2877                 PhoneUtils.mergeCalls(mPhone);
2878                 break;
2879 
2880             case R.id.menuManageConference:
2881                 if (VDBG) log("onClick: ManageConference...");
2882                 // Show the Manage Conference panel.
2883                 setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
2884                 break;
2885 
2886             case R.id.menuShowDialpad:
2887                 if (VDBG) log("onClick: Show/hide dialpad...");
2888                 onShowHideDialpad();
2889                 break;
2890 
2891             case R.id.manage_done:  // mButtonManageConferenceDone
2892                 if (VDBG) log("onClick: mButtonManageConferenceDone...");
2893                 // Hide the Manage Conference panel, return to NORMAL mode.
2894                 setInCallScreenMode(InCallScreenMode.NORMAL);
2895                 break;
2896 
2897             case R.id.menuSpeaker:
2898                 if (VDBG) log("onClick: Speaker...");
2899                 onSpeakerClick();
2900                 // This is a "toggle" button; let the user see the new state for a moment.
2901                 dismissMenuImmediate = false;
2902                 break;
2903 
2904             case R.id.menuBluetooth:
2905                 if (VDBG) log("onClick: Bluetooth...");
2906                 onBluetoothClick();
2907                 // This is a "toggle" button; let the user see the new state for a moment.
2908                 dismissMenuImmediate = false;
2909                 break;
2910 
2911             case R.id.menuMute:
2912                 if (VDBG) log("onClick: Mute...");
2913                 onMuteClick();
2914                 // This is a "toggle" button; let the user see the new state for a moment.
2915                 dismissMenuImmediate = false;
2916                 break;
2917 
2918             case R.id.menuHold:
2919                 if (VDBG) log("onClick: Hold...");
2920                 onHoldClick();
2921                 // This is a "toggle" button; let the user see the new state for a moment.
2922                 dismissMenuImmediate = false;
2923                 break;
2924 
2925             case R.id.menuAddCall:
2926                 if (VDBG) log("onClick: AddCall...");
2927                 PhoneUtils.startNewCall(mPhone);  // Fires off an ACTION_DIAL intent
2928                 break;
2929 
2930             case R.id.menuEndCall:
2931                 if (VDBG) log("onClick: EndCall...");
2932                 internalHangup();
2933                 break;
2934 
2935             default:
2936                 if  ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL
2937                         || mInCallScreenMode == InCallScreenMode.OTA_ENDED)
2938                         && otaUtils != null) {
2939                     otaUtils.onClickHandler(id);
2940                 } else {
2941                     Log.w(LOG_TAG,
2942                             "Got click from unexpected View ID " + id + " (View = " + view + ")");
2943                 }
2944                 break;
2945         }
2946 
2947         if (ENABLE_PHONE_UI_EVENT_LOGGING) {
2948             // TODO: For now we care only about whether the user uses the
2949             // in-call buttons at all.  But in the future we may want to
2950             // log exactly which buttons are being clicked.  (Maybe just
2951             // call view.getText() here, and append that to the event value?)
2952             Checkin.logEvent(getContentResolver(),
2953                              Checkin.Events.Tag.PHONE_UI,
2954                              PHONE_UI_EVENT_BUTTON_CLICK);
2955         }
2956 
2957         // If the user just clicked a "stateful" menu item (i.e. one of
2958         // the toggle buttons), we keep the menu onscreen briefly to
2959         // provide visual feedback.  Since we want the user to see the
2960         // *new* current state, force the menu items to update right now.
2961         //
2962         // Note that some toggle buttons ("Hold" in particular) do NOT
2963         // immediately change the state of the Phone.  In that case, the
2964         // updateItems() call below won't have any visible effect.
2965         // Instead, the menu will get updated by the updateScreen() call
2966         // that happens from onPhoneStateChanged().
2967 
2968         if (!dismissMenuImmediate) {
2969             // TODO: mInCallMenu.updateItems() is a very big hammer; it
2970             // would be more efficient to update *only* the menu item(s)
2971             // we just changed.  (Doing it this way doesn't seem to cause
2972             // a noticeable performance problem, though.)
2973             if (VDBG) log("- onClick: updating menu to show 'new' current state...");
2974             boolean okToShowMenu = mInCallMenu.updateItems(mPhone);
2975             if (!okToShowMenu) {
2976                 // Uh oh.  All we tried to do was update the state of the
2977                 // menu items, but the logic in InCallMenu.updateItems()
2978                 // just decided the menu shouldn't be visible at all!
2979                 // (That probably means that the call ended asynchronously
2980                 // while the menu was up.)
2981                 //
2982                 // That's OK; just make sure to take the menu down ASAP.
2983                 if (VDBG) log("onClick: Tried to update menu, but now need to take it down!");
2984                 dismissMenuImmediate = true;
2985             }
2986         }
2987 
2988         // Any menu item counts as explicit "user activity".
2989         PhoneApp.getInstance().pokeUserActivity();
2990 
2991         // Finally, *any* action handled here closes the menu (either
2992         // immediately, or after a short delay).
2993         //
2994         // Note that some of the clicks we handle here aren't even menu
2995         // items in the first place, like the mButtonManageConferenceDone
2996         // button.  That's OK; if the menu is already closed, the
2997         // dismissMenu() call does nothing.
2998         dismissMenu(dismissMenuImmediate);
2999     }
3000 
onHoldClick()3001     private void onHoldClick() {
3002         if (VDBG) log("onHoldClick()...");
3003 
3004         final boolean hasActiveCall = !mForegroundCall.isIdle();
3005         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
3006         if (VDBG) log("- hasActiveCall = " + hasActiveCall
3007                       + ", hasHoldingCall = " + hasHoldingCall);
3008         boolean newHoldState;
3009         boolean holdButtonEnabled;
3010         if (hasActiveCall && !hasHoldingCall) {
3011             // There's only one line in use, and that line is active.
3012             PhoneUtils.switchHoldingAndActive(mPhone);  // Really means "hold" in this state
3013             newHoldState = true;
3014             holdButtonEnabled = true;
3015         } else if (!hasActiveCall && hasHoldingCall) {
3016             // There's only one line in use, and that line is on hold.
3017             PhoneUtils.switchHoldingAndActive(mPhone);  // Really means "unhold" in this state
3018             newHoldState = false;
3019             holdButtonEnabled = true;
3020         } else {
3021             // Either zero or 2 lines are in use; "hold/unhold" is meaningless.
3022             newHoldState = false;
3023             holdButtonEnabled = false;
3024         }
3025         // TODO: we *could* now forcibly update the "Hold" button based on
3026         // "newHoldState" and "holdButtonEnabled".  But for now, do
3027         // nothing here, and instead let the menu get updated when the
3028         // onPhoneStateChanged() callback comes in.  (This seems to be
3029         // responsive enough.)
3030 
3031         // Also, any time we hold or unhold, force the DTMF dialpad to close.
3032         mDialer.closeDialer(true);  // do the "closing" animation
3033     }
3034 
onSpeakerClick()3035     private void onSpeakerClick() {
3036         if (VDBG) log("onSpeakerClick()...");
3037 
3038         // TODO: Turning on the speaker seems to enable the mic
3039         //   whether or not the "mute" feature is active!
3040         // Not sure if this is an feature of the telephony API
3041         //   that I need to handle specially, or just a bug.
3042         boolean newSpeakerState = !PhoneUtils.isSpeakerOn(this);
3043         if (newSpeakerState && isBluetoothAvailable() && isBluetoothAudioConnected()) {
3044             disconnectBluetoothAudio();
3045         }
3046         PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
3047 
3048         if (newSpeakerState) {
3049             // The "touch lock" overlay is NEVER used when the speaker is on.
3050             enableTouchLock(false);
3051         } else {
3052             // User just turned the speaker *off*.  If the dialpad
3053             // is open, we need to start the timer that will
3054             // eventually bring up the "touch lock" overlay.
3055             if (mDialer.isOpened() && !isTouchLocked()) {
3056                 resetTouchLockTimer();
3057             }
3058         }
3059     }
3060 
onMuteClick()3061     private void onMuteClick() {
3062         if (VDBG) log("onMuteClick()...");
3063         boolean newMuteState = !PhoneUtils.getMute(mPhone);
3064         PhoneUtils.setMute(mPhone, newMuteState);
3065     }
3066 
onBluetoothClick()3067     private void onBluetoothClick() {
3068         if (VDBG) log("onBluetoothClick()...");
3069 
3070         if (isBluetoothAvailable()) {
3071             // Toggle the bluetooth audio connection state:
3072             if (isBluetoothAudioConnected()) {
3073                 disconnectBluetoothAudio();
3074             } else {
3075                 // Manually turn the speaker phone off, instead of allowing the
3076                 // Bluetooth audio routing handle it.  This ensures that the rest
3077                 // of the speakerphone code is executed, and reciprocates the
3078                 // menuSpeaker code above in onClick().  The onClick() code
3079                 // disconnects the active bluetooth headsets when the
3080                 // speakerphone is turned on.
3081                 if (PhoneUtils.isSpeakerOn(this)) {
3082                     PhoneUtils.turnOnSpeaker(this, false, true);
3083                 }
3084 
3085                 connectBluetoothAudio();
3086             }
3087         } else {
3088             // Bluetooth isn't available; the "Audio" button shouldn't have
3089             // been enabled in the first place!
3090             Log.w(LOG_TAG, "Got onBluetoothClick, but bluetooth is unavailable");
3091         }
3092     }
3093 
onShowHideDialpad()3094     private void onShowHideDialpad() {
3095         if (VDBG) log("onShowHideDialpad()...");
3096         if (mDialer.isOpened()) {
3097             mDialer.closeDialer(true);  // do the "closing" animation
3098         } else {
3099             mDialer.openDialer(true);  // do the "opening" animation
3100         }
3101         mDialer.setHandleVisible(true);
3102     }
3103 
3104     /**
3105      * Handles button clicks from the InCallTouchUi widget.
3106      */
handleOnscreenButtonClick(int id)3107     /* package */ void handleOnscreenButtonClick(int id) {
3108         if (DBG) log("handleOnscreenButtonClick(id " + id + ")...");
3109 
3110         switch (id) {
3111             // TODO: since every button here corresponds to a menu item that we
3112             // already handle in onClick(), maybe merge the guts of these two
3113             // methods into a separate helper that takes an ID (of either a menu
3114             // item *or* touch button) and does the appropriate user action.
3115 
3116             // Actions while an incoming call is ringing:
3117             case R.id.answerButton:
3118                 internalAnswerCall();
3119                 break;
3120             case R.id.rejectButton:
3121                 internalHangupRingingCall();
3122                 break;
3123 
3124             // The other regular (single-tap) buttons used while in-call:
3125             case R.id.holdButton:
3126                 onHoldClick();
3127                 break;
3128             case R.id.swapButton:
3129                 internalSwapCalls();
3130                 break;
3131             case R.id.endButton:
3132                 internalHangup();
3133                 break;
3134             case R.id.dialpadButton:
3135                 onShowHideDialpad();
3136                 break;
3137             case R.id.bluetoothButton:
3138                 onBluetoothClick();
3139                 break;
3140             case R.id.muteButton:
3141                 onMuteClick();
3142                 break;
3143             case R.id.speakerButton:
3144                 onSpeakerClick();
3145                 break;
3146             case R.id.addButton:
3147                 PhoneUtils.startNewCall(mPhone);  // Fires off an ACTION_DIAL intent
3148                 break;
3149             case R.id.mergeButton:
3150             case R.id.cdmaMergeButton:
3151                 PhoneUtils.mergeCalls(mPhone);
3152                 break;
3153             case R.id.manageConferencePhotoButton:
3154                 // Show the Manage Conference panel.
3155                 setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
3156                 break;
3157 
3158             default:
3159                 Log.w(LOG_TAG, "handleOnscreenButtonClick: unexpected ID " + id);
3160                 break;
3161         }
3162 
3163         // Just in case the user clicked a "stateful" menu item (i.e. one
3164         // of the toggle buttons), we force the in-call buttons to update,
3165         // to make sure the user sees the *new* current state.
3166         //
3167         // (But note that some toggle buttons may *not* immediately change
3168         // the state of the Phone, in which case the updateInCallTouchUi()
3169         // call here won't have any visible effect.  Instead, those
3170         // buttons will get updated by the updateScreen() call that gets
3171         // triggered when the onPhoneStateChanged() event comes in.)
3172         //
3173         // TODO: updateInCallTouchUi() is overkill here; it would be
3174         // more efficient to update *only* the affected button(s).
3175         // Consider adding API for that.  (This is lo-pri since
3176         // updateInCallTouchUi() is pretty cheap already...)
3177         updateInCallTouchUi();
3178     }
3179 
3180     /**
3181      * Update the network provider's overlay based on the value of
3182      * mProviderOverlayVisible.
3183      * If false the overlay is hidden otherwise it is shown.  A
3184      * delayed message is posted to take the overalay down after
3185      * PROVIDER_OVERLAY_TIMEOUT. This ensures the user will see the
3186      * overlay even if the call setup phase is very short.
3187      */
updateProviderOverlay()3188     private void updateProviderOverlay() {
3189         if (VDBG) log("updateProviderOverlay: " + mProviderOverlayVisible);
3190 
3191         ViewGroup overlay = (ViewGroup) findViewById(R.id.inCallProviderOverlay);
3192 
3193         if (mProviderOverlayVisible) {
3194             CharSequence template = getText(R.string.calling_via_template);
3195             CharSequence text = TextUtils.expandTemplate(template, mProviderLabel,
3196                                                          mProviderAddress);
3197 
3198             TextView message = (TextView) findViewById(R.id.callingVia);
3199             message.setCompoundDrawablesWithIntrinsicBounds(mProviderIcon, null, null, null);
3200             message.setText(text);
3201 
3202             overlay.setVisibility(View.VISIBLE);
3203 
3204             // Remove any zombie messages and then send a message to
3205             // self to remove the overlay after some time.
3206             mHandler.removeMessages(EVENT_HIDE_PROVIDER_OVERLAY);
3207             Message msg = Message.obtain(mHandler, EVENT_HIDE_PROVIDER_OVERLAY);
3208             mHandler.sendMessageDelayed(msg, PROVIDER_OVERLAY_TIMEOUT);
3209         } else {
3210             overlay.setVisibility(View.GONE);
3211         }
3212     }
3213 
3214     /**
3215      * Updates the "Press Menu for more options" hint based on the current
3216      * state of the Phone.
3217      */
updateMenuButtonHint()3218     private void updateMenuButtonHint() {
3219         if (VDBG) log("updateMenuButtonHint()...");
3220         boolean hintVisible = true;
3221 
3222         final boolean hasRingingCall = !mRingingCall.isIdle();
3223         final boolean hasActiveCall = !mForegroundCall.isIdle();
3224         final boolean hasHoldingCall = !mBackgroundCall.isIdle();
3225 
3226         // The hint is hidden only when there's no menu at all,
3227         // which only happens in a few specific cases:
3228         if (mInCallScreenMode == InCallScreenMode.CALL_ENDED) {
3229             // The "Call ended" state.
3230             hintVisible = false;
3231         } else if (hasRingingCall && !(hasActiveCall && !hasHoldingCall)) {
3232             // An incoming call where you *don't* have the option to
3233             // "answer & end" or "answer & hold".
3234             hintVisible = false;
3235         } else if (!phoneIsInUse()) {
3236             // Or if the phone is totally idle (like if an error dialog
3237             // is up, or an MMI is running.)
3238             hintVisible = false;
3239         }
3240 
3241         // The hint is also hidden on devices where we use onscreen
3242         // touchable buttons instead.
3243         if (isTouchUiEnabled()) {
3244             hintVisible = false;
3245         }
3246 
3247         int hintVisibility = (hintVisible) ? View.VISIBLE : View.GONE;
3248         mCallCard.getMenuButtonHint().setVisibility(hintVisibility);
3249 
3250         // TODO: Consider hiding the hint(s) whenever the menu is onscreen!
3251         // (Currently, the menu is rendered on top of the hint, but the
3252         // menu is semitransparent so you can still see the hint
3253         // underneath, and the hint is *just* visible enough to be
3254         // distracting.)
3255     }
3256 
3257     /**
3258      * Brings up UI to handle the various error conditions that
3259      * can occur when first initializing the in-call UI.
3260      * This is called from onResume() if we encountered
3261      * an error while processing our initial Intent.
3262      *
3263      * @param status one of the InCallInitStatus error codes.
3264      */
handleStartupError(InCallInitStatus status)3265     private void handleStartupError(InCallInitStatus status) {
3266         if (DBG) log("handleStartupError(): status = " + status);
3267 
3268         // NOTE that the regular Phone UI is in an uninitialized state at
3269         // this point, so we don't ever want the user to see it.
3270         // That means:
3271         // - Any cases here that need to go to some other activity should
3272         //   call startActivity() AND immediately call endInCallScreenSession
3273         //   on this one.
3274         // - Any cases here that bring up a Dialog must ensure that the
3275         //   Dialog handles both OK *and* cancel by calling endInCallScreenSession.
3276         //   Activity.  (See showGenericErrorDialog() for an example.)
3277 
3278         switch(status) {
3279 
3280             case VOICEMAIL_NUMBER_MISSING:
3281                 // Bring up the "Missing Voicemail Number" dialog, which
3282                 // will ultimately take us to some other Activity (or else
3283                 // just bail out of this activity.)
3284                 handleMissingVoiceMailNumber();
3285                 break;
3286 
3287             case POWER_OFF:
3288                 // Radio is explictly powered off.
3289 
3290                 // TODO: This UI is ultra-simple for 1.0.  It would be nicer
3291                 // to bring up a Dialog instead with the option "turn on radio
3292                 // now".  If selected, we'd turn the radio on, wait for
3293                 // network registration to complete, and then make the call.
3294 
3295                 showGenericErrorDialog(R.string.incall_error_power_off, true);
3296                 break;
3297 
3298             case EMERGENCY_ONLY:
3299                 // Only emergency numbers are allowed, but we tried to dial
3300                 // a non-emergency number.
3301                 showGenericErrorDialog(R.string.incall_error_emergency_only, true);
3302                 break;
3303 
3304             case PHONE_NOT_IN_USE:
3305                 // This error is handled directly in onResume() (by bailing
3306                 // out of the activity.)  We should never see it here.
3307                 Log.w(LOG_TAG,
3308                       "handleStartupError: unexpected PHONE_NOT_IN_USE status");
3309                 break;
3310 
3311             case NO_PHONE_NUMBER_SUPPLIED:
3312                 // The supplied Intent didn't contain a valid phone number.
3313                 // TODO: Need UI spec for this failure case; for now just
3314                 // show a generic error.
3315                 showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied, true);
3316                 break;
3317 
3318             case DIALED_MMI:
3319                 // Our initial phone number was actually an MMI sequence.
3320                 // There's no real "error" here, but we do bring up the
3321                 // a Toast (as requested of the New UI paradigm).
3322                 //
3323                 // In-call MMIs do not trigger the normal MMI Initiate
3324                 // Notifications, so we should notify the user here.
3325                 // Otherwise, the code in PhoneUtils.java should handle
3326                 // user notifications in the form of Toasts or Dialogs.
3327                 if (mPhone.getState() == Phone.State.OFFHOOK) {
3328                     Toast.makeText(this, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
3329                         .show();
3330                 }
3331                 break;
3332 
3333             case CALL_FAILED:
3334                 // We couldn't successfully place the call; there was some
3335                 // failure in the telephony layer.
3336                 // TODO: Need UI spec for this failure case; for now just
3337                 // show a generic error.
3338                 showGenericErrorDialog(R.string.incall_error_call_failed, true);
3339                 break;
3340 
3341             default:
3342                 Log.w(LOG_TAG, "handleStartupError: unexpected status code " + status);
3343                 showGenericErrorDialog(R.string.incall_error_call_failed, true);
3344                 break;
3345         }
3346     }
3347 
3348     /**
3349      * Utility function to bring up a generic "error" dialog, and then bail
3350      * out of the in-call UI when the user hits OK (or the BACK button.)
3351      */
showGenericErrorDialog(int resid, boolean isStartupError)3352     private void showGenericErrorDialog(int resid, boolean isStartupError) {
3353         CharSequence msg = getResources().getText(resid);
3354         if (DBG) log("showGenericErrorDialog('" + msg + "')...");
3355 
3356         // create the clicklistener and cancel listener as needed.
3357         DialogInterface.OnClickListener clickListener;
3358         OnCancelListener cancelListener;
3359         if (isStartupError) {
3360             clickListener = new DialogInterface.OnClickListener() {
3361                 public void onClick(DialogInterface dialog, int which) {
3362                     bailOutAfterErrorDialog();
3363                 }};
3364             cancelListener = new OnCancelListener() {
3365                 public void onCancel(DialogInterface dialog) {
3366                     bailOutAfterErrorDialog();
3367                 }};
3368         } else {
3369             clickListener = new DialogInterface.OnClickListener() {
3370                 public void onClick(DialogInterface dialog, int which) {
3371                     delayedCleanupAfterDisconnect();
3372                 }};
3373             cancelListener = new OnCancelListener() {
3374                 public void onCancel(DialogInterface dialog) {
3375                     delayedCleanupAfterDisconnect();
3376                 }};
3377         }
3378 
3379         // TODO: Consider adding a setTitle() call here (with some generic
3380         // "failure" title?)
3381         mGenericErrorDialog = new AlertDialog.Builder(this)
3382                 .setMessage(msg)
3383                 .setPositiveButton(R.string.ok, clickListener)
3384                 .setOnCancelListener(cancelListener)
3385                 .create();
3386 
3387         // When the dialog is up, completely hide the in-call UI
3388         // underneath (which is in a partially-constructed state).
3389         mGenericErrorDialog.getWindow().addFlags(
3390                 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
3391 
3392         mGenericErrorDialog.show();
3393     }
3394 
showCallLostDialog()3395     private void showCallLostDialog() {
3396         if (DBG) log("showCallLostDialog()...");
3397 
3398         // Don't need to show the dialog if InCallScreen isn't in the forgeround
3399         if (!mIsForegroundActivity) {
3400             if (DBG) log("showCallLostDialog: not the foreground Activity! Bailing out...");
3401             return;
3402         }
3403 
3404         // Don't need to show the dialog again, if there is one already.
3405         if (mCallLostDialog != null) {
3406             if (DBG) log("showCallLostDialog: There is a mCallLostDialog already.");
3407             return;
3408         }
3409 
3410         mCallLostDialog = new AlertDialog.Builder(this)
3411                 .setMessage(R.string.call_lost)
3412                 .setIcon(android.R.drawable.ic_dialog_alert)
3413                 .create();
3414         mCallLostDialog.show();
3415     }
3416 
bailOutAfterErrorDialog()3417     private void bailOutAfterErrorDialog() {
3418         if (mGenericErrorDialog != null) {
3419             if (DBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
3420             mGenericErrorDialog.dismiss();
3421             mGenericErrorDialog = null;
3422         }
3423         if (DBG) log("bailOutAfterErrorDialog(): end InCallScreen session...");
3424         endInCallScreenSession();
3425     }
3426 
3427     /**
3428      * Dismisses (and nulls out) all persistent Dialogs managed
3429      * by the InCallScreen.  Useful if (a) we're about to bring up
3430      * a dialog and want to pre-empt any currently visible dialogs,
3431      * or (b) as a cleanup step when the Activity is going away.
3432      */
dismissAllDialogs()3433     private void dismissAllDialogs() {
3434         if (DBG) log("dismissAllDialogs()...");
3435 
3436         // Note it's safe to dismiss() a dialog that's already dismissed.
3437         // (Even if the AlertDialog object(s) below are still around, it's
3438         // possible that the actual dialog(s) may have already been
3439         // dismissed by the user.)
3440 
3441         if (mMissingVoicemailDialog != null) {
3442             if (VDBG) log("- DISMISSING mMissingVoicemailDialog.");
3443             mMissingVoicemailDialog.dismiss();
3444             mMissingVoicemailDialog = null;
3445         }
3446         if (mMmiStartedDialog != null) {
3447             if (VDBG) log("- DISMISSING mMmiStartedDialog.");
3448             mMmiStartedDialog.dismiss();
3449             mMmiStartedDialog = null;
3450         }
3451         if (mGenericErrorDialog != null) {
3452             if (VDBG) log("- DISMISSING mGenericErrorDialog.");
3453             mGenericErrorDialog.dismiss();
3454             mGenericErrorDialog = null;
3455         }
3456         if (mSuppServiceFailureDialog != null) {
3457             if (VDBG) log("- DISMISSING mSuppServiceFailureDialog.");
3458             mSuppServiceFailureDialog.dismiss();
3459             mSuppServiceFailureDialog = null;
3460         }
3461         if (mWaitPromptDialog != null) {
3462             if (VDBG) log("- DISMISSING mWaitPromptDialog.");
3463             mWaitPromptDialog.dismiss();
3464             mWaitPromptDialog = null;
3465         }
3466         if (mWildPromptDialog != null) {
3467             if (VDBG) log("- DISMISSING mWildPromptDialog.");
3468             mWildPromptDialog.dismiss();
3469             mWildPromptDialog = null;
3470         }
3471         if (mCallLostDialog != null) {
3472             if (VDBG) log("- DISMISSING mCallLostDialog.");
3473             mCallLostDialog.dismiss();
3474             mCallLostDialog = null;
3475         }
3476         if ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL
3477                 || mInCallScreenMode == InCallScreenMode.OTA_ENDED)
3478                 && otaUtils != null) {
3479             otaUtils.dismissAllOtaDialogs();
3480         }
3481         if (mPausePromptDialog != null) {
3482             if (DBG) log("- DISMISSING mPausePromptDialog.");
3483             mPausePromptDialog.dismiss();
3484             mPausePromptDialog = null;
3485         }
3486     }
3487 
3488 
3489     //
3490     // Helper functions for answering incoming calls.
3491     //
3492 
3493     /**
3494      * Answer a ringing call.  This method does nothing if there's no
3495      * ringing or waiting call.
3496      */
internalAnswerCall()3497     /* package */ void internalAnswerCall() {
3498         // if (DBG) log("internalAnswerCall()...");
3499         // if (DBG) PhoneUtils.dumpCallState(mPhone);
3500 
3501         final boolean hasRingingCall = !mRingingCall.isIdle();
3502 
3503         if (hasRingingCall) {
3504             int phoneType = mPhone.getPhoneType();
3505             if (phoneType == Phone.PHONE_TYPE_CDMA) {
3506                 if (DBG) log("internalAnswerCall: answering (CDMA)...");
3507                 // In CDMA this is simply a wrapper around PhoneUtils.answerCall().
3508                 PhoneUtils.answerCall(mPhone);  // Automatically holds the current active call,
3509                                                 // if there is one
3510             } else if (phoneType == Phone.PHONE_TYPE_GSM) {
3511                 // GSM: this is usually just a wrapper around
3512                 // PhoneUtils.answerCall(), *but* we also need to do
3513                 // something special for the "both lines in use" case.
3514 
3515                 final boolean hasActiveCall = !mForegroundCall.isIdle();
3516                 final boolean hasHoldingCall = !mBackgroundCall.isIdle();
3517 
3518                 if (hasActiveCall && hasHoldingCall) {
3519                     if (DBG) log("internalAnswerCall: answering (both lines in use!)...");
3520                     // The relatively rare case where both lines are
3521                     // already in use.  We "answer incoming, end ongoing"
3522                     // in this case, according to the current UI spec.
3523                     PhoneUtils.answerAndEndActive(mPhone);
3524 
3525                     // Alternatively, we could use
3526                     //    PhoneUtils.answerAndEndHolding(mPhone);
3527                     // here to end the on-hold call instead.
3528                 } else {
3529                     if (DBG) log("internalAnswerCall: answering...");
3530                     PhoneUtils.answerCall(mPhone);  // Automatically holds the current active call,
3531                                                     // if there is one
3532                 }
3533             } else {
3534                 throw new IllegalStateException("Unexpected phone type: " + phoneType);
3535             }
3536         }
3537     }
3538 
3539     /**
3540      * Answer the ringing call *and* hang up the ongoing call.
3541      */
internalAnswerAndEnd()3542     /* package */ void internalAnswerAndEnd() {
3543         if (DBG) log("internalAnswerAndEnd()...");
3544         // if (DBG) PhoneUtils.dumpCallState(mPhone);
3545         PhoneUtils.answerAndEndActive(mPhone);
3546     }
3547 
3548     /**
3549      * Hang up the ringing call (aka "Don't answer").
3550      */
internalHangupRingingCall()3551     /* package */ void internalHangupRingingCall() {
3552         if (DBG) log("internalHangupRingingCall()...");
3553         PhoneUtils.hangupRingingCall(mPhone);
3554     }
3555 
3556     /**
3557      * Hang up the current active call.
3558      */
internalHangup()3559     /* package */ void internalHangup() {
3560         if (DBG) log("internalHangup()...");
3561         PhoneUtils.hangup(mPhone);
3562     }
3563 
3564     /**
3565      * InCallScreen-specific wrapper around PhoneUtils.switchHoldingAndActive().
3566      */
internalSwapCalls()3567     private void internalSwapCalls() {
3568         if (DBG) log("internalSwapCalls()...");
3569 
3570         // Any time we swap calls, force the DTMF dialpad to close.
3571         // (We want the regular in-call UI to be visible right now, so the
3572         // user can clearly see which call is now in the foreground.)
3573         mDialer.closeDialer(true);  // do the "closing" animation
3574 
3575         // Also, clear out the "history" of DTMF digits you typed, to make
3576         // sure you don't see digits from call #1 while call #2 is active.
3577         // (Yes, this does mean that swapping calls twice will cause you
3578         // to lose any previous digits from the current call; see the TODO
3579         // comment on DTMFTwelvKeyDialer.clearDigits() for more info.)
3580         mDialer.clearDigits();
3581 
3582         // Swap the fg and bg calls.
3583         PhoneUtils.switchHoldingAndActive(mPhone);
3584 
3585         // If we have a valid BluetoothHandsfree then since CDMA network or
3586         // Telephony FW does not send us information on which caller got swapped
3587         // we need to update the second call active state in BluetoothHandsfree internally
3588         if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
3589             BluetoothHandsfree bthf = PhoneApp.getInstance().getBluetoothHandsfree();
3590             if (bthf != null) {
3591                 bthf.cdmaSwapSecondCallState();
3592             }
3593         }
3594 
3595     }
3596 
3597     /**
3598      * Sets the current high-level "mode" of the in-call UI.
3599      *
3600      * NOTE: if newMode is CALL_ENDED, the caller is responsible for
3601      * posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
3602      * sure the "call ended" state goes away after a couple of seconds.
3603      */
setInCallScreenMode(InCallScreenMode newMode)3604     private void setInCallScreenMode(InCallScreenMode newMode) {
3605         if (DBG) log("setInCallScreenMode: " + newMode);
3606         mInCallScreenMode = newMode;
3607         switch (mInCallScreenMode) {
3608             case MANAGE_CONFERENCE:
3609                 if (!PhoneUtils.isConferenceCall(mForegroundCall)) {
3610                     Log.w(LOG_TAG, "MANAGE_CONFERENCE: no active conference call!");
3611                     // Hide the Manage Conference panel, return to NORMAL mode.
3612                     setInCallScreenMode(InCallScreenMode.NORMAL);
3613                     return;
3614                 }
3615                 List<Connection> connections = mForegroundCall.getConnections();
3616                 // There almost certainly will be > 1 connection,
3617                 // since isConferenceCall() just returned true.
3618                 if ((connections == null) || (connections.size() <= 1)) {
3619                     Log.w(LOG_TAG,
3620                           "MANAGE_CONFERENCE: Bogus TRUE from isConferenceCall(); connections = "
3621                           + connections);
3622                     // Hide the Manage Conference panel, return to NORMAL mode.
3623                     setInCallScreenMode(InCallScreenMode.NORMAL);
3624                     return;
3625                 }
3626 
3627                 // TODO: Don't do this here. The call to
3628                 // initManageConferencePanel() should instead happen
3629                 // automagically in ManageConferenceUtils the very first
3630                 // time you call updateManageConferencePanel() or
3631                 // setPanelVisible(true).
3632                 mManageConferenceUtils.initManageConferencePanel();  // if necessary
3633 
3634                 mManageConferenceUtils.updateManageConferencePanel(connections);
3635 
3636                 // The "Manage conference" UI takes up the full main frame,
3637                 // replacing the inCallPanel and CallCard PopupWindow.
3638                 mManageConferenceUtils.setPanelVisible(true);
3639 
3640                 // Start the chronometer.
3641                 // TODO: Similarly, we shouldn't expose startConferenceTime()
3642                 // and stopConferenceTime(); the ManageConferenceUtils
3643                 // class ought to manage the conferenceTime widget itself
3644                 // based on setPanelVisible() calls.
3645                 long callDuration = mForegroundCall.getEarliestConnection().getDurationMillis();
3646                 mManageConferenceUtils.startConferenceTime(
3647                         SystemClock.elapsedRealtime() - callDuration);
3648 
3649                 mInCallPanel.setVisibility(View.GONE);
3650 
3651                 // No need to close the dialer here, since the Manage
3652                 // Conference UI will just cover it up anyway.
3653 
3654                 break;
3655 
3656             case CALL_ENDED:
3657                 // Display the CallCard (in the "Call ended" state)
3658                 // and hide all other UI.
3659 
3660                 mManageConferenceUtils.setPanelVisible(false);
3661                 mManageConferenceUtils.stopConferenceTime();
3662 
3663                 updateMenuButtonHint();  // Hide the Menu button hint
3664 
3665                 // Make sure the CallCard (which is a child of mInCallPanel) is visible.
3666                 mInCallPanel.setVisibility(View.VISIBLE);
3667 
3668                 break;
3669 
3670             case NORMAL:
3671                 mInCallPanel.setVisibility(View.VISIBLE);
3672                 mManageConferenceUtils.setPanelVisible(false);
3673                 mManageConferenceUtils.stopConferenceTime();
3674                 break;
3675 
3676             case OTA_NORMAL:
3677                 otaUtils.setCdmaOtaInCallScreenUiState(
3678                         OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL);
3679                 mInCallPanel.setVisibility(View.GONE);
3680                 break;
3681 
3682             case OTA_ENDED:
3683                 otaUtils.setCdmaOtaInCallScreenUiState(
3684                         OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
3685                 mInCallPanel.setVisibility(View.GONE);
3686                 break;
3687 
3688             case UNDEFINED:
3689                 // Set our Activities intent to ACTION_UNDEFINED so
3690                 // that if we get resumed after we've completed a call
3691                 // the next call will not cause checkIsOtaCall to
3692                 // return true.
3693                 //
3694                 // With the framework as of October 2009 the sequence below
3695                 // causes the framework to call onResume, onPause, onNewIntent,
3696                 // onResume. If we don't call setIntent below then when the
3697                 // first onResume calls checkIsOtaCall via initOtaState it will
3698                 // return true and the Activity will be confused.
3699                 //
3700                 //  1) Power up Phone A
3701                 //  2) Place *22899 call and activate Phone A
3702                 //  3) Press the power key on Phone A to turn off the display
3703                 //  4) Call Phone A from Phone B answering Phone A
3704                 //  5) The screen will be blank (Should be normal InCallScreen)
3705                 //  6) Hang up the Phone B
3706                 //  7) Phone A displays the activation screen.
3707                 //
3708                 // Step 3 is the critical step to cause the onResume, onPause
3709                 // onNewIntent, onResume sequence. If step 3 is skipped the
3710                 // sequence will be onNewIntent, onResume and all will be well.
3711                 setIntent(new Intent(ACTION_UNDEFINED));
3712 
3713                 // Cleanup Ota Screen if necessary and set the panel
3714                 // to VISIBLE.
3715                 if (mPhone.getState() != Phone.State.OFFHOOK) {
3716                     if (otaUtils != null) {
3717                         otaUtils.cleanOtaScreen(true);
3718                     }
3719                 } else {
3720                     log("WARNING: Setting mode to UNDEFINED but phone is OFFHOOK,"
3721                             + " skip cleanOtaScreen.");
3722                 }
3723                 mInCallPanel.setVisibility(View.VISIBLE);
3724                 break;
3725         }
3726 
3727         // Update the visibility of the DTMF dialer tab on any state
3728         // change.
3729         updateDialpadVisibility();
3730 
3731         // Update the in-call touch UI on any state change (since it may
3732         // need to hide or re-show itself.)
3733         updateInCallTouchUi();
3734     }
3735 
3736     /**
3737      * @return true if the "Manage conference" UI is currently visible.
3738      */
isManageConferenceMode()3739     /* package */ boolean isManageConferenceMode() {
3740         return (mInCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE);
3741     }
3742 
3743     /**
3744      * Checks if the "Manage conference" UI needs to be updated.
3745      * If the state of the current conference call has changed
3746      * since our previous call to updateManageConferencePanel()),
3747      * do a fresh update.  Also, if the current call is no longer a
3748      * conference call at all, bail out of the "Manage conference" UI and
3749      * return to InCallScreenMode.NORMAL mode.
3750      */
updateManageConferencePanelIfNecessary()3751     private void updateManageConferencePanelIfNecessary() {
3752         if (VDBG) log("updateManageConferencePanelIfNecessary: " + mForegroundCall + "...");
3753 
3754         List<Connection> connections = mForegroundCall.getConnections();
3755         if (connections == null) {
3756             if (VDBG) log("==> no connections on foreground call!");
3757             // Hide the Manage Conference panel, return to NORMAL mode.
3758             setInCallScreenMode(InCallScreenMode.NORMAL);
3759             InCallInitStatus status = syncWithPhoneState();
3760             if (status != InCallInitStatus.SUCCESS) {
3761                 Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3762                 // We shouldn't even be in the in-call UI in the first
3763                 // place, so bail out:
3764                 if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 1");
3765                 endInCallScreenSession();
3766                 return;
3767             }
3768             return;
3769         }
3770 
3771         int numConnections = connections.size();
3772         if (numConnections <= 1) {
3773             if (VDBG) log("==> foreground call no longer a conference!");
3774             // Hide the Manage Conference panel, return to NORMAL mode.
3775             setInCallScreenMode(InCallScreenMode.NORMAL);
3776             InCallInitStatus status = syncWithPhoneState();
3777             if (status != InCallInitStatus.SUCCESS) {
3778                 Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3779                 // We shouldn't even be in the in-call UI in the first
3780                 // place, so bail out:
3781                 if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 2");
3782                 endInCallScreenSession();
3783                 return;
3784             }
3785             return;
3786         }
3787 
3788         // TODO: the test to see if numConnections has changed can go in
3789         // updateManageConferencePanel(), rather than here.
3790         if (numConnections != mManageConferenceUtils.getNumCallersInConference()) {
3791             if (VDBG) log("==> Conference size has changed; need to rebuild UI!");
3792             mManageConferenceUtils.updateManageConferencePanel(connections);
3793         }
3794     }
3795 
3796     /**
3797      * Updates the visibility of the DTMF dialpad (and its onscreen
3798      * "handle", if applicable), based on the current state of the phone
3799      * and/or the current InCallScreenMode.
3800      */
updateDialpadVisibility()3801     private void updateDialpadVisibility() {
3802         //
3803         // (1) The dialpad itself:
3804         //
3805         // If an incoming call is ringing, make sure the dialpad is
3806         // closed.  (We do this to make sure we're not covering up the
3807         // "incoming call" UI, and especially to make sure that the "touch
3808         // lock" overlay won't appear.)
3809         if (mPhone.getState() == Phone.State.RINGING) {
3810             mDialer.closeDialer(false);  // don't do the "closing" animation
3811 
3812             // Also, clear out the "history" of DTMF digits you may have typed
3813             // into the previous call (so you don't see the previous call's
3814             // digits if you answer this call and then bring up the dialpad.)
3815             //
3816             // TODO: it would be more precise to do this when you *answer* the
3817             // incoming call, rather than as soon as it starts ringing, but
3818             // the InCallScreen doesn't keep enough state right now to notice
3819             // that specific transition in onPhoneStateChanged().
3820             mDialer.clearDigits();
3821         }
3822 
3823         //
3824         // (2) The onscreen "handle":
3825         //
3826         // The handle is visible only if it's OK to actually open the
3827         // dialpad.  (Note this is meaningful only on platforms that use a
3828         // SlidingDrawer as a container for the dialpad.)
3829         mDialer.setHandleVisible(okToShowDialpad());
3830 
3831         //
3832         // (3) The main in-call panel (containing the CallCard):
3833         //
3834         // On some platforms(*) we need to hide the CallCard (which is a
3835         // child of mInCallPanel) while the dialpad is visible.
3836         //
3837         // (*) We need to do this when using the dialpad from the
3838         //     InCallTouchUi widget, but not when using the
3839         //     SlidingDrawer-based dialpad, because the SlidingDrawer itself
3840         //     is opaque.)
3841         if (!mDialer.usingSlidingDrawer()) {
3842             if (mDialerView != null) {
3843                 mDialerView.setKeysBackgroundResource(
3844                         isBluetoothAudioConnected() ? R.drawable.btn_dial_blue
3845                         : R.drawable.btn_dial_green);
3846             }
3847 
3848             if (isDialerOpened()) {
3849                 mInCallPanel.setVisibility(View.GONE);
3850             } else {
3851                 // Dialpad is dismissed; bring back the CallCard if
3852                 // it's supposed to be visible.
3853                 if ((mInCallScreenMode == InCallScreenMode.NORMAL)
3854                     || (mInCallScreenMode == InCallScreenMode.CALL_ENDED)) {
3855                     mInCallPanel.setVisibility(View.VISIBLE);
3856                 }
3857             }
3858         }
3859     }
3860 
3861     /**
3862      * @return true if the DTMF dialpad is currently visible.
3863      */
isDialerOpened()3864     /* package */ boolean isDialerOpened() {
3865         return (mDialer != null && mDialer.isOpened());
3866     }
3867 
3868     /**
3869      * Called any time the DTMF dialpad is opened.
3870      * @see DTMFTwelveKeyDialer.onDialerOpen()
3871      */
onDialerOpen()3872     /* package */ void onDialerOpen() {
3873         if (DBG) log("onDialerOpen()...");
3874 
3875         // ANY time the dialpad becomes visible, start the timer that will
3876         // eventually bring up the "touch lock" overlay.
3877         resetTouchLockTimer();
3878 
3879         // Update the in-call touch UI (which may need to hide itself, if
3880         // it's enabled.)
3881         updateInCallTouchUi();
3882 
3883         // Update any other onscreen UI elements that depend on the dialpad.
3884         updateDialpadVisibility();
3885 
3886         // This counts as explicit "user activity".
3887         PhoneApp.getInstance().pokeUserActivity();
3888 
3889         //If on OTA Call, hide OTA Screen
3890         // TODO: This may not be necessary, now that the dialpad is
3891         // always visible in OTA mode.
3892         if  ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL
3893                 || mInCallScreenMode == InCallScreenMode.OTA_ENDED)
3894                 && otaUtils != null) {
3895             otaUtils.hideOtaScreen();
3896         }
3897     }
3898 
3899     /**
3900      * Called any time the DTMF dialpad is closed.
3901      * @see DTMFTwelveKeyDialer.onDialerClose()
3902      */
onDialerClose()3903     /* package */ void onDialerClose() {
3904         if (DBG) log("onDialerClose()...");
3905 
3906         final PhoneApp app = PhoneApp.getInstance();
3907 
3908         // OTA-specific cleanup upon closing the dialpad.
3909         if ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL)
3910             || (mInCallScreenMode == InCallScreenMode.OTA_ENDED)
3911             || ((app.cdmaOtaScreenState != null)
3912                 && (app.cdmaOtaScreenState.otaScreenState ==
3913                     CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
3914             mDialer.setHandleVisible(false);
3915             if (otaUtils != null) {
3916                 otaUtils.otaShowProperScreen();
3917             }
3918         }
3919 
3920         // Dismiss the "touch lock" overlay if it was visible.
3921         // (The overlay is only ever used on top of the dialpad).
3922         enableTouchLock(false);
3923 
3924         // Update the in-call touch UI (which may need to re-show itself.)
3925         updateInCallTouchUi();
3926 
3927         // Update the visibility of the dialpad itself (and any other
3928         // onscreen UI elements that depend on it.)
3929         updateDialpadVisibility();
3930 
3931         // This counts as explicit "user activity".
3932         app.getInstance().pokeUserActivity();
3933     }
3934 
3935     /**
3936      * Determines when we can dial DTMF tones.
3937      */
okToDialDTMFTones()3938     private boolean okToDialDTMFTones() {
3939         final boolean hasRingingCall = !mRingingCall.isIdle();
3940         final Call.State fgCallState = mForegroundCall.getState();
3941 
3942         // We're allowed to send DTMF tones when there's an ACTIVE
3943         // foreground call, and not when an incoming call is ringing
3944         // (since DTMF tones are useless in that state), or if the
3945         // Manage Conference UI is visible (since the tab interferes
3946         // with the "Back to call" button.)
3947 
3948         // We can also dial while in ALERTING state because there are
3949         // some connections that never update to an ACTIVE state (no
3950         // indication from the network).
3951         boolean canDial =
3952             (fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
3953             && !hasRingingCall
3954             && (mInCallScreenMode != InCallScreenMode.MANAGE_CONFERENCE);
3955 
3956         if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
3957                 ", ringing state: " + hasRingingCall +
3958                 ", call screen mode: " + mInCallScreenMode +
3959                 ", result: " + canDial);
3960 
3961         return canDial;
3962     }
3963 
3964     /**
3965      * @return true if the in-call DTMF dialpad should be available to the
3966      *      user, given the current state of the phone and the in-call UI.
3967      *      (This is used to control the visibility of the dialer's
3968      *      onscreen handle, if applicable, and the enabledness of the "Show
3969      *      dialpad" onscreen button or menu item.)
3970      */
okToShowDialpad()3971     /* package */ boolean okToShowDialpad() {
3972         // The dialpad is available only when it's OK to dial DTMF
3973         // tones given the current state of the current call.
3974         return okToDialDTMFTones();
3975     }
3976 
3977     /**
3978      * Initializes the in-call touch UI on devices that need it.
3979      */
initInCallTouchUi()3980     private void initInCallTouchUi() {
3981         if (DBG) log("initInCallTouchUi()...");
3982         // TODO: we currently use the InCallTouchUi widget in at least
3983         // some states on ALL platforms.  But if some devices ultimately
3984         // end up not using *any* onscreen touch UI, we should make sure
3985         // to not even inflate the InCallTouchUi widget on those devices.
3986         mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
3987         mInCallTouchUi.setInCallScreenInstance(this);
3988     }
3989 
3990     /**
3991      * Updates the state of the in-call touch UI.
3992      */
updateInCallTouchUi()3993     private void updateInCallTouchUi() {
3994         if (mInCallTouchUi != null) {
3995             mInCallTouchUi.updateState(mPhone);
3996         }
3997     }
3998 
3999     /**
4000      * @return true if the onscreen touch UI is enabled (for regular
4001      * "ongoing call" states) on the current device.
4002      */
isTouchUiEnabled()4003     public boolean isTouchUiEnabled() {
4004         return (mInCallTouchUi != null) && mInCallTouchUi.isTouchUiEnabled();
4005     }
4006 
4007     /**
4008      * Posts a handler message telling the InCallScreen to update the
4009      * onscreen in-call touch UI.
4010      *
4011      * This is just a wrapper around updateInCallTouchUi(), for use by the
4012      * rest of the phone app or from a thread other than the UI thread.
4013      */
requestUpdateTouchUi()4014     /* package */ void requestUpdateTouchUi() {
4015         if (DBG) log("requestUpdateTouchUi()...");
4016 
4017         mHandler.removeMessages(REQUEST_UPDATE_TOUCH_UI);
4018         mHandler.sendEmptyMessage(REQUEST_UPDATE_TOUCH_UI);
4019     }
4020 
4021     /**
4022      * @return true if it's OK to display the in-call touch UI, given the
4023      * current state of the InCallScreen.
4024      */
okToShowInCallTouchUi()4025     /* package */ boolean okToShowInCallTouchUi() {
4026         // Note that this method is concerned only with the internal state
4027         // of the InCallScreen.  (The InCallTouchUi widget has separate
4028         // logic to make sure it's OK to display the touch UI given the
4029         // current telephony state, and that it's allowed on the current
4030         // device in the first place.)
4031 
4032         // The touch UI is NOT available if:
4033         // - we're in some InCallScreenMode other than NORMAL
4034         //   (like CALL_ENDED or one of the OTA modes)
4035         return (mInCallScreenMode == InCallScreenMode.NORMAL);
4036     }
4037 
4038     /**
4039      * @return true if we're in restricted / emergency dialing only mode.
4040      */
isPhoneStateRestricted()4041     public boolean isPhoneStateRestricted() {
4042         // TODO:  This needs to work IN TANDEM with the KeyGuardViewMediator Code.
4043         // Right now, it looks like the mInputRestricted flag is INTERNAL to the
4044         // KeyGuardViewMediator and SPECIFICALLY set to be FALSE while the emergency
4045         // phone call is being made, to allow for input into the InCallScreen.
4046         // Having the InCallScreen judge the state of the device from this flag
4047         // becomes meaningless since it is always false for us.  The mediator should
4048         // have an additional API to let this app know that it should be restricted.
4049         return ((mPhone.getServiceState().getState() == ServiceState.STATE_EMERGENCY_ONLY) ||
4050                 (mPhone.getServiceState().getState() == ServiceState.STATE_OUT_OF_SERVICE) ||
4051                 (PhoneApp.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()));
4052     }
4053 
4054     //
4055     // In-call menu UI
4056     //
4057 
4058     /**
4059      * Override onCreatePanelView(), in order to get complete control
4060      * over the UI that comes up when the user presses MENU.
4061      *
4062      * This callback allows me to return a totally custom View hierarchy
4063      * (with custom layout and custom "item" views) to be shown instead
4064      * of a standard android.view.Menu hierarchy.
4065      *
4066      * This gets called (with featureId == FEATURE_OPTIONS_PANEL) every
4067      * time we need to bring up the menu.  (And in cases where we return
4068      * non-null, that means that the "standard" menu callbacks
4069      * onCreateOptionsMenu() and onPrepareOptionsMenu() won't get called
4070      * at all.)
4071      */
4072     @Override
onCreatePanelView(int featureId)4073     public View onCreatePanelView(int featureId) {
4074         if (VDBG) log("onCreatePanelView(featureId = " + featureId + ")...");
4075 
4076         // We only want this special behavior for the "options panel"
4077         // feature (i.e. the standard menu triggered by the MENU button.)
4078         if (featureId != Window.FEATURE_OPTIONS_PANEL) {
4079             return null;
4080         }
4081 
4082         // For now, totally disable the in-call menu on devices where we
4083         // use onscreen touchable buttons instead.
4084         // TODO: even on "full touch" devices we may still ultimately need
4085         // a regular menu in some states.  Need UI spec.
4086         if (isTouchUiEnabled()) {
4087             return null;
4088         }
4089 
4090         // TODO: May need to revisit the wake state here if this needs to be
4091         // tweaked.
4092 
4093         // Make sure there are no pending messages to *dismiss* the menu.
4094         mHandler.removeMessages(DISMISS_MENU);
4095 
4096         if (mInCallMenu == null) {
4097             if (VDBG) log("onCreatePanelView: creating mInCallMenu (first time)...");
4098             mInCallMenu = new InCallMenu(this);
4099             mInCallMenu.initMenu();
4100         }
4101 
4102         boolean okToShowMenu = mInCallMenu.updateItems(mPhone);
4103         return okToShowMenu ? mInCallMenu.getView() : null;
4104     }
4105 
4106     /**
4107      * Dismisses the menu panel (see onCreatePanelView().)
4108      *
4109      * @param dismissImmediate If true, hide the panel immediately.
4110      *            If false, leave the menu visible onscreen for
4111      *            a brief interval before dismissing it (so the
4112      *            user can see the state change resulting from
4113      *            his original click.)
4114      */
dismissMenu(boolean dismissImmediate)4115     /* package */ void dismissMenu(boolean dismissImmediate) {
4116         if (VDBG) log("dismissMenu(immediate = " + dismissImmediate + ")...");
4117 
4118         if (dismissImmediate) {
4119             closeOptionsMenu();
4120         } else {
4121             mHandler.removeMessages(DISMISS_MENU);
4122             mHandler.sendEmptyMessageDelayed(DISMISS_MENU, MENU_DISMISS_DELAY);
4123             // This will result in a dismissMenu(true) call shortly.
4124         }
4125     }
4126 
4127     /**
4128      * Override onPanelClosed() to capture the panel closing event,
4129      * allowing us to set the poke lock correctly whenever the option
4130      * menu panel goes away.
4131      */
4132     @Override
onPanelClosed(int featureId, Menu menu)4133     public void onPanelClosed(int featureId, Menu menu) {
4134         if (VDBG) log("onPanelClosed(featureId = " + featureId + ")...");
4135 
4136         // We only want this special behavior for the "options panel"
4137         // feature (i.e. the standard menu triggered by the MENU button.)
4138         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
4139             // TODO: May need to return to the original wake state here
4140             // if onCreatePanelView ends up changing the wake state.
4141         }
4142 
4143         super.onPanelClosed(featureId, menu);
4144     }
4145 
4146     //
4147     // Bluetooth helper methods.
4148     //
4149     // - BluetoothAdapter is the Bluetooth system service.  If
4150     //   getDefaultAdapter() returns null
4151     //   then the device is not BT capable.  Use BluetoothDevice.isEnabled()
4152     //   to see if BT is enabled on the device.
4153     //
4154     // - BluetoothHeadset is the API for the control connection to a
4155     //   Bluetooth Headset.  This lets you completely connect/disconnect a
4156     //   headset (which we don't do from the Phone UI!) but also lets you
4157     //   get the address of the currently active headset and see whether
4158     //   it's currently connected.
4159     //
4160     // - BluetoothHandsfree is the API to control the audio connection to
4161     //   a bluetooth headset. We use this API to switch the headset on and
4162     //   off when the user presses the "Bluetooth" button.
4163     //   Our BluetoothHandsfree instance (mBluetoothHandsfree) is created
4164     //   by the PhoneApp and will be null if the device is not BT capable.
4165     //
4166 
4167     /**
4168      * @return true if the Bluetooth on/off switch in the UI should be
4169      *         available to the user (i.e. if the device is BT-capable
4170      *         and a headset is connected.)
4171      */
isBluetoothAvailable()4172     /* package */ boolean isBluetoothAvailable() {
4173         if (VDBG) log("isBluetoothAvailable()...");
4174         if (mBluetoothHandsfree == null) {
4175             // Device is not BT capable.
4176             if (VDBG) log("  ==> FALSE (not BT capable)");
4177             return false;
4178         }
4179 
4180         // There's no need to ask the Bluetooth system service if BT is enabled:
4181         //
4182         //    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
4183         //    if ((adapter == null) || !adapter.isEnabled()) {
4184         //        if (DBG) log("  ==> FALSE (BT not enabled)");
4185         //        return false;
4186         //    }
4187         //    if (DBG) log("  - BT enabled!  device name " + adapter.getName()
4188         //                 + ", address " + adapter.getAddress());
4189         //
4190         // ...since we already have a BluetoothHeadset instance.  We can just
4191         // call isConnected() on that, and assume it'll be false if BT isn't
4192         // enabled at all.
4193 
4194         // Check if there's a connected headset, using the BluetoothHeadset API.
4195         boolean isConnected = false;
4196         if (mBluetoothHeadset != null) {
4197             if (VDBG) log("  - headset state = " + mBluetoothHeadset.getState());
4198             BluetoothDevice headset = mBluetoothHeadset.getCurrentHeadset();
4199             if (VDBG) log("  - headset address: " + headset);
4200             if (headset != null) {
4201                 isConnected = mBluetoothHeadset.isConnected(headset);
4202                 if (VDBG) log("  - isConnected: " + isConnected);
4203             }
4204         }
4205 
4206         if (VDBG) log("  ==> " + isConnected);
4207         return isConnected;
4208     }
4209 
4210     /**
4211      * @return true if a BT device is available, and its audio is currently connected.
4212      */
isBluetoothAudioConnected()4213     /* package */ boolean isBluetoothAudioConnected() {
4214         if (mBluetoothHandsfree == null) {
4215             if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHandsfree)");
4216             return false;
4217         }
4218         boolean isAudioOn = mBluetoothHandsfree.isAudioOn();
4219         if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
4220         return isAudioOn;
4221     }
4222 
4223     /**
4224      * Helper method used to control the state of the green LED in the
4225      * "Bluetooth" menu item.
4226      *
4227      * @return true if a BT device is available and its audio is currently connected,
4228      *              <b>or</b> if we issued a BluetoothHandsfree.userWantsAudioOn()
4229      *              call within the last 5 seconds (which presumably means
4230      *              that the BT audio connection is currently being set
4231      *              up, and will be connected soon.)
4232      */
isBluetoothAudioConnectedOrPending()4233     /* package */ boolean isBluetoothAudioConnectedOrPending() {
4234         if (isBluetoothAudioConnected()) {
4235             if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
4236             return true;
4237         }
4238 
4239         // If we issued a userWantsAudioOn() call "recently enough", even
4240         // if BT isn't actually connected yet, let's still pretend BT is
4241         // on.  This is how we make the green LED in the menu item turn on
4242         // right away.
4243         if (mBluetoothConnectionPending) {
4244             long timeSinceRequest =
4245                     SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
4246             if (timeSinceRequest < 5000 /* 5 seconds */) {
4247                 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
4248                              + timeSinceRequest + " msec ago)");
4249                 return true;
4250             } else {
4251                 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
4252                              + timeSinceRequest + " msec ago)");
4253                 mBluetoothConnectionPending = false;
4254                 return false;
4255             }
4256         }
4257 
4258         if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
4259         return false;
4260     }
4261 
4262     /**
4263      * Posts a message to our handler saying to update the onscreen UI
4264      * based on a bluetooth headset state change.
4265      */
requestUpdateBluetoothIndication()4266     /* package */ void requestUpdateBluetoothIndication() {
4267         if (VDBG) log("requestUpdateBluetoothIndication()...");
4268         // No need to look at the current state here; any UI elements that
4269         // care about the bluetooth state (i.e. the CallCard) get
4270         // the necessary state directly from PhoneApp.showBluetoothIndication().
4271         mHandler.removeMessages(REQUEST_UPDATE_BLUETOOTH_INDICATION);
4272         mHandler.sendEmptyMessage(REQUEST_UPDATE_BLUETOOTH_INDICATION);
4273     }
4274 
dumpBluetoothState()4275     private void dumpBluetoothState() {
4276         log("============== dumpBluetoothState() =============");
4277         log("= isBluetoothAvailable: " + isBluetoothAvailable());
4278         log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
4279         log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
4280         log("= PhoneApp.showBluetoothIndication: "
4281             + PhoneApp.getInstance().showBluetoothIndication());
4282         log("=");
4283         if (mBluetoothHandsfree != null) {
4284             log("= BluetoothHandsfree.isAudioOn: " + mBluetoothHandsfree.isAudioOn());
4285             if (mBluetoothHeadset != null) {
4286                 BluetoothDevice headset = mBluetoothHeadset.getCurrentHeadset();
4287                 log("= BluetoothHeadset.getCurrentHeadset: " + headset);
4288                 if (headset != null) {
4289                     log("= BluetoothHeadset.isConnected: "
4290                         + mBluetoothHeadset.isConnected(headset));
4291                 }
4292             } else {
4293                 log("= mBluetoothHeadset is null");
4294             }
4295         } else {
4296             log("= mBluetoothHandsfree is null; device is not BT capable");
4297         }
4298     }
4299 
connectBluetoothAudio()4300     /* package */ void connectBluetoothAudio() {
4301         if (VDBG) log("connectBluetoothAudio()...");
4302         if (mBluetoothHandsfree != null) {
4303             mBluetoothHandsfree.userWantsAudioOn();
4304         }
4305 
4306         // Watch out: The bluetooth connection doesn't happen instantly;
4307         // the userWantsAudioOn() call returns instantly but does its real
4308         // work in another thread.  Also, in practice the BT connection
4309         // takes longer than MENU_DISMISS_DELAY to complete(!) so we need
4310         // a little trickery here to make the menu item's green LED update
4311         // instantly.
4312         // (See isBluetoothAudioConnectedOrPending() above.)
4313         mBluetoothConnectionPending = true;
4314         mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
4315     }
4316 
disconnectBluetoothAudio()4317     /* package */ void disconnectBluetoothAudio() {
4318         if (VDBG) log("disconnectBluetoothAudio()...");
4319         if (mBluetoothHandsfree != null) {
4320             mBluetoothHandsfree.userWantsAudioOff();
4321         }
4322         mBluetoothConnectionPending = false;
4323     }
4324 
4325     //
4326     // "Touch lock" UI.
4327     //
4328     // When the DTMF dialpad is up, after a certain amount of idle time we
4329     // display an overlay graphic on top of the dialpad and "lock" the
4330     // touch UI.  (UI Rationale: We need *some* sort of screen lock, with
4331     // a fairly short timeout, to avoid false touches from the user's face
4332     // while in-call.  But we *don't* want to do this by turning off the
4333     // screen completely, since that's confusing (the user can't tell
4334     // what's going on) *and* it's fairly cumbersome to have to hit MENU
4335     // to bring the screen back, rather than using some gesture on the
4336     // touch screen.)
4337     //
4338     // The user can dismiss the touch lock overlay by double-tapping on
4339     // the central "lock" icon.  Also, the touch lock overlay will go away
4340     // by itself if the DTMF dialpad is dismissed for any reason, such as
4341     // the current call getting disconnected (see onDialerClose()).
4342     //
4343     // This entire feature is disabled on devices which use a proximity
4344     // sensor to turn the screen off while in-call.
4345     //
4346 
4347     /**
4348      * Initializes the "touch lock" UI widgets.  We do this lazily
4349      * to avoid slowing down the initial launch of the InCallScreen.
4350      */
initTouchLock()4351     private void initTouchLock() {
4352         if (VDBG) log("initTouchLock()...");
4353         if (mTouchLockOverlay != null) {
4354             Log.w(LOG_TAG, "initTouchLock: already initialized!");
4355             return;
4356         }
4357 
4358         if (!mUseTouchLockOverlay) {
4359             Log.w(LOG_TAG, "initTouchLock: touch lock isn't used on this device!");
4360             return;
4361         }
4362 
4363         mTouchLockOverlay = (View) findViewById(R.id.touchLockOverlay);
4364         // Note mTouchLockOverlay's visibility is initially GONE.
4365         mTouchLockIcon = (View) findViewById(R.id.touchLockIcon);
4366 
4367         // Handle touch events.  (Basically mTouchLockOverlay consumes and
4368         // discards any touch events it sees, and mTouchLockIcon listens
4369         // for the "double-tap to unlock" gesture.)
4370         mTouchLockOverlay.setOnTouchListener(this);
4371         mTouchLockIcon.setOnTouchListener(this);
4372 
4373         mTouchLockFadeIn = AnimationUtils.loadAnimation(this, R.anim.touch_lock_fade_in);
4374     }
4375 
isTouchLocked()4376     private boolean isTouchLocked() {
4377         return mUseTouchLockOverlay
4378                 && (mTouchLockOverlay != null)
4379                 && (mTouchLockOverlay.getVisibility() == View.VISIBLE);
4380     }
4381 
4382     /**
4383      * Enables or disables the "touch lock" overlay on top of the DTMF dialpad.
4384      *
4385      * If enable=true, bring up the overlay immediately using an animated
4386      * fade-in effect.  (Or do nothing if the overlay isn't appropriate
4387      * right now, like if the dialpad isn't up, or the speaker is on.)
4388      *
4389      * If enable=false, immediately take down the overlay.  (Or do nothing
4390      * if the overlay isn't actually up right now.)
4391      *
4392      * Note that with enable=false this method will *not* automatically
4393      * start the touch lock timer.  (So when taking down the overlay while
4394      * the dialer is still up, the caller is also responsible for calling
4395      * resetTouchLockTimer(), to make sure the overlay will get
4396      * (re-)enabled later.)
4397      *
4398      */
enableTouchLock(boolean enable)4399     private void enableTouchLock(boolean enable) {
4400         if (VDBG) log("enableTouchLock(" + enable + ")...");
4401         if (enable) {
4402             // We shouldn't have even gotten here if we don't use the
4403             // touch lock overlay feature at all on this device.
4404             if (!mUseTouchLockOverlay) {
4405                 Log.w(LOG_TAG, "enableTouchLock: touch lock isn't used on this device!");
4406                 return;
4407             }
4408 
4409             // The "touch lock" overlay is only ever used on top of the
4410             // DTMF dialpad.
4411             if (!mDialer.isOpened()) {
4412                 if (VDBG) log("enableTouchLock: dialpad isn't up, no need to lock screen.");
4413                 return;
4414             }
4415 
4416             // Also, the "touch lock" overlay NEVER appears if the speaker is in use.
4417             if (PhoneUtils.isSpeakerOn(this)) {
4418                 if (VDBG) log("enableTouchLock: speaker is on, no need to lock screen.");
4419                 return;
4420             }
4421 
4422             // Initialize the UI elements if necessary.
4423             if (mTouchLockOverlay == null) {
4424                 initTouchLock();
4425             }
4426 
4427             // First take down the menu if it's up (since it's confusing
4428             // to see a touchable menu *above* the touch lock overlay.)
4429             // Note dismissMenu() has no effect if the menu is already closed.
4430             dismissMenu(true);  // dismissImmediate = true
4431 
4432             // Bring up the touch lock overlay (with an animated fade)
4433             mTouchLockOverlay.setVisibility(View.VISIBLE);
4434             mTouchLockOverlay.startAnimation(mTouchLockFadeIn);
4435         } else {
4436             // TODO: it might be nice to immediately kill the animation if
4437             // we're in the middle of fading-in:
4438             //   if (mTouchLockFadeIn.hasStarted() && !mTouchLockFadeIn.hasEnded()) {
4439             //      mTouchLockOverlay.clearAnimation();
4440             //   }
4441             // but the fade-in is so quick that this probably isn't necessary.
4442 
4443             // Take down the touch lock overlay (immediately)
4444             if (mTouchLockOverlay != null) mTouchLockOverlay.setVisibility(View.GONE);
4445         }
4446     }
4447 
4448     /**
4449      * Schedule the "touch lock" overlay to begin fading in after a short
4450      * delay, but only if the DTMF dialpad is currently visible.
4451      *
4452      * (This is designed to be triggered on any user activity
4453      * while the dialpad is up but not locked, and also
4454      * whenever the user "unlocks" the touch lock overlay.)
4455      *
4456      * Calling this method supersedes any previous resetTouchLockTimer()
4457      * calls (i.e. we first clear any pending TOUCH_LOCK_TIMER messages.)
4458      */
resetTouchLockTimer()4459     private void resetTouchLockTimer() {
4460         if (VDBG) log("resetTouchLockTimer()...");
4461 
4462         // This is a no-op if this device doesn't use the touch lock
4463         // overlay feature at all.
4464         if (!mUseTouchLockOverlay) return;
4465 
4466         mHandler.removeMessages(TOUCH_LOCK_TIMER);
4467         if (mDialer.isOpened() && !isTouchLocked()) {
4468             // The touch lock delay value comes from Gservices; we use
4469             // the same value that's used for the PowerManager's
4470             // POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest possible
4471             // screen timeout behavior.)
4472 
4473             // Do a fresh lookup each time, since Gservices values can
4474             // change on the fly.  (The Settings.Gservices helper class
4475             // caches these values so this call is usually cheap.)
4476             int touchLockDelay = Settings.Gservices.getInt(
4477                     getContentResolver(),
4478                     Settings.Gservices.SHORT_KEYLIGHT_DELAY_MS,
4479                     TOUCH_LOCK_DELAY_DEFAULT);
4480             mHandler.sendEmptyMessageDelayed(TOUCH_LOCK_TIMER, touchLockDelay);
4481         }
4482     }
4483 
4484     /**
4485      * Handles the TOUCH_LOCK_TIMER event.
4486      * @see resetTouchLockTimer
4487      */
touchLockTimerExpired()4488     private void touchLockTimerExpired() {
4489         // Ok, it's been long enough since we had any user activity with
4490         // the DTMF dialpad up.  If the dialpad is still up, start fading
4491         // in the "touch lock" overlay.
4492         enableTouchLock(true);
4493     }
4494 
4495     // View.OnTouchListener implementation
onTouch(View v, MotionEvent event)4496     public boolean onTouch(View v, MotionEvent event) {
4497         if (VDBG) log ("onTouch(View " + v + ")...");
4498 
4499         // Handle touch events on the "touch lock" overlay.
4500         if ((v == mTouchLockIcon) || (v == mTouchLockOverlay)) {
4501 
4502             // TODO: move this big hunk of code to a helper function, or
4503             // even better out to a separate helper class containing all
4504             // the touch lock overlay code.
4505 
4506             // We only care about these touches while the touch lock UI is
4507             // visible (including the time during the fade-in animation.)
4508             if (!isTouchLocked()) {
4509                 // Got an event from the touch lock UI, but we're not locked!
4510                 // (This was probably a touch-UP right after we unlocked.
4511                 // Ignore it.)
4512                 return false;
4513             }
4514 
4515             // (v == mTouchLockIcon) means the user hit the lock icon in the
4516             // middle of the screen, and (v == mTouchLockOverlay) is a touch
4517             // anywhere else on the overlay.
4518 
4519             if (v == mTouchLockIcon) {
4520                 // Direct hit on the "lock" icon.  Handle the double-tap gesture.
4521                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
4522                     long now = SystemClock.uptimeMillis();
4523                     if (VDBG) log("- touch lock icon: handling a DOWN event, t = " + now);
4524 
4525                     // Look for the double-tap gesture:
4526                     if (now < mTouchLockLastTouchTime + ViewConfiguration.getDoubleTapTimeout()) {
4527                         if (VDBG) log("==> touch lock icon: DOUBLE-TAP!");
4528                         // This was the 2nd tap of a double-tap gesture.
4529                         // Take down the touch lock overlay, but post a
4530                         // message in the future to bring it back later.
4531                         enableTouchLock(false);
4532                         resetTouchLockTimer();
4533                         // This counts as explicit "user activity".
4534                         PhoneApp.getInstance().pokeUserActivity();
4535                     }
4536                 } else if (event.getAction() == MotionEvent.ACTION_UP) {
4537                     // Stash away the current time in case this is the first
4538                     // tap of a double-tap gesture.  (We measure the time from
4539                     // the first tap's UP to the second tap's DOWN.)
4540                     mTouchLockLastTouchTime = SystemClock.uptimeMillis();
4541                 }
4542 
4543                 // And regardless of what just happened, we *always* consume
4544                 // touch events while the touch lock UI is (or was) visible.
4545                 return true;
4546 
4547             } else {  // (v == mTouchLockOverlay)
4548                 // User touched the "background" area of the touch lock overlay.
4549 
4550                 // TODO: If we're in the middle of the fade-in animation,
4551                 // consider making a touch *anywhere* immediately unlock the
4552                 // UI.  This could be risky, though, if the user tries to
4553                 // *double-tap* during the fade-in (in which case the 2nd tap
4554                 // might 't become a false touch on the dialpad!)
4555                 //
4556                 //if (event.getAction() == MotionEvent.ACTION_DOWN) {
4557                 //    if (DBG) log("- touch lock overlay background: handling a DOWN event.");
4558                 //
4559                 //    if (mTouchLockFadeIn.hasStarted() && !mTouchLockFadeIn.hasEnded()) {
4560                 //        // If we're still fading-in, a touch *anywhere* onscreen
4561                 //        // immediately unlocks.
4562                 //        if (DBG) log("==> touch lock: tap during fade-in!");
4563                 //
4564                 //        mTouchLockOverlay.clearAnimation();
4565                 //        enableTouchLock(false);
4566                 //        // ...but post a message in the future to bring it
4567                 //        // back later.
4568                 //        resetTouchLockTimer();
4569                 //    }
4570                 //}
4571 
4572                 // And regardless of what just happened, we *always* consume
4573                 // touch events while the touch lock UI is (or was) visible.
4574                 return true;
4575             }
4576         } else {
4577             Log.w(LOG_TAG, "onTouch: event from unexpected View: " + v);
4578             return false;
4579         }
4580     }
4581 
4582     // Any user activity while the dialpad is up, but not locked, should
4583     // reset the touch lock timer back to the full delay amount.
4584     @Override
onUserInteraction()4585     public void onUserInteraction() {
4586         if (mDialer.isOpened() && !isTouchLocked()) {
4587             resetTouchLockTimer();
4588         }
4589     }
4590 
4591     /**
4592      * Posts a handler message telling the InCallScreen to close
4593      * the OTA failure notice after the specified delay.
4594      * @see OtaUtils.otaShowProgramFailureNotice
4595      */
requestCloseOtaFailureNotice(long timeout)4596     /* package */ void requestCloseOtaFailureNotice(long timeout) {
4597         if (DBG) log("requestCloseOtaFailureNotice() with timeout: " + timeout);
4598         mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_OTA_FAILURE_NOTICE, timeout);
4599 
4600         // TODO: we probably ought to call removeMessages() for this
4601         // message code in either onPause or onResume, just to be 100%
4602         // sure that the message we just posted has no way to affect a
4603         // *different* call if the user quickly backs out and restarts.
4604         // (This is also true for requestCloseSpcErrorNotice() below, and
4605         // probably anywhere else we use mHandler.sendEmptyMessageDelayed().)
4606     }
4607 
4608     /**
4609      * Posts a handler message telling the InCallScreen to close
4610      * the SPC error notice after the specified delay.
4611      * @see OtaUtils.otaShowSpcErrorNotice
4612      */
requestCloseSpcErrorNotice(long timeout)4613     /* package */ void requestCloseSpcErrorNotice(long timeout) {
4614         if (DBG) log("requestCloseSpcErrorNotice() with timeout: " + timeout);
4615         mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_SPC_ERROR_NOTICE, timeout);
4616     }
4617 
isOtaCallInActiveState()4618     public boolean isOtaCallInActiveState() {
4619         final PhoneApp app = PhoneApp.getInstance();
4620         if ((mInCallScreenMode == InCallScreenMode.OTA_NORMAL)
4621                 || ((app.cdmaOtaScreenState != null)
4622                     && (app.cdmaOtaScreenState.otaScreenState ==
4623                         CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
4624             return true;
4625         } else {
4626             return false;
4627         }
4628     }
4629 
4630     /**
4631      * Handle OTA Call End scenario when display becomes dark during OTA Call
4632      * and InCallScreen is in pause mode.  CallNotifier will listen for call
4633      * end indication and call this api to handle OTA Call end scenario
4634      */
handleOtaCallEnd()4635     public void handleOtaCallEnd() {
4636         final PhoneApp app = PhoneApp.getInstance();
4637 
4638         if (DBG) log("handleOtaCallEnd entering");
4639         if (((mInCallScreenMode == InCallScreenMode.OTA_NORMAL)
4640                 || ((app.cdmaOtaScreenState != null)
4641                 && (app.cdmaOtaScreenState.otaScreenState !=
4642                     CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))
4643                 && ((app.cdmaOtaProvisionData != null)
4644                 && (!app.cdmaOtaProvisionData.inOtaSpcState))) {
4645             if (DBG) log("handleOtaCallEnd - Set OTA Call End stater");
4646             setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4647             updateScreen();
4648         }
4649     }
4650 
isOtaCallInEndState()4651     public boolean isOtaCallInEndState() {
4652         return (mInCallScreenMode == InCallScreenMode.OTA_ENDED);
4653     }
4654 
4655    /**
4656     * This function returns true if the current call is OTA Call.
4657     * It uses intent action and OTA Screen state information to determine
4658     * if current call is OTA call or not
4659     */
checkIsOtaCall(Intent intent)4660     private boolean checkIsOtaCall(Intent intent) {
4661         if (DBG) log("checkIsOtaCall entering");
4662 
4663         if (intent == null || intent.getAction() == null) {
4664             return false;
4665         }
4666 
4667         final PhoneApp app = PhoneApp.getInstance();
4668 
4669         if ((app.cdmaOtaScreenState == null)
4670                 || (app.cdmaOtaProvisionData == null)) {
4671             if (DBG) log("checkIsOtaCall OtaUtils.CdmaOtaScreenState not initialized");
4672             return false;
4673         }
4674 
4675         String action = intent.getAction();
4676         boolean isOtaCall = false;
4677         if (action.equals(ACTION_SHOW_ACTIVATION)) {
4678             if (DBG) log("checkIsOtaCall action = ACTION_SHOW_ACTIVATION");
4679             if (!app.cdmaOtaProvisionData.isOtaCallIntentProcessed) {
4680                 if (DBG) log("checkIsOtaCall: ACTION_SHOW_ACTIVATION is not handled before");
4681                 app.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
4682                 app.cdmaOtaScreenState.otaScreenState =
4683                         CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
4684             }
4685             isOtaCall = true;
4686         } else if (action.equals(Intent.ACTION_CALL)
4687                 || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
4688             String number;
4689             try {
4690                 number = getInitialNumber(intent);
4691             } catch (PhoneUtils.VoiceMailNumberMissingException ex) {
4692                 if (DBG) log("Error retrieving number using the api getInitialNumber()");
4693                 return false;
4694             }
4695             if (mPhone.isOtaSpNumber(number)) {
4696                 if (DBG) log("checkIsOtaCall action ACTION_CALL, it is valid OTA number");
4697                 isOtaCall = true;
4698             }
4699         } else if (action.equals(intent.ACTION_MAIN)) {
4700             if (DBG) log("checkIsOtaCall action ACTION_MAIN");
4701             boolean isRingingCall = !mRingingCall.isIdle();
4702             if (isRingingCall) {
4703                 if (DBG) log("checkIsOtaCall isRingingCall: " + isRingingCall);
4704                 return false;
4705             } else if ((app.cdmaOtaInCallScreenUiState.state
4706                             == CdmaOtaInCallScreenUiState.State.NORMAL)
4707                     || (app.cdmaOtaInCallScreenUiState.state
4708                             == CdmaOtaInCallScreenUiState.State.ENDED)) {
4709                 if (DBG) log("checkIsOtaCall action ACTION_MAIN, OTA call already in progress");
4710                 isOtaCall = true;
4711             } else {
4712                 if (app.cdmaOtaScreenState.otaScreenState !=
4713                         CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED) {
4714                     if (DBG) log("checkIsOtaCall action ACTION_MAIN, "
4715                                  + "OTA call in progress with UNDEFINED");
4716                     isOtaCall = true;
4717                 }
4718             }
4719         }
4720         if (DBG) log("checkIsOtaCall valid =" + isOtaCall);
4721         if (isOtaCall && (otaUtils == null)) {
4722             if (DBG) log("checkIsOtaCall create OtaUtils");
4723             otaUtils = new OtaUtils(getApplicationContext(),
4724                                         this, mInCallPanel, mCallCard, mDialer);
4725         }
4726         return isOtaCall;
4727     }
4728 
4729     /**
4730      * Initialize the OTA State and UI.
4731      *
4732      * On Resume, this function is called to check if current call is
4733      * OTA Call and if it is OTA Call, create OtaUtil object and set
4734      * InCallScreenMode to OTA Call mode (OTA_NORMAL or OTA_ENDED).
4735      * As part of initialization, OTA Call Card is inflated.
4736      * OtaUtil object provides utility apis that InCallScreen calls for OTA Call UI
4737      * rendering, handling of touck/key events on OTA Screens and handling of
4738      * Framework events that result in OTA State change
4739      */
initOtaState()4740     private void initOtaState() {
4741         if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
4742             final PhoneApp app = PhoneApp.getInstance();
4743 
4744             if ((app.cdmaOtaScreenState == null) || (app.cdmaOtaProvisionData == null)) {
4745                 if (DBG) log("initOtaState func - All CdmaOTA utility classes not initialized");
4746                 return;
4747             }
4748 
4749             if (checkIsOtaCall(getIntent())) {
4750                 OtaUtils.CdmaOtaInCallScreenUiState.State cdmaOtaInCallScreenState =
4751                         otaUtils.getCdmaOtaInCallScreenUiState();
4752                 if (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL) {
4753                     if (DBG) log("initOtaState - in OTA Normal mode");
4754                     setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
4755                 } else if (cdmaOtaInCallScreenState ==
4756                                 OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED) {
4757                     if (DBG) log("initOtaState - in OTA END mode");
4758                     setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4759                 } else if (app.cdmaOtaScreenState.otaScreenState ==
4760                                 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG) {
4761                     if (DBG) log("initOtaState - set OTA END Mode");
4762                     setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4763                 } else {
4764                     if (DBG) log("initOtaState - Set OTA NORMAL Mode");
4765                     setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
4766                 }
4767             } else {
4768                 if (otaUtils != null) {
4769                     otaUtils.cleanOtaScreen(false);
4770                 }
4771             }
4772         }
4773     }
4774 
updateMenuItems()4775     public void updateMenuItems() {
4776         if (mInCallMenu != null) {
4777             boolean okToShowMenu =  mInCallMenu.updateItems(PhoneApp.getInstance().phone);
4778             if (!okToShowMenu) {
4779                 dismissMenu(true);
4780             }
4781         }
4782     }
4783 
4784     /**
4785      * Updates and returns the InCallControlState instance.
4786      */
getUpdatedInCallControlState()4787     public InCallControlState getUpdatedInCallControlState() {
4788         mInCallControlState.update();
4789         return mInCallControlState;
4790     }
4791 
4792     /**
4793      * Updates the background of the InCallScreen to indicate the state of
4794      * the current call(s).
4795      */
updateInCallBackground()4796     private void updateInCallBackground() {
4797         final boolean hasRingingCall = !mRingingCall.isIdle();
4798         final boolean hasActiveCall = !mForegroundCall.isIdle();
4799         final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
4800         final PhoneApp app = PhoneApp.getInstance();
4801         final boolean bluetoothActive = app.showBluetoothIndication();
4802 
4803         int backgroundResId = R.drawable.bg_in_call_gradient_unidentified;
4804 
4805         // Possible states of the background are:
4806         // - bg_in_call_gradient_bluetooth.9.png     // blue
4807         // - bg_in_call_gradient_connected.9.png     // green
4808         // - bg_in_call_gradient_ended.9.png         // red
4809         // - bg_in_call_gradient_on_hold.9.png       // orange
4810         // - bg_in_call_gradient_unidentified.9.png  // gray
4811 
4812         if (hasRingingCall) {
4813             // There's an INCOMING (or WAITING) call.
4814             if (bluetoothActive) {
4815                 backgroundResId = R.drawable.bg_in_call_gradient_bluetooth;
4816             } else {
4817                 backgroundResId = R.drawable.bg_in_call_gradient_unidentified;
4818             }
4819         } else if (hasHoldingCall && !hasActiveCall) {
4820             // No foreground call, but there is a call on hold.
4821             backgroundResId = R.drawable.bg_in_call_gradient_on_hold;
4822         } else {
4823             // In all cases other than "ringing" and "on hold", the state
4824             // of the foreground call determines the background.
4825             final Call.State fgState = mForegroundCall.getState();
4826             switch (fgState) {
4827                 case ACTIVE:
4828                 case DISCONNECTING:  // Call will disconnect soon, but keep showing
4829                                      // the normal "connected" background for now.
4830                     if (bluetoothActive) {
4831                         backgroundResId = R.drawable.bg_in_call_gradient_bluetooth;
4832                     } else {
4833                         backgroundResId = R.drawable.bg_in_call_gradient_connected;
4834                     }
4835                     break;
4836 
4837                 case DISCONNECTED:
4838                     backgroundResId = R.drawable.bg_in_call_gradient_ended;
4839                     break;
4840 
4841                 case DIALING:
4842                 case ALERTING:
4843                     if (bluetoothActive) {
4844                         backgroundResId = R.drawable.bg_in_call_gradient_bluetooth;
4845                     } else {
4846                         backgroundResId = R.drawable.bg_in_call_gradient_unidentified;
4847                     }
4848                     break;
4849 
4850                 default:
4851                     // Foreground call is (presumably) IDLE.
4852                     // We're not usually here at all in this state, but
4853                     // this *does* happen in some unusual cases (like
4854                     // while displaying an MMI result).
4855                     // Use the most generic background.
4856                     backgroundResId = R.drawable.bg_in_call_gradient_unidentified;
4857                     break;
4858             }
4859         }
4860         mMainFrame.setBackgroundResource(backgroundResId);
4861     }
4862 
resetInCallScreenMode()4863     public void resetInCallScreenMode() {
4864         if (DBG) log("resetInCallScreenMode - InCallScreenMode set to UNDEFINED");
4865         setInCallScreenMode(InCallScreenMode.UNDEFINED);
4866     }
4867 
4868     /**
4869      * Clear all the fields related to the provider support.
4870      */
clearProvider()4871     private void clearProvider() {
4872         mProviderOverlayVisible = false;
4873         mProviderLabel = null;
4874         mProviderIcon = null;
4875         mProviderGatewayUri = null;
4876         mProviderAddress = null;
4877     }
4878 
4879     /**
4880      * Updates the onscreen hint displayed while the user is dragging one
4881      * of the handles of the RotarySelector widget used for incoming
4882      * calls.
4883      *
4884      * @param hintTextResId resource ID of the hint text to display,
4885      *        or 0 if no hint should be visible.
4886      * @param hintColorResId resource ID for the color of the hint text
4887      */
updateSlidingTabHint(int hintTextResId, int hintColorResId)4888     /* package */ void updateSlidingTabHint(int hintTextResId, int hintColorResId) {
4889         if (VDBG) log("updateRotarySelectorHint(" + hintTextResId + ")...");
4890         if (mCallCard != null) {
4891             mCallCard.setRotarySelectorHint(hintTextResId, hintColorResId);
4892             mCallCard.updateState(mPhone);
4893             // TODO: if hintTextResId == 0, consider NOT clearing the onscreen
4894             // hint right away, but instead post a delayed handler message to
4895             // keep it onscreen for an extra second or two.  (This might make
4896             // the hint more helpful if the user quickly taps one of the
4897             // handles without dragging at all...)
4898             // (Or, maybe this should happen completely within the RotarySelector
4899             // widget, since the widget itself probably wants to keep the colored
4900             // arrow visible for some extra time also...)
4901         }
4902     }
4903 
log(String msg)4904     private void log(String msg) {
4905         Log.d(LOG_TAG, msg);
4906     }
4907 }
4908