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