Remove unused old UI code
Remove unused UI code copied over from apps/Phone that is no longer used.
Bug:10608890
Change-Id: I41a0bd01102df70aa9a28bfa24994eca05e3fc0c
diff --git a/src/com/android/phone/InCallScreen.java b/src/com/android/phone/InCallScreen.java
index 53cd40d..b18cc43 100644
--- a/src/com/android/phone/InCallScreen.java
+++ b/src/com/android/phone/InCallScreen.java
@@ -16,101 +16,7 @@
package com.android.phone;
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothHeadsetPhone;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.media.AudioManager;
-import android.os.AsyncResult;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.telephony.ServiceState;
-import android.text.TextUtils;
-import android.text.method.DialerKeyListener;
-import android.util.EventLog;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.CallManager;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.MmiCode;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyCapabilities;
-import com.android.phone.Constants.CallStatusCode;
-import com.android.phone.InCallUiState.InCallScreenMode;
-import com.android.phone.OtaUtils.CdmaOtaScreenState;
-
-import java.util.List;
-
-
-/**
- * Phone app "in call" screen.
- */
-public class InCallScreen extends Activity
- implements View.OnClickListener {
- private static final String LOG_TAG = "InCallScreen";
-
- private static final boolean DBG =
- (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
- private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
-
- /**
- * Intent extra used to specify whether the DTMF dialpad should be
- * initially visible when bringing up the InCallScreen. (If this
- * extra is present, the dialpad will be initially shown if the extra
- * has the boolean value true, and initially hidden otherwise.)
- */
- // TODO: Should be EXTRA_SHOW_DIALPAD for consistency.
- static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
-
- // Amount of time (in msec) that we display the "Call ended" state.
- // The "short" value is for calls ended by the local user, and the
- // "long" value is for calls ended by the remote caller.
- private static final int CALL_ENDED_SHORT_DELAY = 200; // msec
- private static final int CALL_ENDED_LONG_DELAY = 2000; // msec
- private static final int CALL_ENDED_EXTRA_LONG_DELAY = 5000; // msec
-
- // Amount of time that we display the PAUSE alert Dialog showing the
- // post dial string yet to be send out to the n/w
- private static final int PAUSE_PROMPT_DIALOG_TIMEOUT = 2000; //msec
-
- // Amount of time that we display the provider info if applicable.
- private static final int PROVIDER_INFO_TIMEOUT = 5000; // msec
-
+public class InCallScreen {
// These are values for the settings of the auto retry mode:
// 0 = disabled
// 1 = enabled
@@ -118,4071 +24,4 @@
// they should be moved to Settings where the value is being looked up in the first place
static final int AUTO_RETRY_OFF = 0;
static final int AUTO_RETRY_ON = 1;
-
- // Message codes; see mHandler below.
- // Note message codes < 100 are reserved for the PhoneApp.
- private static final int PHONE_STATE_CHANGED = 101;
- private static final int PHONE_DISCONNECT = 102;
- private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
- private static final int POST_ON_DIAL_CHARS = 104;
- private static final int WILD_PROMPT_CHAR_ENTERED = 105;
- private static final int ADD_VOICEMAIL_NUMBER = 106;
- private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
- private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
- private static final int SUPP_SERVICE_FAILED = 110;
- private static final int PHONE_CDMA_CALL_WAITING = 115;
- private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
- private static final int REQUEST_CLOSE_OTA_FAILURE_NOTICE = 119;
- private static final int EVENT_PAUSE_DIALOG_COMPLETE = 120;
- private static final int EVENT_HIDE_PROVIDER_INFO = 121; // Time to remove the info.
- private static final int REQUEST_UPDATE_SCREEN = 122;
- private static final int PHONE_INCOMING_RING = 123;
- private static final int PHONE_NEW_RINGING_CONNECTION = 124;
-
- // When InCallScreenMode is UNDEFINED set the default action
- // to ACTION_UNDEFINED so if we are resumed the activity will
- // know its undefined. In particular checkIsOtaCall will return
- // false.
- public static final String ACTION_UNDEFINED = "com.android.phone.InCallScreen.UNDEFINED";
-
- /** Status codes returned from syncWithPhoneState(). */
- private enum SyncWithPhoneStateStatus {
- /**
- * Successfully updated our internal state based on the telephony state.
- */
- SUCCESS,
-
- /**
- * There was no phone state to sync with (i.e. the phone was
- * completely idle). In most cases this means that the
- * in-call UI shouldn't be visible in the first place, unless
- * we need to remain in the foreground while displaying an
- * error message.
- */
- PHONE_NOT_IN_USE
- }
-
- private boolean mRegisteredForPhoneStates;
-
- private PhoneGlobals mApp;
- private CallManager mCM;
-
- // TODO: need to clean up all remaining uses of mPhone.
- // (There may be more than one Phone instance on the device, so it's wrong
- // to just keep a single mPhone field. Instead, any time we need a Phone
- // reference we should get it dynamically from the CallManager, probably
- // based on the current foreground Call.)
- private Phone mPhone;
-
- /** Main in-call UI elements. */
- private CallCard mCallCard;
-
- // UI controls:
- private InCallControlState mInCallControlState;
- private InCallTouchUi mInCallTouchUi;
- private RespondViaSmsManager mRespondViaSmsManager; // see internalRespondViaSms()
- private ManageConferenceUtils mManageConferenceUtils;
-
- // DTMF Dialer controller and its view:
- private DTMFTwelveKeyDialer mDialer;
-
- private EditText mWildPromptText;
-
- // Various dialogs we bring up (see dismissAllDialogs()).
- // TODO: convert these all to use the "managed dialogs" framework.
- //
- // The MMI started dialog can actually be one of 2 items:
- // 1. An alert dialog if the MMI code is a normal MMI
- // 2. A progress dialog if the user requested a USSD
- private Dialog mMmiStartedDialog;
- private AlertDialog mMissingVoicemailDialog;
- private AlertDialog mGenericErrorDialog;
- private AlertDialog mSuppServiceFailureDialog;
- private AlertDialog mWaitPromptDialog;
- private AlertDialog mWildPromptDialog;
- private AlertDialog mCallLostDialog;
- private AlertDialog mPausePromptDialog;
- private AlertDialog mExitingECMDialog;
- // NOTE: if you add a new dialog here, be sure to add it to dismissAllDialogs() also.
-
- // ProgressDialog created by showProgressIndication()
- private ProgressDialog mProgressDialog;
-
- // TODO: If the Activity class ever provides an easy way to get the
- // current "activity lifecycle" state, we can remove these flags.
- private boolean mIsDestroyed = false;
- private boolean mIsForegroundActivity = false;
- private PowerManager mPowerManager;
-
- // For use with Pause/Wait dialogs
- private String mPostDialStrAfterPause;
- private boolean mPauseInProgress = false;
-
- // Info about the most-recently-disconnected Connection, which is used
- // to determine what should happen when exiting the InCallScreen after a
- // call. (This info is set by onDisconnect(), and used by
- // delayedCleanupAfterDisconnect().)
- private Connection.DisconnectCause mLastDisconnectCause;
-
- /** In-call audio routing options; see switchInCallAudio(). */
- public enum InCallAudioMode {
- SPEAKER, // Speakerphone
- BLUETOOTH, // Bluetooth headset (if available)
- EARPIECE, // Handset earpiece (or wired headset, if connected)
- }
-
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (mIsDestroyed) {
- if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
- return;
- }
- if (!mIsForegroundActivity) {
- if (DBG) log("Handler: handling message " + msg + " while not in foreground");
- // Continue anyway; some of the messages below *want* to
- // be handled even if we're not the foreground activity
- // (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
- // should at least be safe to handle if we're not in the
- // foreground...
- }
-
- switch (msg.what) {
- case SUPP_SERVICE_FAILED:
- onSuppServiceFailed((AsyncResult) msg.obj);
- break;
-
- case PHONE_STATE_CHANGED:
- onPhoneStateChanged((AsyncResult) msg.obj);
- break;
-
- case PHONE_DISCONNECT:
- onDisconnect((AsyncResult) msg.obj);
- break;
-
- case EVENT_HEADSET_PLUG_STATE_CHANGED:
- // Update the in-call UI, since some UI elements (such
- // as the "Speaker" button) may change state depending on
- // whether a headset is plugged in.
- // TODO: A full updateScreen() is overkill here, since
- // the value of PhoneApp.isHeadsetPlugged() only affects a
- // single onscreen UI element. (But even a full updateScreen()
- // is still pretty cheap, so let's keep this simple
- // for now.)
- updateScreen();
-
- // Also, force the "audio mode" popup to refresh itself if
- // it's visible, since one of its items is either "Wired
- // headset" or "Handset earpiece" depending on whether the
- // headset is plugged in or not.
- mInCallTouchUi.refreshAudioModePopup(); // safe even if the popup's not active
-
- break;
-
- // TODO: sort out MMI code (probably we should remove this method entirely).
- // See also MMI handling code in onResume()
- // case PhoneApp.MMI_INITIATE:
- // onMMIInitiate((AsyncResult) msg.obj);
- // break;
-
- case PhoneGlobals.MMI_CANCEL:
- onMMICancel();
- break;
-
- // handle the mmi complete message.
- // since the message display class has been replaced with
- // a system dialog in PhoneUtils.displayMMIComplete(), we
- // should finish the activity here to close the window.
- case PhoneGlobals.MMI_COMPLETE:
- onMMIComplete((MmiCode) ((AsyncResult) msg.obj).result);
- break;
-
- case POST_ON_DIAL_CHARS:
- handlePostOnDialChars((AsyncResult) msg.obj, (char) msg.arg1);
- break;
-
- case ADD_VOICEMAIL_NUMBER:
- addVoiceMailNumberPanel();
- break;
-
- case DONT_ADD_VOICEMAIL_NUMBER:
- dontAddVoiceMailNumber();
- break;
-
- case DELAYED_CLEANUP_AFTER_DISCONNECT:
- delayedCleanupAfterDisconnect();
- break;
-
- case PHONE_CDMA_CALL_WAITING:
- if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
- Connection cn = mCM.getFirstActiveRingingCall().getLatestConnection();
-
- // Only proceed if we get a valid connection object
- if (cn != null) {
- // Finally update screen with Call waiting info and request
- // screen to wake up
- updateScreen();
- mApp.updateWakeState();
- }
- break;
-
- case REQUEST_CLOSE_SPC_ERROR_NOTICE:
- if (mApp.otaUtils != null) {
- mApp.otaUtils.onOtaCloseSpcNotice();
- }
- break;
-
- case REQUEST_CLOSE_OTA_FAILURE_NOTICE:
- if (mApp.otaUtils != null) {
- mApp.otaUtils.onOtaCloseFailureNotice();
- }
- break;
-
- case EVENT_PAUSE_DIALOG_COMPLETE:
- if (mPausePromptDialog != null) {
- if (DBG) log("- DISMISSING mPausePromptDialog.");
- mPausePromptDialog.dismiss(); // safe even if already dismissed
- mPausePromptDialog = null;
- }
- break;
-
- case EVENT_HIDE_PROVIDER_INFO:
- if (mCallCard != null) {
- mCallCard.updateState(mCM);
- }
- break;
- case REQUEST_UPDATE_SCREEN:
- updateScreen();
- break;
-
- case PHONE_INCOMING_RING:
- onIncomingRing();
- break;
-
- case PHONE_NEW_RINGING_CONNECTION:
- onNewRingingConnection();
- break;
-
- default:
- Log.wtf(LOG_TAG, "mHandler: unexpected message: " + msg);
- break;
- }
- }
- };
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
- // Listen for ACTION_HEADSET_PLUG broadcasts so that we
- // can update the onscreen UI when the headset state changes.
- // if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
- // if (DBG) log("==> intent: " + intent);
- // if (DBG) log(" state: " + intent.getIntExtra("state", 0));
- // if (DBG) log(" name: " + intent.getStringExtra("name"));
- // send the event and add the state as an argument.
- Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
- intent.getIntExtra("state", 0), 0);
- mHandler.sendMessage(message);
- }
- }
- };
-
-
- @Override
- protected void onCreate(Bundle icicle) {
- Log.i(LOG_TAG, "onCreate()... this = " + this);
- Profiler.callScreenOnCreate();
- super.onCreate(icicle);
-
- // Make sure this is a voice-capable device.
- if (!PhoneGlobals.sVoiceCapable) {
- // There should be no way to ever reach the InCallScreen on a
- // non-voice-capable device, since this activity is not exported by
- // our manifest, and we explicitly disable any other external APIs
- // like the CALL intent and ITelephony.showCallScreen().
- // So the fact that we got here indicates a phone app bug.
- Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");
- finish();
- return;
- }
-
- mApp = PhoneGlobals.getInstance();
- mApp.setInCallScreenInstance(this);
-
- // set this flag so this activity will stay in front of the keyguard
- int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
- if (mApp.getPhoneState() == PhoneConstants.State.OFFHOOK) {
- // While we are in call, the in-call screen should dismiss the keyguard.
- // This allows the user to press Home to go directly home without going through
- // an insecure lock screen.
- // But we do not want to do this if there is no active call so we do not
- // bypass the keyguard if the call is not answered or declined.
- flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
- }
-
- WindowManager.LayoutParams lp = getWindow().getAttributes();
- lp.flags |= flags;
-
- setPhone(mApp.phone); // Sets mPhone
-
- mCM = mApp.mCM;
- log("- onCreate: phone state = " + mCM.getState());
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
-
- // Inflate everything in incall_screen.xml and add it to the screen.
- setContentView(R.layout.incall_screen);
-
- // If in landscape, then one of the ViewStubs (instead of <include>) is used for the
- // incall_touch_ui, because CDMA and GSM button layouts are noticeably different.
- final ViewStub touchUiStub = (ViewStub) findViewById(
- mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
- ? R.id.inCallTouchUiCdmaStub : R.id.inCallTouchUiStub);
- if (touchUiStub != null) touchUiStub.inflate();
-
- initInCallScreen();
-
- registerForPhoneStates();
-
- // No need to change wake state here; that happens in onResume() when we
- // are actually displayed.
-
- // Handle the Intent we were launched with, but only if this is the
- // the very first time we're being launched (ie. NOT if we're being
- // re-initialized after previously being shut down.)
- // Once we're up and running, any future Intents we need
- // to handle will come in via the onNewIntent() method.
- if (icicle == null) {
- if (DBG) log("onCreate(): this is our very first launch, checking intent...");
- internalResolveIntent(getIntent());
- }
-
- Profiler.callScreenCreated();
- if (DBG) log("onCreate(): exit");
- }
-
- /**
- * Sets the Phone object used internally by the InCallScreen.
- *
- * In normal operation this is called from onCreate(), and the
- * passed-in Phone object comes from the PhoneApp.
- * For testing, test classes can use this method to
- * inject a test Phone instance.
- */
- /* package */ void setPhone(Phone phone) {
- mPhone = phone;
- }
-
- @Override
- protected void onResume() {
- if (DBG) log("onResume()...");
- super.onResume();
-
- mIsForegroundActivity = true;
-
- // The flag shouldn't be turned on when there are actual phone calls.
- if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()) {
- mApp.inCallUiState.showAlreadyDisconnectedState = false;
- }
-
- final InCallUiState inCallUiState = mApp.inCallUiState;
- if (VDBG) inCallUiState.dumpState();
-
- updateExpandedViewState();
-
- // Listen for broadcast intents that might affect the onscreen UI.
- registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
-
- // Keep a "dialer session" active when we're in the foreground.
- // (This is needed to play DTMF tones.)
- mDialer.startDialerSession();
-
- // Restore various other state from the InCallUiState object:
-
- // Update the onscreen dialpad state to match the InCallUiState.
- if (inCallUiState.showDialpad) {
- openDialpadInternal(false); // no "opening" animation
- } else {
- closeDialpadInternal(false); // no "closing" animation
- }
-
- // Reset the dialpad context
- // TODO: Dialpad digits should be set here as well (once they are saved)
- mDialer.setDialpadContext(inCallUiState.dialpadContextText);
-
- // If there's a "Respond via SMS" popup still around since the
- // last time we were the foreground activity, make sure it's not
- // still active!
- // (The popup should *never* be visible initially when we first
- // come to the foreground; it only ever comes up in response to
- // the user selecting the "SMS" option from the incoming call
- // widget.)
- mRespondViaSmsManager.dismissPopup(); // safe even if already dismissed
-
- // Display an error / diagnostic indication if necessary.
- //
- // When the InCallScreen comes to the foreground, we normally we
- // display the in-call UI in whatever state is appropriate based on
- // the state of the telephony framework (e.g. an outgoing call in
- // DIALING state, an incoming call, etc.)
- //
- // But if the InCallUiState has a "pending call status code" set,
- // that means we need to display some kind of status or error
- // indication to the user instead of the regular in-call UI. (The
- // most common example of this is when there's some kind of
- // failure while initiating an outgoing call; see
- // CallController.placeCall().)
- boolean handledStartupError = false;
- if (inCallUiState.hasPendingCallStatusCode()) {
- if (DBG) log("- onResume: need to show status indication!");
- showStatusIndication(inCallUiState.getPendingCallStatusCode());
-
- // Set handledStartupError to ensure that we won't bail out below.
- // (We need to stay here in the InCallScreen so that the user
- // is able to see the error dialog!)
- handledStartupError = true;
- }
-
- // Set the volume control handler while we are in the foreground.
- final boolean bluetoothConnected = false; //isBluetoothAudioConnected();
-
- // TODO(klp): Move this volume button control code to the UI
- if (bluetoothConnected) {
- setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
- } else {
- setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
- }
-
- takeKeyEvents(true);
-
- // If an OTASP call is in progress, use the special OTASP-specific UI.
- boolean inOtaCall = false;
- if (TelephonyCapabilities.supportsOtasp(mPhone)) {
- inOtaCall = checkOtaspStateOnResume();
- }
- if (!inOtaCall) {
- // Always start off in NORMAL mode
- setInCallScreenMode(InCallScreenMode.NORMAL);
- }
-
- // Before checking the state of the CallManager, clean up any
- // connections in the DISCONNECTED state.
- // (The DISCONNECTED state is used only to drive the "call ended"
- // UI; it's totally useless when *entering* the InCallScreen.)
- mCM.clearDisconnected();
-
- // Update the onscreen UI to reflect the current telephony state.
- SyncWithPhoneStateStatus status = syncWithPhoneState();
-
- // Note there's no need to call updateScreen() here;
- // syncWithPhoneState() already did that if necessary.
-
- if (status != SyncWithPhoneStateStatus.SUCCESS) {
- if (DBG) log("- onResume: syncWithPhoneState failed! status = " + status);
- // Couldn't update the UI, presumably because the phone is totally
- // idle.
-
- // Even though the phone is idle, though, we do still need to
- // stay here on the InCallScreen if we're displaying an
- // error dialog (see "showStatusIndication()" above).
-
- if (handledStartupError) {
- // Stay here for now. We'll eventually leave the
- // InCallScreen when the user presses the dialog's OK
- // button (see bailOutAfterErrorDialog()), or when the
- // progress indicator goes away.
- Log.i(LOG_TAG, " ==> syncWithPhoneState failed, but staying here anyway.");
- } else {
- // The phone is idle, and we did NOT handle a
- // startup error during this pass thru onResume.
- //
- // This basically means that we're being resumed because of
- // some action *other* than a new intent. (For example,
- // the user pressing POWER to wake up the device, causing
- // the InCallScreen to come back to the foreground.)
- //
- // In this scenario we do NOT want to stay here on the
- // InCallScreen: we're not showing any useful info to the
- // user (like a dialog), and the in-call UI itself is
- // useless if there's no active call. So bail out.
-
- Log.i(LOG_TAG, " ==> syncWithPhoneState failed; bailing out!");
- dismissAllDialogs();
-
- // Force the InCallScreen to truly finish(), rather than just
- // moving it to the back of the activity stack (which is what
- // our finish() method usually does.)
- // This is necessary to avoid an obscure scenario where the
- // InCallScreen can get stuck in an inconsistent state, somehow
- // causing a *subsequent* outgoing call to fail (bug 4172599).
- endInCallScreenSession(true /* force a real finish() call */);
- return;
- }
- } else if (TelephonyCapabilities.supportsOtasp(mPhone)) {
- if (inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL ||
- inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED) {
- if (mCallCard != null) mCallCard.setVisibility(View.GONE);
- updateScreen();
- return;
- }
- }
-
- // InCallScreen is now active.
- EventLog.writeEvent(EventLogTags.PHONE_UI_ENTER);
-
- // Update the poke lock and wake lock when we move to the foreground.
- // This will be no-op when prox sensor is effective.
- mApp.updateWakeState();
-
- // Restore the mute state if the last mute state change was NOT
- // done by the user.
- if (mApp.getRestoreMuteOnInCallResume()) {
- // Mute state is based on the foreground call
- PhoneUtils.restoreMuteState();
- mApp.setRestoreMuteOnInCallResume(false);
- }
-
- Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
-
- // If there's a pending MMI code, we'll show a dialog here.
- //
- // Note: previously we had shown the dialog when MMI_INITIATE event's coming
- // from telephony layer, while right now we don't because the event comes
- // too early (before in-call screen is prepared).
- // Now we instead check pending MMI code and show the dialog here.
- //
- // This *may* cause some problem, e.g. when the user really quickly starts
- // MMI sequence and calls an actual phone number before the MMI request
- // being completed, which is rather rare.
- //
- // TODO: streamline this logic and have a UX in a better manner.
- // Right now syncWithPhoneState() above will return SUCCESS based on
- // mPhone.getPendingMmiCodes().isEmpty(), while we check it again here.
- // Also we show pre-populated in-call UI under the dialog, which looks
- // not great. (issue 5210375, 5545506)
- // After cleaning them, remove commented-out MMI handling code elsewhere.
- if (!mPhone.getPendingMmiCodes().isEmpty()) {
- if (mMmiStartedDialog == null) {
- MmiCode mmiCode = mPhone.getPendingMmiCodes().get(0);
- Message message = Message.obtain(mHandler, PhoneGlobals.MMI_CANCEL);
- mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
- message, mMmiStartedDialog);
- // mInCallScreen needs to receive MMI_COMPLETE/MMI_CANCEL event from telephony,
- // which will dismiss the entire screen.
- }
- }
-
- // This means the screen is shown even though there's no connection, which only happens
- // when the phone call has hung up while the screen is turned off at that moment.
- // We want to show "disconnected" state with photos with appropriate elapsed time for
- // the finished phone call.
- if (mApp.inCallUiState.showAlreadyDisconnectedState) {
- // if (DBG) {
- log("onResume(): detected \"show already disconnected state\" situation."
- + " set up DELAYED_CLEANUP_AFTER_DISCONNECT message with "
- + CALL_ENDED_LONG_DELAY + " msec delay.");
- //}
- mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
- mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
- CALL_ENDED_LONG_DELAY);
- }
-
- if (VDBG) log("onResume() done.");
- }
-
- // onPause is guaranteed to be called when the InCallScreen goes
- // in the background.
- @Override
- protected void onPause() {
- if (DBG) log("onPause()...");
- super.onPause();
-
- mIsForegroundActivity = false;
-
- // "show-already-disconnected-state" should be effective just during the first wake-up.
- // We should never allow it to stay true after that.
- mApp.inCallUiState.showAlreadyDisconnectedState = false;
-
- // Make sure the "Manage conference" chronometer is stopped when
- // we move away from the foreground.
- mManageConferenceUtils.stopConferenceTime();
-
- // as a catch-all, make sure that any dtmf tones are stopped
- // when the UI is no longer in the foreground.
- mDialer.onDialerKeyUp(null);
-
- // Release any "dialer session" resources, now that we're no
- // longer in the foreground.
- mDialer.stopDialerSession();
-
- // If the device is put to sleep as the phone call is ending,
- // we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
- // event gets handled AFTER the device goes to sleep and wakes
- // up again.
-
- // This is because it is possible for a sleep command
- // (executed with the End Call key) to come during the 2
- // seconds that the "Call Ended" screen is up. Sleep then
- // pauses the device (including the cleanup event) and
- // resumes the event when it wakes up.
-
- // To fix this, we introduce a bit of code that pushes the UI
- // to the background if we pause and see a request to
- // DELAYED_CLEANUP_AFTER_DISCONNECT.
-
- // Note: We can try to finish directly, by:
- // 1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
- // 2. Calling delayedCleanupAfterDisconnect directly
-
- // However, doing so can cause problems between the phone
- // app and the keyguard - the keyguard is trying to sleep at
- // the same time that the phone state is changing. This can
- // end up causing the sleep request to be ignored.
- if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)
- && mCM.getState() != PhoneConstants.State.RINGING) {
- if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
- endInCallScreenSession();
- }
-
- EventLog.writeEvent(EventLogTags.PHONE_UI_EXIT);
-
- // Dismiss any dialogs we may have brought up, just to be 100%
- // sure they won't still be around when we get back here.
- dismissAllDialogs();
-
- updateExpandedViewState();
-
- // ...and *always* reset the system bar back to its normal state
- // when leaving the in-call UI.
- // (While we're the foreground activity, we disable navigation in
- // some call states; see InCallTouchUi.updateState().)
- mApp.notificationMgr.statusBarHelper.enableSystemBarNavigation(true);
-
- // Unregister for broadcast intents. (These affect the visible UI
- // of the InCallScreen, so we only care about them while we're in the
- // foreground.)
- unregisterReceiver(mReceiver);
-
- // Make sure we revert the poke lock and wake lock when we move to
- // the background.
- mApp.updateWakeState();
-
- // clear the dismiss keyguard flag so we are back to the default state
- // when we next resume
- updateKeyguardPolicy(false);
-
- // See also PhoneApp#updatePhoneState(), which takes care of all the other release() calls.
- if (mApp.getUpdateLock().isHeld() && mApp.getPhoneState() == PhoneConstants.State.IDLE) {
- if (DBG) {
- log("Release UpdateLock on onPause() because there's no active phone call.");
- }
- mApp.getUpdateLock().release();
- }
- }
-
- @Override
- protected void onStop() {
- if (DBG) log("onStop()...");
- super.onStop();
-
- stopTimer();
-
- PhoneConstants.State state = mCM.getState();
- if (DBG) log("onStop: state = " + state);
-
- if (state == PhoneConstants.State.IDLE) {
- if (mRespondViaSmsManager.isShowingPopup()) {
- // This means that the user has been opening the "Respond via SMS" dialog even
- // after the incoming call hanging up, and the screen finally went background.
- // In that case we just close the dialog and exit the whole in-call screen.
- mRespondViaSmsManager.dismissPopup();
- }
-
- // when OTA Activation, OTA Success/Failure dialog or OTA SPC
- // failure dialog is running, do not destroy inCallScreen. Because call
- // is already ended and dialog will not get redrawn on slider event.
- if ((mApp.cdmaOtaProvisionData != null) && (mApp.cdmaOtaScreenState != null)
- && ((mApp.cdmaOtaScreenState.otaScreenState !=
- CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION)
- && (mApp.cdmaOtaScreenState.otaScreenState !=
- CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG)
- && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
- // we don't want the call screen to remain in the activity history
- // if there are not active or ringing calls.
- if (DBG) log("- onStop: calling finish() to clear activity history...");
- moveTaskToBack(true);
- if (mApp.otaUtils != null) {
- mApp.otaUtils.cleanOtaScreen(true);
- }
- }
- }
- }
-
- @Override
- protected void onDestroy() {
- Log.i(LOG_TAG, "onDestroy()... this = " + this);
- super.onDestroy();
-
- // Set the magic flag that tells us NOT to handle any handler
- // messages that come in asynchronously after we get destroyed.
- mIsDestroyed = true;
-
- mApp.setInCallScreenInstance(null);
-
- // Clear out the InCallScreen references in various helper objects
- // (to let them know we've been destroyed).
- if (mCallCard != null) {
- mCallCard.setInCallScreenInstance(null);
- }
- if (mInCallTouchUi != null) {
- mInCallTouchUi.setInCallScreenInstance(null);
- }
- mRespondViaSmsManager.setInCallScreenInstance(null);
-
- mDialer.clearInCallScreenReference();
- mDialer = null;
-
- unregisterForPhoneStates();
- // No need to change wake state here; that happens in onPause() when we
- // are moving out of the foreground.
-
- // Dismiss all dialogs, to be absolutely sure we won't leak any of
- // them while changing orientation.
- dismissAllDialogs();
-
- // If there's an OtaUtils instance around, clear out its
- // references to our internal widgets.
- if (mApp.otaUtils != null) {
- mApp.otaUtils.clearUiWidgets();
- }
- }
-
- /**
- * Dismisses the in-call screen.
- *
- * We never *really* finish() the InCallScreen, since we don't want to
- * get destroyed and then have to be re-created from scratch for the
- * next call. Instead, we just move ourselves to the back of the
- * activity stack.
- *
- * This also means that we'll no longer be reachable via the BACK
- * button (since moveTaskToBack() puts us behind the Home app, but the
- * home app doesn't allow the BACK key to move you any farther down in
- * the history stack.)
- *
- * (Since the Phone app itself is never killed, this basically means
- * that we'll keep a single InCallScreen instance around for the
- * entire uptime of the device. This noticeably improves the UI
- * responsiveness for incoming calls.)
- */
- @Override
- public void finish() {
- if (DBG) log("finish()...");
- moveTaskToBack(true);
- }
-
- /**
- * End the current in call screen session.
- *
- * This must be called when an InCallScreen session has
- * complete so that the next invocation via an onResume will
- * not be in an old state.
- */
- public void endInCallScreenSession() {
- if (DBG) log("endInCallScreenSession()... phone state = " + mCM.getState());
- endInCallScreenSession(false);
- }
-
- /**
- * Internal version of endInCallScreenSession().
- *
- * @param forceFinish If true, force the InCallScreen to
- * truly finish() rather than just calling moveTaskToBack().
- * @see finish()
- */
- private void endInCallScreenSession(boolean forceFinish) {
- if (DBG) {
- log("endInCallScreenSession(" + forceFinish + ")... phone state = " + mCM.getState());
- }
- if (forceFinish) {
- Log.i(LOG_TAG, "endInCallScreenSession(): FORCING a call to super.finish()!");
- super.finish(); // Call super.finish() rather than our own finish() method,
- // which actually just calls moveTaskToBack().
- } else {
- moveTaskToBack(true);
- }
- setInCallScreenMode(InCallScreenMode.UNDEFINED);
-
- // Call update screen so that the in-call screen goes back to a normal state.
- // This avoids bugs where a previous state will filcker the next time phone is
- // opened.
- updateScreen();
-
- if (mCallCard != null) {
- mCallCard.clear();
- }
- }
-
- /**
- * True when this Activity is in foreground (between onResume() and onPause()).
- */
- /* package */ boolean isForegroundActivity() {
- return mIsForegroundActivity;
- }
-
- /* package */ void updateKeyguardPolicy(boolean dismissKeyguard) {
- if (dismissKeyguard) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- } else {
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- }
- }
-
- private void registerForPhoneStates() {
- if (!mRegisteredForPhoneStates) {
- mCM.registerForPreciseCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
- mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
- // TODO: sort out MMI code (probably we should remove this method entirely).
- // See also MMI handling code in onResume()
- // mCM.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
-
- // register for the MMI complete message. Upon completion,
- // PhoneUtils will bring up a system dialog instead of the
- // message display class in PhoneUtils.displayMMIComplete().
- // We'll listen for that message too, so that we can finish
- // the activity at the same time.
- mCM.registerForMmiComplete(mHandler, PhoneGlobals.MMI_COMPLETE, null);
- mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
- mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
- mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
- mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);
- mCM.registerForNewRingingConnection(mHandler, PHONE_NEW_RINGING_CONNECTION, null);
- mRegisteredForPhoneStates = true;
- }
- }
-
- private void unregisterForPhoneStates() {
- mCM.unregisterForPreciseCallStateChanged(mHandler);
- mCM.unregisterForDisconnect(mHandler);
- mCM.unregisterForMmiInitiate(mHandler);
- mCM.unregisterForMmiComplete(mHandler);
- mCM.unregisterForCallWaiting(mHandler);
- mCM.unregisterForPostDialCharacter(mHandler);
- mCM.unregisterForSuppServiceFailed(mHandler);
- mCM.unregisterForIncomingRing(mHandler);
- mCM.unregisterForNewRingingConnection(mHandler);
- mRegisteredForPhoneStates = false;
- }
-
- /* package */ void updateAfterRadioTechnologyChange() {
- if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
-
- // Reset the call screen since the calls cannot be transferred
- // across radio technologies.
- resetInCallScreenMode();
-
- // Unregister for all events from the old obsolete phone
- unregisterForPhoneStates();
-
- // (Re)register for all events relevant to the new active phone
- registerForPhoneStates();
-
- // And finally, refresh the onscreen UI. (Note that it's safe
- // to call requestUpdateScreen() even if the radio change ended up
- // causing us to exit the InCallScreen.)
- requestUpdateScreen();
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- log("onNewIntent: intent = " + intent + ", phone state = " + mCM.getState());
-
- // We're being re-launched with a new Intent. Since it's possible for a
- // single InCallScreen instance to persist indefinitely (even if we
- // finish() ourselves), this sequence can potentially happen any time
- // the InCallScreen needs to be displayed.
-
- // Stash away the new intent so that we can get it in the future
- // by calling getIntent(). (Otherwise getIntent() will return the
- // original Intent from when we first got created!)
- setIntent(intent);
-
- // Activities are always paused before receiving a new intent, so
- // we can count on our onResume() method being called next.
-
- // Just like in onCreate(), handle the intent.
- internalResolveIntent(intent);
- }
-
- private void internalResolveIntent(Intent intent) {
- if (intent == null || intent.getAction() == null) {
- return;
- }
- String action = intent.getAction();
- if (DBG) log("internalResolveIntent: action=" + action);
-
- // In gingerbread and earlier releases, the InCallScreen used to
- // directly handle certain intent actions that could initiate phone
- // calls, namely ACTION_CALL and ACTION_CALL_EMERGENCY, and also
- // OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING.
- //
- // But it doesn't make sense to tie those actions to the InCallScreen
- // (or especially to the *activity lifecycle* of the InCallScreen).
- // Instead, the InCallScreen should only be concerned with running the
- // onscreen UI while in a call. So we've now offloaded the call-control
- // functionality to a new module called CallController, and OTASP calls
- // are now launched from the OtaUtils startInteractiveOtasp() or
- // startNonInteractiveOtasp() methods.
- //
- // So now, the InCallScreen is only ever launched using the ACTION_MAIN
- // action, and (upon launch) performs no functionality other than
- // displaying the UI in a state that matches the current telephony
- // state.
-
- if (action.equals(intent.ACTION_MAIN)) {
- // This action is the normal way to bring up the in-call UI.
- //
- // Most of the interesting work of updating the onscreen UI (to
- // match the current telephony state) happens in the
- // syncWithPhoneState() => updateScreen() sequence that happens in
- // onResume().
- //
- // But we do check here for one extra that can come along with the
- // ACTION_MAIN intent:
-
- if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
- // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
- // dialpad should be initially visible. If the extra isn't
- // present at all, we just leave the dialpad in its previous state.
-
- boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
- if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
-
- // If SHOW_DIALPAD_EXTRA is specified, that overrides whatever
- // the previous state of inCallUiState.showDialpad was.
- mApp.inCallUiState.showDialpad = showDialpad;
-
- final boolean hasActiveCall = mCM.hasActiveFgCall();
- final boolean hasHoldingCall = mCM.hasActiveBgCall();
-
- // There's only one line in use, AND it's on hold, at which we're sure the user
- // wants to use the dialpad toward the exact line, so un-hold the holding line.
- if (showDialpad && !hasActiveCall && hasHoldingCall) {
- PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
- }
- }
- // ...and in onResume() we'll update the onscreen dialpad state to
- // match the InCallUiState.
-
- return;
- }
-
- if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {
- // Bring up the in-call UI in the OTASP-specific "activate" state;
- // see OtaUtils.startInteractiveOtasp(). Note that at this point
- // the OTASP call has not been started yet; we won't actually make
- // the call until the user presses the "Activate" button.
-
- if (!TelephonyCapabilities.supportsOtasp(mPhone)) {
- throw new IllegalStateException(
- "Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "
- + intent);
- }
-
- setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
- if ((mApp.cdmaOtaProvisionData != null)
- && (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
- mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
- mApp.cdmaOtaScreenState.otaScreenState =
- CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
- }
- return;
- }
-
- // Various intent actions that should no longer come here directly:
- if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
- // This intent is now handled by the InCallScreenShowActivation
- // activity, which translates it into a call to
- // OtaUtils.startInteractiveOtasp().
- throw new IllegalStateException(
- "Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "
- + intent);
- } else if (action.equals(Intent.ACTION_CALL)
- || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
- // ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now
- // translates them into CallController.placeCall() calls rather than
- // launching the InCallScreen directly.
- throw new IllegalStateException("Unexpected CALL action received by InCallScreen: "
- + intent);
- } else if (action.equals(ACTION_UNDEFINED)) {
- // This action is only used for internal bookkeeping; we should
- // never actually get launched with it.
- Log.wtf(LOG_TAG, "internalResolveIntent: got launched with ACTION_UNDEFINED");
- return;
- } else {
- Log.wtf(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
- // But continue the best we can (basically treating this case
- // like ACTION_MAIN...)
- return;
- }
- }
-
- private void stopTimer() {
- if (mCallCard != null) mCallCard.stopTimer();
- }
-
- private void initInCallScreen() {
- if (VDBG) log("initInCallScreen()...");
-
- // Have the WindowManager filter out touch events that are "too fat".
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
-
- // Initialize the CallCard.
- mCallCard = (CallCard) findViewById(R.id.callCard);
- if (VDBG) log(" - mCallCard = " + mCallCard);
- mCallCard.setInCallScreenInstance(this);
-
- // Initialize the onscreen UI elements.
- initInCallTouchUi();
-
- // Helper class to keep track of enabledness/state of UI controls
- mInCallControlState = new InCallControlState(this, mCM, mApp.getBluetoothManager());
-
- // Helper class to run the "Manage conference" UI
- mManageConferenceUtils = new ManageConferenceUtils(this, mCM);
-
- // The DTMF Dialpad.
- ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
- mDialer = new DTMFTwelveKeyDialer(this, stub);
- mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
- }
-
- /**
- * Returns true if the phone is "in use", meaning that at least one line
- * is active (ie. off hook or ringing or dialing). Conversely, a return
- * value of false means there's currently no phone activity at all.
- */
- private boolean phoneIsInUse() {
- return mCM.getState() != PhoneConstants.State.IDLE;
- }
-
- private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
- if (VDBG) log("handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
-
- // As soon as the user starts typing valid dialable keys on the
- // keyboard (presumably to type DTMF tones) we start passing the
- // key events to the DTMFDialer's onDialerKeyDown. We do so
- // only if the okToDialDTMFTones() conditions pass.
- if (okToDialDTMFTones()) {
- return mDialer.onDialerKeyDown(event);
-
- // TODO: If the dialpad isn't currently visible, maybe
- // consider automatically bringing it up right now?
- // (Just to make sure the user sees the digits widget...)
- // But this probably isn't too critical since it's awkward to
- // use the hard keyboard while in-call in the first place,
- // especially now that the in-call UI is portrait-only...
- }
-
- return false;
- }
-
- @Override
- public void onBackPressed() {
- if (DBG) log("onBackPressed()...");
-
- // To consume this BACK press, the code here should just do
- // something and return. Otherwise, call super.onBackPressed() to
- // get the default implementation (which simply finishes the
- // current activity.)
-
- if (mCM.hasActiveRingingCall()) {
- // The Back key, just like the Home key, is always disabled
- // while an incoming call is ringing. (The user *must* either
- // answer or reject the call before leaving the incoming-call
- // screen.)
- if (DBG) log("BACK key while ringing: ignored");
-
- // And consume this event; *don't* call super.onBackPressed().
- return;
- }
-
- // BACK is also used to exit out of any "special modes" of the
- // in-call UI:
-
- if (mDialer.isOpened()) {
- closeDialpadInternal(true); // do the "closing" animation
- return;
- }
-
- if (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
- // Hide the Manage Conference panel, return to NORMAL mode.
- setInCallScreenMode(InCallScreenMode.NORMAL);
- requestUpdateScreen();
- return;
- }
-
- // Nothing special to do. Fall back to the default behavior.
- super.onBackPressed();
- }
-
- /**
- * Handles the green CALL key while in-call.
- * @return true if we consumed the event.
- */
- private boolean handleCallKey() {
- // The green CALL button means either "Answer", "Unhold", or
- // "Swap calls", or can be a no-op, depending on the current state
- // of the Phone.
-
- final boolean hasRingingCall = mCM.hasActiveRingingCall();
- final boolean hasActiveCall = mCM.hasActiveFgCall();
- final boolean hasHoldingCall = mCM.hasActiveBgCall();
-
- int phoneType = mPhone.getPhoneType();
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- // The green CALL button means either "Answer", "Swap calls/On Hold", or
- // "Add to 3WC", depending on the current state of the Phone.
-
- CdmaPhoneCallState.PhoneCallState currCallState =
- mApp.cdmaPhoneCallState.getCurrentCallState();
- if (hasRingingCall) {
- //Scenario 1: Accepting the First Incoming and Call Waiting call
- if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
- internalAnswerCall(); // Automatically holds the current active call,
- // if there is one
- } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
- && (hasActiveCall)) {
- //Scenario 2: Merging 3Way calls
- if (DBG) log("answerCall: Merge 3-way call scenario");
- // Merge calls
- PhoneUtils.mergeCalls(mCM);
- } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- //Scenario 3: Switching between two Call waiting calls or drop the latest
- // connection if in a 3Way merge scenario
- if (DBG) log("answerCall: Switch btwn 2 calls scenario");
- internalSwapCalls();
- }
- } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
- || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
- if (hasRingingCall) {
- // If an incoming call is ringing, the CALL button is actually
- // handled by the PhoneWindowManager. (We do this to make
- // sure that we'll respond to the key even if the InCallScreen
- // hasn't come to the foreground yet.)
- //
- // We'd only ever get here in the extremely rare case that the
- // incoming call started ringing *after*
- // PhoneWindowManager.interceptKeyTq() but before the event
- // got here, or else if the PhoneWindowManager had some
- // problem connecting to the ITelephony service.
- Log.w(LOG_TAG, "handleCallKey: incoming call is ringing!"
- + " (PhoneWindowManager should have handled this key.)");
- // But go ahead and handle the key as normal, since the
- // PhoneWindowManager presumably did NOT handle it:
-
- // There's an incoming ringing call: CALL means "Answer".
- internalAnswerCall();
- } else if (hasActiveCall && hasHoldingCall) {
- // Two lines are in use: CALL means "Swap calls".
- if (DBG) log("handleCallKey: both lines in use ==> swap calls.");
- internalSwapCalls();
- } else if (hasHoldingCall) {
- // There's only one line in use, AND it's on hold.
- // In this case CALL is a shortcut for "unhold".
- if (DBG) log("handleCallKey: call on hold ==> unhold.");
- PhoneUtils.switchHoldingAndActive(
- mCM.getFirstActiveBgCall()); // Really means "unhold" in this state
- } else {
- // The most common case: there's only one line in use, and
- // it's an active call (i.e. it's not on hold.)
- // In this case CALL is a no-op.
- // (This used to be a shortcut for "add call", but that was a
- // bad idea because "Add call" is so infrequently-used, and
- // because the user experience is pretty confusing if you
- // inadvertently trigger it.)
- if (VDBG) log("handleCallKey: call in foregound ==> ignoring.");
- // But note we still consume this key event; see below.
- }
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
-
- // We *always* consume the CALL key, since the system-wide default
- // action ("go to the in-call screen") is useless here.
- return true;
- }
-
- boolean isKeyEventAcceptableDTMF (KeyEvent event) {
- return (mDialer != null && mDialer.isKeyEventAcceptable(event));
- }
-
- /**
- * Overriden to track relevant focus changes.
- *
- * If a key is down and some time later the focus changes, we may
- * NOT recieve the keyup event; logically the keyup event has not
- * occured in this window. This issue is fixed by treating a focus
- * changed event as an interruption to the keydown, making sure
- * that any code that needs to be run in onKeyUp is ALSO run here.
- */
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- // the dtmf tones should no longer be played
- if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
- if (!hasFocus && mDialer != null) {
- if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
- mDialer.onDialerKeyUp(null);
- }
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- // if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
-
- // push input to the dialer.
- if ((mDialer != null) && (mDialer.onDialerKeyUp(event))){
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_CALL) {
- // Always consume CALL to be sure the PhoneWindow won't do anything with it
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL:
- boolean handled = handleCallKey();
- if (!handled) {
- Log.w(LOG_TAG, "InCallScreen should always handle KEYCODE_CALL in onKeyDown");
- }
- // Always consume CALL to be sure the PhoneWindow won't do anything with it
- return true;
-
- // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
- // The standard system-wide handling of the ENDCALL key
- // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
- // already implements exactly what the UI spec wants,
- // namely (1) "hang up" if there's a current active call,
- // or (2) "don't answer" if there's a current ringing call.
-
- case KeyEvent.KEYCODE_CAMERA:
- // Disable the CAMERA button while in-call since it's too
- // easy to press accidentally.
- return true;
-
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- if (mCM.getState() == PhoneConstants.State.RINGING) {
- // If an incoming call is ringing, the VOLUME buttons are
- // actually handled by the PhoneWindowManager. (We do
- // this to make sure that we'll respond to them even if
- // the InCallScreen hasn't come to the foreground yet.)
- //
- // We'd only ever get here in the extremely rare case that the
- // incoming call started ringing *after*
- // PhoneWindowManager.interceptKeyTq() but before the event
- // got here, or else if the PhoneWindowManager had some
- // problem connecting to the ITelephony service.
- Log.w(LOG_TAG, "VOLUME key: incoming call is ringing!"
- + " (PhoneWindowManager should have handled this key.)");
- // But go ahead and handle the key as normal, since the
- // PhoneWindowManager presumably did NOT handle it:
- internalSilenceRinger();
-
- // As long as an incoming call is ringing, we always
- // consume the VOLUME keys.
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_MUTE:
- onMuteClick();
- return true;
-
- // Various testing/debugging features, enabled ONLY when VDBG == true.
- case KeyEvent.KEYCODE_SLASH:
- if (VDBG) {
- log("----------- InCallScreen View dump --------------");
- // Dump starting from the top-level view of the entire activity:
- Window w = this.getWindow();
- View decorView = w.getDecorView();
- decorView.debug();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_EQUALS:
- if (VDBG) {
- log("----------- InCallScreen call state dump --------------");
- PhoneUtils.dumpCallState(mPhone);
- PhoneUtils.dumpCallManager();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_GRAVE:
- if (VDBG) {
- // Placeholder for other misc temp testing
- log("------------ Temp testing -----------------");
- return true;
- }
- break;
- }
-
- if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * Handle a failure notification for a supplementary service
- * (i.e. conference, switch, separate, transfer, etc.).
- */
- void onSuppServiceFailed(AsyncResult r) {
- Phone.SuppService service = (Phone.SuppService) r.result;
- if (DBG) log("onSuppServiceFailed: " + service);
-
- int errorMessageResId;
- switch (service) {
- case SWITCH:
- // Attempt to switch foreground and background/incoming calls failed
- // ("Failed to switch calls")
- errorMessageResId = R.string.incall_error_supp_service_switch;
- break;
-
- case SEPARATE:
- // Attempt to separate a call from a conference call
- // failed ("Failed to separate out call")
- errorMessageResId = R.string.incall_error_supp_service_separate;
- break;
-
- case TRANSFER:
- // Attempt to connect foreground and background calls to
- // each other (and hanging up user's line) failed ("Call
- // transfer failed")
- errorMessageResId = R.string.incall_error_supp_service_transfer;
- break;
-
- case CONFERENCE:
- // Attempt to add a call to conference call failed
- // ("Conference call failed")
- errorMessageResId = R.string.incall_error_supp_service_conference;
- break;
-
- case REJECT:
- // Attempt to reject an incoming call failed
- // ("Call rejection failed")
- errorMessageResId = R.string.incall_error_supp_service_reject;
- break;
-
- case HANGUP:
- // Attempt to release a call failed ("Failed to release call(s)")
- errorMessageResId = R.string.incall_error_supp_service_hangup;
- break;
-
- case UNKNOWN:
- default:
- // Attempt to use a service we don't recognize or support
- // ("Unsupported service" or "Selected service failed")
- errorMessageResId = R.string.incall_error_supp_service_unknown;
- break;
- }
-
- // mSuppServiceFailureDialog is a generic dialog used for any
- // supp service failure, and there's only ever have one
- // instance at a time. So just in case a previous dialog is
- // still around, dismiss it.
- if (mSuppServiceFailureDialog != null) {
- if (DBG) log("- DISMISSING mSuppServiceFailureDialog.");
- mSuppServiceFailureDialog.dismiss(); // It's safe to dismiss() a dialog
- // that's already dismissed.
- mSuppServiceFailureDialog = null;
- }
-
- mSuppServiceFailureDialog = new AlertDialog.Builder(this)
- .setMessage(errorMessageResId)
- .setPositiveButton(R.string.ok, null)
- .create();
- mSuppServiceFailureDialog.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
- mSuppServiceFailureDialog.show();
- }
-
- /**
- * Something has changed in the phone's state. Update the UI.
- */
- private void onPhoneStateChanged(AsyncResult r) {
- PhoneConstants.State state = mCM.getState();
- if (DBG) log("onPhoneStateChanged: current state = " + state);
-
- // There's nothing to do here if we're not the foreground activity.
- // (When we *do* eventually come to the foreground, we'll do a
- // full update then.)
- if (!mIsForegroundActivity) {
- if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
- return;
- }
-
- updateExpandedViewState();
-
- // Update the onscreen UI.
- // We use requestUpdateScreen() here (which posts a handler message)
- // instead of calling updateScreen() directly, which allows us to avoid
- // unnecessary work if multiple onPhoneStateChanged() events come in all
- // at the same time.
-
- requestUpdateScreen();
-
- // Make sure we update the poke lock and wake lock when certain
- // phone state changes occur.
- mApp.updateWakeState();
- }
-
- /**
- * Updates the UI after a phone connection is disconnected, as follows:
- *
- * - If this was a missed or rejected incoming call, and no other
- * calls are active, dismiss the in-call UI immediately. (The
- * CallNotifier will still create a "missed call" notification if
- * necessary.)
- *
- * - With any other disconnect cause, if the phone is now totally
- * idle, display the "Call ended" state for a couple of seconds.
- *
- * - Or, if the phone is still in use, stay on the in-call screen
- * (and update the UI to reflect the current state of the Phone.)
- *
- * @param r r.result contains the connection that just ended
- */
- private void onDisconnect(AsyncResult r) {
- Connection c = (Connection) r.result;
- Connection.DisconnectCause cause = c.getDisconnectCause();
- if (DBG) log("onDisconnect: connection '" + c + "', cause = " + cause
- + ", showing screen: " + mApp.isShowingCallScreen());
-
- boolean currentlyIdle = !phoneIsInUse();
- int autoretrySetting = AUTO_RETRY_OFF;
- boolean phoneIsCdma = (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
- if (phoneIsCdma) {
- // Get the Auto-retry setting only if Phone State is IDLE,
- // else let it stay as AUTO_RETRY_OFF
- if (currentlyIdle) {
- autoretrySetting = android.provider.Settings.Global.getInt(mPhone.getContext().
- getContentResolver(), android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
- }
- }
-
- // for OTA Call, only if in OTA NORMAL mode, handle OTA END scenario
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
- && ((mApp.cdmaOtaProvisionData != null)
- && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
- setInCallScreenMode(InCallScreenMode.OTA_ENDED);
- updateScreen();
- return;
- } else if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
- || ((mApp.cdmaOtaProvisionData != null)
- && mApp.cdmaOtaProvisionData.inOtaSpcState)) {
- if (DBG) log("onDisconnect: OTA Call end already handled");
- return;
- }
-
- // Any time a call disconnects, clear out the "history" of DTMF
- // digits you typed (to make sure it doesn't persist from one call
- // to the next.)
- mDialer.clearDigits();
-
- // Under certain call disconnected states, we want to alert the user
- // with a dialog instead of going through the normal disconnect
- // routine.
- if (cause == Connection.DisconnectCause.CALL_BARRED) {
- showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
- return;
- } else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
- showGenericErrorDialog(R.string.callFailed_fdn_only, false);
- return;
- } else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
- showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
- return;
- } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
- showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
- return;
- } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
- showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
- return;
- }
-
- if (phoneIsCdma) {
- Call.State callState = mApp.notifier.getPreviousCdmaCallState();
- if ((callState == Call.State.ACTIVE)
- && (cause != Connection.DisconnectCause.INCOMING_MISSED)
- && (cause != Connection.DisconnectCause.NORMAL)
- && (cause != Connection.DisconnectCause.LOCAL)
- && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
- showCallLostDialog();
- } else if ((callState == Call.State.DIALING || callState == Call.State.ALERTING)
- && (cause != Connection.DisconnectCause.INCOMING_MISSED)
- && (cause != Connection.DisconnectCause.NORMAL)
- && (cause != Connection.DisconnectCause.LOCAL)
- && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
-
- if (mApp.inCallUiState.needToShowCallLostDialog) {
- // Show the dialog now since the call that just failed was a retry.
- showCallLostDialog();
- mApp.inCallUiState.needToShowCallLostDialog = false;
- } else {
- if (autoretrySetting == AUTO_RETRY_OFF) {
- // Show the dialog for failed call if Auto Retry is OFF in Settings.
- showCallLostDialog();
- mApp.inCallUiState.needToShowCallLostDialog = false;
- } else {
- // Set the needToShowCallLostDialog flag now, so we'll know to show
- // the dialog if *this* call fails.
- mApp.inCallUiState.needToShowCallLostDialog = true;
- }
- }
- }
- }
-
- // Explicitly clean up up any DISCONNECTED connections
- // in a conference call.
- // [Background: Even after a connection gets disconnected, its
- // Connection object still stays around for a few seconds, in the
- // DISCONNECTED state. With regular calls, this state drives the
- // "call ended" UI. But when a single person disconnects from a
- // conference call there's no "call ended" state at all; in that
- // case we blow away any DISCONNECTED connections right now to make sure
- // the UI updates instantly to reflect the current state.]
- final Call call = c.getCall();
- if (call != null) {
- // We only care about situation of a single caller
- // disconnecting from a conference call. In that case, the
- // call will have more than one Connection (including the one
- // that just disconnected, which will be in the DISCONNECTED
- // state) *and* at least one ACTIVE connection. (If the Call
- // has *no* ACTIVE connections, that means that the entire
- // conference call just ended, so we *do* want to show the
- // "Call ended" state.)
- List<Connection> connections = call.getConnections();
- if (connections != null && connections.size() > 1) {
- for (Connection conn : connections) {
- if (conn.getState() == Call.State.ACTIVE) {
- // This call still has at least one ACTIVE connection!
- // So blow away any DISCONNECTED connections
- // (including, presumably, the one that just
- // disconnected from this conference call.)
-
- // We also force the wake state to refresh, just in
- // case the disconnected connections are removed
- // before the phone state change.
- if (VDBG) log("- Still-active conf call; clearing DISCONNECTED...");
- mApp.updateWakeState();
- mCM.clearDisconnected(); // This happens synchronously.
- break;
- }
- }
- }
- }
-
- // Note: see CallNotifier.onDisconnect() for some other behavior
- // that might be triggered by a disconnect event, like playing the
- // busy/congestion tone.
-
- // Stash away some info about the call that just disconnected.
- // (This might affect what happens after we exit the InCallScreen; see
- // delayedCleanupAfterDisconnect().)
- // TODO: rather than stashing this away now and then reading it in
- // delayedCleanupAfterDisconnect(), it would be cleaner to just pass
- // this as an argument to delayedCleanupAfterDisconnect() (if we call
- // it directly) or else pass it as a Message argument when we post the
- // DELAYED_CLEANUP_AFTER_DISCONNECT message.
- mLastDisconnectCause = cause;
-
- // We bail out immediately (and *don't* display the "call ended"
- // state at all) if this was an incoming call.
- boolean bailOutImmediately =
- ((cause == Connection.DisconnectCause.INCOMING_MISSED)
- || (cause == Connection.DisconnectCause.INCOMING_REJECTED))
- && currentlyIdle;
-
- boolean showingQuickResponseDialog =
- mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
-
- // Note: we also do some special handling for the case when a call
- // disconnects with cause==OUT_OF_SERVICE while making an
- // emergency call from airplane mode. That's handled by
- // EmergencyCallHelper.onDisconnect().
-
- if (bailOutImmediately && showingQuickResponseDialog) {
- if (DBG) log("- onDisconnect: Respond-via-SMS dialog is still being displayed...");
-
- // Do *not* exit the in-call UI yet!
- // If the call was an incoming call that was missed *and* the user is using
- // quick response screen, we keep showing the screen for a moment, assuming the
- // user wants to reply the call anyway.
- //
- // For this case, we will exit the screen when:
- // - the message is sent (RespondViaSmsManager)
- // - the message is canceled (RespondViaSmsManager), or
- // - when the whole in-call UI becomes background (onPause())
- } else if (bailOutImmediately) {
- if (DBG) log("- onDisconnect: bailOutImmediately...");
-
- // Exit the in-call UI!
- // (This is basically the same "delayed cleanup" we do below,
- // just with zero delay. Since the Phone is currently idle,
- // this call is guaranteed to immediately finish this activity.)
- delayedCleanupAfterDisconnect();
- } else {
- if (DBG) log("- onDisconnect: delayed bailout...");
- // Stay on the in-call screen for now. (Either the phone is
- // still in use, or the phone is idle but we want to display
- // the "call ended" state for a couple of seconds.)
-
- // Switch to the special "Call ended" state when the phone is idle
- // but there's still a call in the DISCONNECTED state:
- if (currentlyIdle
- && (mCM.hasDisconnectedFgCall() || mCM.hasDisconnectedBgCall())) {
- if (DBG) log("- onDisconnect: switching to 'Call ended' state...");
- setInCallScreenMode(InCallScreenMode.CALL_ENDED);
- }
-
- // Force a UI update in case we need to display anything
- // special based on this connection's DisconnectCause
- // (see CallCard.getCallFailedString()).
- updateScreen();
-
- // Some other misc cleanup that we do if the call that just
- // disconnected was the foreground call.
- final boolean hasActiveCall = mCM.hasActiveFgCall();
- if (!hasActiveCall) {
- if (DBG) log("- onDisconnect: cleaning up after FG call disconnect...");
-
- // Dismiss any dialogs which are only meaningful for an
- // active call *and* which become moot if the call ends.
- if (mWaitPromptDialog != null) {
- if (VDBG) log("- DISMISSING mWaitPromptDialog.");
- mWaitPromptDialog.dismiss(); // safe even if already dismissed
- mWaitPromptDialog = null;
- }
- if (mWildPromptDialog != null) {
- if (VDBG) log("- DISMISSING mWildPromptDialog.");
- mWildPromptDialog.dismiss(); // safe even if already dismissed
- mWildPromptDialog = null;
- }
- if (mPausePromptDialog != null) {
- if (DBG) log("- DISMISSING mPausePromptDialog.");
- mPausePromptDialog.dismiss(); // safe even if already dismissed
- mPausePromptDialog = null;
- }
- }
-
- // Updating the screen wake state is done in onPhoneStateChanged().
-
-
- // CDMA: We only clean up if the Phone state is IDLE as we might receive an
- // onDisconnect for a Call Collision case (rare but possible).
- // For Call collision cases i.e. when the user makes an out going call
- // and at the same time receives an Incoming Call, the Incoming Call is given
- // higher preference. At this time framework sends a disconnect for the Out going
- // call connection hence we should *not* bring down the InCallScreen as the Phone
- // State would be RINGING
- if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
- if (!currentlyIdle) {
- // Clean up any connections in the DISCONNECTED state.
- // This is necessary cause in CallCollision the foreground call might have
- // connections in DISCONNECTED state which needs to be cleared.
- mCM.clearDisconnected();
-
- // The phone is still in use. Stay here in this activity.
- // But we don't need to keep the screen on.
- if (DBG) log("onDisconnect: Call Collision case - staying on InCallScreen.");
- if (DBG) PhoneUtils.dumpCallState(mPhone);
- return;
- }
- }
-
- // This is onDisconnect() request from the last phone call; no available call anymore.
- //
- // When the in-call UI is in background *because* the screen is turned off (unlike the
- // other case where the other activity is being shown), we wake up the screen and
- // show "DISCONNECTED" state once, with appropriate elapsed time. After showing that
- // we *must* bail out of the screen again, showing screen lock if needed.
- //
- //
- // TODO: Consider moving this to CallNotifier. This code assumes the InCallScreen
- // never gets destroyed. For this exact case, it works (since InCallScreen won't be
- // destroyed), while technically this isn't right; Activity may be destroyed when
- // in background.
- if (currentlyIdle && !isForegroundActivity()) {
- log("Force waking up the screen to let users see \"disconnected\" state");
- if (call != null) {
- mCallCard.updateElapsedTimeWidget(call);
- }
- // This variable will be kept true until the next InCallScreen#onPause(), which
- // forcibly turns it off regardless of the situation (for avoiding unnecessary
- // confusion around this special case).
- mApp.inCallUiState.showAlreadyDisconnectedState = true;
-
- // Finally request wake-up..
- mApp.wakeUpScreen();
-
- // InCallScreen#onResume() will set DELAYED_CLEANUP_AFTER_DISCONNECT message,
- // so skip the following section.
- return;
- }
-
- // Finally, arrange for delayedCleanupAfterDisconnect() to get
- // called after a short interval (during which we display the
- // "call ended" state.) At that point, if the
- // Phone is idle, we'll finish out of this activity.
- final int callEndedDisplayDelay;
- switch (cause) {
- // When the local user hanged up the ongoing call, it is ok to dismiss the screen
- // soon. In other cases, we show the "hung up" screen longer.
- //
- // - For expected reasons we will use CALL_ENDED_LONG_DELAY.
- // -- when the peer hanged up the call
- // -- when the local user rejects the incoming call during the other ongoing call
- // (TODO: there may be other cases which should be in this category)
- //
- // - For other unexpected reasons, we will use CALL_ENDED_EXTRA_LONG_DELAY,
- // assuming the local user wants to confirm the disconnect reason.
- case LOCAL:
- callEndedDisplayDelay = CALL_ENDED_SHORT_DELAY;
- break;
- case NORMAL:
- case INCOMING_REJECTED:
- callEndedDisplayDelay = CALL_ENDED_LONG_DELAY;
- break;
- default:
- callEndedDisplayDelay = CALL_ENDED_EXTRA_LONG_DELAY;
- break;
- }
- mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
- mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
- callEndedDisplayDelay);
- }
-
- // Remove 3way timer (only meaningful for CDMA)
- // TODO: this call needs to happen in the CallController, not here.
- // (It should probably be triggered by the CallNotifier's onDisconnect method.)
- // mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
- }
-
- /**
- * Brings up the "MMI Started" dialog.
- */
- /* TODO: sort out MMI code (probably we should remove this method entirely). See also
- MMI handling code in onResume()
- private void onMMIInitiate(AsyncResult r) {
- if (VDBG) log("onMMIInitiate()... AsyncResult r = " + r);
-
- // Watch out: don't do this if we're not the foreground activity,
- // mainly since in the Dialog.show() might fail if we don't have a
- // valid window token any more...
- // (Note that this exact sequence can happen if you try to start
- // an MMI code while the radio is off or out of service.)
- if (!mIsForegroundActivity) {
- if (VDBG) log("Activity not in foreground! Bailing out...");
- return;
- }
-
- // Also, if any other dialog is up right now (presumably the
- // generic error dialog displaying the "Starting MMI..." message)
- // take it down before bringing up the real "MMI Started" dialog
- // in its place.
- dismissAllDialogs();
-
- MmiCode mmiCode = (MmiCode) r.result;
- if (VDBG) log(" - MmiCode: " + mmiCode);
-
- Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
- mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
- message, mMmiStartedDialog);
- }*/
-
- /**
- * Handles an MMI_CANCEL event, which is triggered by the button
- * (labeled either "OK" or "Cancel") on the "MMI Started" dialog.
- * @see PhoneUtils#cancelMmiCode(Phone)
- */
- private void onMMICancel() {
- if (VDBG) log("onMMICancel()...");
-
- // First of all, cancel the outstanding MMI code (if possible.)
- PhoneUtils.cancelMmiCode(mPhone);
-
- // Regardless of whether the current MMI code was cancelable, the
- // PhoneApp will get an MMI_COMPLETE event very soon, which will
- // take us to the MMI Complete dialog (see
- // PhoneUtils.displayMMIComplete().)
- //
- // But until that event comes in, we *don't* want to stay here on
- // the in-call screen, since we'll be visible in a
- // partially-constructed state as soon as the "MMI Started" dialog
- // gets dismissed. So let's forcibly bail out right now.
- if (DBG) log("onMMICancel: finishing InCallScreen...");
- dismissAllDialogs();
- endInCallScreenSession();
- }
-
- /**
- * Handles an MMI_COMPLETE event, which is triggered by telephony,
- * implying MMI
- */
- private void onMMIComplete(MmiCode mmiCode) {
- // Check the code to see if the request is ready to
- // finish, this includes any MMI state that is not
- // PENDING.
-
- // if phone is a CDMA phone display feature code completed message
- int phoneType = mPhone.getPhoneType();
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- PhoneUtils.displayMMIComplete(mPhone, mApp, mmiCode, null, null);
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
- if (mmiCode.getState() != MmiCode.State.PENDING) {
- if (DBG) log("Got MMI_COMPLETE, finishing InCallScreen...");
- dismissAllDialogs();
- endInCallScreenSession();
- }
- }
- }
-
- /**
- * Handles the POST_ON_DIAL_CHARS message from the Phone
- * (see our call to mPhone.setOnPostDialCharacter() above.)
- *
- * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle
- * "dialable" key events here in the InCallScreen: we do directly to the
- * Dialer UI instead. Similarly, we may now need to go directly to the
- * Dialer to handle POST_ON_DIAL_CHARS too.
- */
- private void handlePostOnDialChars(AsyncResult r, char ch) {
- Connection c = (Connection) r.result;
-
- if (c != null) {
- Connection.PostDialState state =
- (Connection.PostDialState) r.userObj;
-
- if (VDBG) log("handlePostOnDialChar: state = " +
- state + ", ch = " + ch);
-
- switch (state) {
- case STARTED:
- mDialer.stopLocalToneIfNeeded();
- if (mPauseInProgress) {
- /**
- * Note that on some devices, this will never happen,
- * because we will not ever enter the PAUSE state.
- */
- showPausePromptDialog(c, mPostDialStrAfterPause);
- }
- mPauseInProgress = false;
- mDialer.startLocalToneIfNeeded(ch);
-
- // TODO: is this needed, now that you can't actually
- // type DTMF chars or dial directly from here?
- // If so, we'd need to yank you out of the in-call screen
- // here too (and take you to the 12-key dialer in "in-call" mode.)
- // displayPostDialedChar(ch);
- break;
-
- case WAIT:
- // wait shows a prompt.
- if (DBG) log("handlePostOnDialChars: show WAIT prompt...");
- mDialer.stopLocalToneIfNeeded();
- String postDialStr = c.getRemainingPostDialString();
- showWaitPromptDialog(c, postDialStr);
- break;
-
- case WILD:
- if (DBG) log("handlePostOnDialChars: show WILD prompt");
- mDialer.stopLocalToneIfNeeded();
- showWildPromptDialog(c);
- break;
-
- case COMPLETE:
- mDialer.stopLocalToneIfNeeded();
- break;
-
- case PAUSE:
- // pauses for a brief period of time then continue dialing.
- mDialer.stopLocalToneIfNeeded();
- mPostDialStrAfterPause = c.getRemainingPostDialString();
- mPauseInProgress = true;
- break;
-
- default:
- break;
- }
- }
- }
-
- /**
- * Pop up an alert dialog with OK and Cancel buttons to allow user to
- * Accept or Reject the WAIT inserted as part of the Dial string.
- */
- private void showWaitPromptDialog(final Connection c, String postDialStr) {
- if (DBG) log("showWaitPromptDialogChoice: '" + postDialStr + "'...");
-
- Resources r = getResources();
- StringBuilder buf = new StringBuilder();
- buf.append(r.getText(R.string.wait_prompt_str));
- buf.append(postDialStr);
-
- // if (DBG) log("- mWaitPromptDialog = " + mWaitPromptDialog);
- if (mWaitPromptDialog != null) {
- if (DBG) log("- DISMISSING mWaitPromptDialog.");
- mWaitPromptDialog.dismiss(); // safe even if already dismissed
- mWaitPromptDialog = null;
- }
-
- mWaitPromptDialog = new AlertDialog.Builder(this)
- .setMessage(buf.toString())
- .setPositiveButton(R.string.pause_prompt_yes,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- if (DBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
- c.proceedAfterWaitChar();
- }
- })
- .setNegativeButton(R.string.pause_prompt_no,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- if (DBG) log("handle POST_DIAL_CANCELED!");
- c.cancelPostDial();
- }
- })
- .create();
- mWaitPromptDialog.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
-
- mWaitPromptDialog.show();
- }
-
- /**
- * Pop up an alert dialog which waits for 2 seconds for each P (Pause) Character entered
- * as part of the Dial String.
- */
- private void showPausePromptDialog(final Connection c, String postDialStrAfterPause) {
- Resources r = getResources();
- StringBuilder buf = new StringBuilder();
- buf.append(r.getText(R.string.pause_prompt_str));
- buf.append(postDialStrAfterPause);
-
- if (mPausePromptDialog != null) {
- if (DBG) log("- DISMISSING mPausePromptDialog.");
- mPausePromptDialog.dismiss(); // safe even if already dismissed
- mPausePromptDialog = null;
- }
-
- mPausePromptDialog = new AlertDialog.Builder(this)
- .setMessage(buf.toString())
- .create();
- mPausePromptDialog.show();
- // 2 second timer
- Message msg = Message.obtain(mHandler, EVENT_PAUSE_DIALOG_COMPLETE);
- mHandler.sendMessageDelayed(msg, PAUSE_PROMPT_DIALOG_TIMEOUT);
- }
-
- private View createWildPromptView() {
- LinearLayout result = new LinearLayout(this);
- result.setOrientation(LinearLayout.VERTICAL);
- result.setPadding(5, 5, 5, 5);
-
- LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
-
- TextView promptMsg = new TextView(this);
- promptMsg.setTextSize(14);
- promptMsg.setTypeface(Typeface.DEFAULT_BOLD);
- promptMsg.setText(getResources().getText(R.string.wild_prompt_str));
-
- result.addView(promptMsg, lp);
-
- mWildPromptText = new EditText(this);
- mWildPromptText.setKeyListener(DialerKeyListener.getInstance());
- mWildPromptText.setMovementMethod(null);
- mWildPromptText.setTextSize(14);
- mWildPromptText.setMaxLines(1);
- mWildPromptText.setHorizontallyScrolling(true);
- mWildPromptText.setBackgroundResource(android.R.drawable.editbox_background);
-
- LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- lp2.setMargins(0, 3, 0, 0);
-
- result.addView(mWildPromptText, lp2);
-
- return result;
- }
-
- private void showWildPromptDialog(final Connection c) {
- View v = createWildPromptView();
-
- if (mWildPromptDialog != null) {
- if (VDBG) log("- DISMISSING mWildPromptDialog.");
- mWildPromptDialog.dismiss(); // safe even if already dismissed
- mWildPromptDialog = null;
- }
-
- mWildPromptDialog = new AlertDialog.Builder(this)
- .setView(v)
- .setPositiveButton(
- R.string.send_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- if (VDBG) log("handle WILD_PROMPT_CHAR_ENTERED, proceed...");
- String replacement = null;
- if (mWildPromptText != null) {
- replacement = mWildPromptText.getText().toString();
- mWildPromptText = null;
- }
- c.proceedAfterWildChar(replacement);
- mApp.pokeUserActivity();
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- if (VDBG) log("handle POST_DIAL_CANCELED!");
- c.cancelPostDial();
- mApp.pokeUserActivity();
- }
- })
- .create();
- mWildPromptDialog.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
- mWildPromptDialog.show();
-
- mWildPromptText.requestFocus();
- }
-
- /**
- * Updates the state of the in-call UI based on the current state of
- * the Phone. This call has no effect if we're not currently the
- * foreground activity.
- *
- * This method is only allowed to be called from the UI thread (since it
- * manipulates our View hierarchy). If you need to update the screen from
- * some other thread, or if you just want to "post a request" for the screen
- * to be updated (rather than doing it synchronously), call
- * requestUpdateScreen() instead.
- *
- * Right now this method will update UI visibility immediately, with no animation.
- * TODO: have animate flag here and use it anywhere possible.
- */
- private void updateScreen() {
- if (DBG) log("updateScreen()...");
- final InCallScreenMode inCallScreenMode = mApp.inCallUiState.inCallScreenMode;
- if (VDBG) {
- PhoneConstants.State state = mCM.getState();
- log(" - phone state = " + state);
- log(" - inCallScreenMode = " + inCallScreenMode);
- }
-
- // Don't update anything if we're not in the foreground (there's
- // no point updating our UI widgets since we're not visible!)
- // Also note this check also ensures we won't update while we're
- // in the middle of pausing, which could cause a visible glitch in
- // the "activity ending" transition.
- if (!mIsForegroundActivity) {
- if (DBG) log("- updateScreen: not the foreground Activity! Bailing out...");
- return;
- }
-
- if (inCallScreenMode == InCallScreenMode.OTA_NORMAL) {
- if (DBG) log("- updateScreen: OTA call state NORMAL (NOT updating in-call UI)...");
- mCallCard.setVisibility(View.GONE);
- if (mApp.otaUtils != null) {
- mApp.otaUtils.otaShowProperScreen();
- } else {
- Log.w(LOG_TAG, "OtaUtils object is null, not showing any screen for that.");
- }
- return; // Return without updating in-call UI.
- } else if (inCallScreenMode == InCallScreenMode.OTA_ENDED) {
- if (DBG) log("- updateScreen: OTA call ended state (NOT updating in-call UI)...");
- mCallCard.setVisibility(View.GONE);
- // Wake up the screen when we get notification, good or bad.
- mApp.wakeUpScreen();
- if (mApp.cdmaOtaScreenState.otaScreenState
- == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION) {
- if (DBG) log("- updateScreen: OTA_STATUS_ACTIVATION");
- if (mApp.otaUtils != null) {
- if (DBG) log("- updateScreen: mApp.otaUtils is not null, "
- + "call otaShowActivationScreen");
- mApp.otaUtils.otaShowActivateScreen();
- }
- } else {
- if (DBG) log("- updateScreen: OTA Call end state for Dialogs");
- if (mApp.otaUtils != null) {
- if (DBG) log("- updateScreen: Show OTA Success Failure dialog");
- mApp.otaUtils.otaShowSuccessFailure();
- }
- }
- return; // Return without updating in-call UI.
- } else if (inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
- if (DBG) log("- updateScreen: manage conference mode (NOT updating in-call UI)...");
- mCallCard.setVisibility(View.GONE);
- updateManageConferencePanelIfNecessary();
- return; // Return without updating in-call UI.
- } else if (inCallScreenMode == InCallScreenMode.CALL_ENDED) {
- if (DBG) log("- updateScreen: call ended state...");
- // Continue with the rest of updateScreen() as usual, since we do
- // need to update the background (to the special "call ended" color)
- // and the CallCard (to show the "Call ended" label.)
- }
-
- if (DBG) log("- updateScreen: updating the in-call UI...");
- // Note we update the InCallTouchUi widget before the CallCard,
- // since the CallCard adjusts its size based on how much vertical
- // space the InCallTouchUi widget needs.
- updateInCallTouchUi();
- mCallCard.updateState(mCM);
-
- // If an incoming call is ringing, make sure the dialpad is
- // closed. (We do this to make sure we're not covering up the
- // "incoming call" UI.)
- if (mCM.getState() == PhoneConstants.State.RINGING) {
- if (mDialer.isOpened()) {
- Log.i(LOG_TAG, "During RINGING state we force hiding dialpad.");
- closeDialpadInternal(false); // don't do the "closing" animation
- }
-
- // At this point, we are guranteed that the dialer is closed.
- // This means that it is safe to clear out the "history" of DTMF digits
- // you may have typed into the previous call (so you don't see the
- // previous call's digits if you answer this call and then bring up the
- // dialpad.)
- //
- // TODO: it would be more precise to do this when you *answer* the
- // incoming call, rather than as soon as it starts ringing, but
- // the InCallScreen doesn't keep enough state right now to notice
- // that specific transition in onPhoneStateChanged().
- // TODO: This clears out the dialpad context as well so when a second
- // call comes in while a voicemail call is happening, the voicemail
- // dialpad will no longer have the "Voice Mail" context. It's a small
- // case so not terribly bad, but we need to maintain a better
- // call-to-callstate mapping before we can fix this.
- mDialer.clearDigits();
- }
-
-
- // Now that we're sure DTMF dialpad is in an appropriate state, reflect
- // the dialpad state into CallCard
- updateCallCardVisibilityPerDialerState(false);
-
- updateProgressIndication();
-
- // Forcibly take down all dialog if an incoming call is ringing.
- if (mCM.hasActiveRingingCall()) {
- dismissAllDialogs();
- } else {
- // Wait prompt dialog is not currently up. But it *should* be
- // up if the FG call has a connection in the WAIT state and
- // the phone isn't ringing.
- String postDialStr = null;
- List<Connection> fgConnections = mCM.getFgCallConnections();
- int phoneType = mCM.getFgPhone().getPhoneType();
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- Connection fgLatestConnection = mCM.getFgCallLatestConnection();
- if (mApp.cdmaPhoneCallState.getCurrentCallState() ==
- CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- for (Connection cn : fgConnections) {
- if ((cn != null) && (cn.getPostDialState() ==
- Connection.PostDialState.WAIT)) {
- cn.cancelPostDial();
- }
- }
- } else if ((fgLatestConnection != null)
- && (fgLatestConnection.getPostDialState() == Connection.PostDialState.WAIT)) {
- if(DBG) log("show the Wait dialog for CDMA");
- postDialStr = fgLatestConnection.getRemainingPostDialString();
- showWaitPromptDialog(fgLatestConnection, postDialStr);
- }
- } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
- || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
- for (Connection cn : fgConnections) {
- if ((cn != null) && (cn.getPostDialState() == Connection.PostDialState.WAIT)) {
- postDialStr = cn.getRemainingPostDialString();
- showWaitPromptDialog(cn, postDialStr);
- }
- }
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
- }
- }
-
- /**
- * (Re)synchronizes the onscreen UI with the current state of the
- * telephony framework.
- *
- * @return SyncWithPhoneStateStatus.SUCCESS if we successfully updated the UI, or
- * SyncWithPhoneStateStatus.PHONE_NOT_IN_USE if there was no phone state to sync
- * with (ie. the phone was completely idle). In the latter case, we
- * shouldn't even be in the in-call UI in the first place, and it's
- * the caller's responsibility to bail out of this activity by
- * calling endInCallScreenSession if appropriate.
- *
- * This method directly calls updateScreen() in the normal "phone is
- * in use" case, so there's no need for the caller to do so.
- */
- private SyncWithPhoneStateStatus syncWithPhoneState() {
- boolean updateSuccessful = false;
- if (DBG) log("syncWithPhoneState()...");
- if (DBG) PhoneUtils.dumpCallState(mPhone);
-
- // Make sure the Phone is "in use". (If not, we shouldn't be on
- // this screen in the first place.)
-
- // An active or just-ended OTA call counts as "in use".
- if (TelephonyCapabilities.supportsOtasp(mCM.getFgPhone())
- && ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
- || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED))) {
- // Even when OTA Call ends, need to show OTA End UI,
- // so return Success to allow UI update.
- return SyncWithPhoneStateStatus.SUCCESS;
- }
-
- // If an MMI code is running that also counts as "in use".
- //
- // TODO: We currently only call getPendingMmiCodes() for GSM
- // phones. (The code's been that way all along.) But CDMAPhone
- // does in fact implement getPendingMmiCodes(), so should we
- // check that here regardless of the phone type?
- boolean hasPendingMmiCodes =
- (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
- && !mPhone.getPendingMmiCodes().isEmpty();
-
- // Finally, it's also OK to stay here on the InCallScreen if we
- // need to display a progress indicator while something's
- // happening in the background.
- boolean showProgressIndication = mApp.inCallUiState.isProgressIndicationActive();
-
- boolean showScreenEvenAfterDisconnect = mApp.inCallUiState.showAlreadyDisconnectedState;
-
- if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()
- || hasPendingMmiCodes || showProgressIndication || showScreenEvenAfterDisconnect) {
- if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");
- updateScreen();
- return SyncWithPhoneStateStatus.SUCCESS;
- }
-
- Log.i(LOG_TAG, "syncWithPhoneState: phone is idle (shouldn't be here)");
- return SyncWithPhoneStateStatus.PHONE_NOT_IN_USE;
- }
-
-
-
- private void handleMissingVoiceMailNumber() {
- if (DBG) log("handleMissingVoiceMailNumber");
-
- final Message msg = Message.obtain(mHandler);
- msg.what = DONT_ADD_VOICEMAIL_NUMBER;
-
- final Message msg2 = Message.obtain(mHandler);
- msg2.what = ADD_VOICEMAIL_NUMBER;
-
- mMissingVoicemailDialog = new AlertDialog.Builder(this)
- .setTitle(R.string.no_vm_number)
- .setMessage(R.string.no_vm_number_msg)
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (VDBG) log("Missing voicemail AlertDialog: POSITIVE click...");
- msg.sendToTarget(); // see dontAddVoiceMailNumber()
- mApp.pokeUserActivity();
- }})
- .setNegativeButton(R.string.add_vm_number_str,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (VDBG) log("Missing voicemail AlertDialog: NEGATIVE click...");
- msg2.sendToTarget(); // see addVoiceMailNumber()
- mApp.pokeUserActivity();
- }})
- .setOnCancelListener(new OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- if (VDBG) log("Missing voicemail AlertDialog: CANCEL handler...");
- msg.sendToTarget(); // see dontAddVoiceMailNumber()
- mApp.pokeUserActivity();
- }})
- .create();
-
- // When the dialog is up, completely hide the in-call UI
- // underneath (which is in a partially-constructed state).
- mMissingVoicemailDialog.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
- mMissingVoicemailDialog.show();
- }
-
- private void addVoiceMailNumberPanel() {
- if (mMissingVoicemailDialog != null) {
- mMissingVoicemailDialog.dismiss();
- mMissingVoicemailDialog = null;
- }
- if (DBG) log("addVoiceMailNumberPanel: finishing InCallScreen...");
- endInCallScreenSession();
-
- if (DBG) log("show vm setting");
-
- // navigate to the Voicemail setting in the Call Settings activity.
- Intent intent = new Intent(CallFeaturesSetting.ACTION_ADD_VOICEMAIL);
- intent.setClass(this, CallFeaturesSetting.class);
- startActivity(intent);
- }
-
- private void dontAddVoiceMailNumber() {
- if (mMissingVoicemailDialog != null) {
- mMissingVoicemailDialog.dismiss();
- mMissingVoicemailDialog = null;
- }
- if (DBG) log("dontAddVoiceMailNumber: finishing InCallScreen...");
- endInCallScreenSession();
- }
-
- /**
- * Do some delayed cleanup after a Phone call gets disconnected.
- *
- * This method gets called a couple of seconds after any DISCONNECT
- * event from the Phone; it's triggered by the
- * DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
- *
- * If the Phone is totally idle right now, that means we've already
- * shown the "call ended" state for a couple of seconds, and it's now
- * time to endInCallScreenSession this activity.
- *
- * If the Phone is *not* idle right now, that probably means that one
- * call ended but the other line is still in use. In that case, do
- * nothing, and instead stay here on the InCallScreen.
- */
- private void delayedCleanupAfterDisconnect() {
- if (VDBG) log("delayedCleanupAfterDisconnect()... Phone state = " + mCM.getState());
-
- // Clean up any connections in the DISCONNECTED state.
- //
- // [Background: Even after a connection gets disconnected, its
- // Connection object still stays around, in the special
- // DISCONNECTED state. This is necessary because we we need the
- // caller-id information from that Connection to properly draw the
- // "Call ended" state of the CallCard.
- // But at this point we truly don't need that connection any
- // more, so tell the Phone that it's now OK to to clean up any
- // connections still in that state.]
- mCM.clearDisconnected();
-
- // There are two cases where we should *not* exit the InCallScreen:
- // (1) Phone is still in use
- // or
- // (2) There's an active progress indication (i.e. the "Retrying..."
- // progress dialog) that we need to continue to display.
-
- boolean stayHere = phoneIsInUse() || mApp.inCallUiState.isProgressIndicationActive();
-
- if (stayHere) {
- if (DBG) log("- delayedCleanupAfterDisconnect: staying on the InCallScreen...");
- } else {
- // Phone is idle! We should exit the in-call UI now.
- if (DBG) log("- delayedCleanupAfterDisconnect: phone is idle...");
-
- // And (finally!) exit from the in-call screen
- // (but not if we're already in the process of pausing...)
- if (mIsForegroundActivity) {
- if (DBG) log("- delayedCleanupAfterDisconnect: finishing InCallScreen...");
-
- // In some cases we finish the call by taking the user to the
- // Call Log. Otherwise, we simply call endInCallScreenSession,
- // which will take us back to wherever we came from.
- //
- // UI note: In eclair and earlier, we went to the Call Log
- // after outgoing calls initiated on the device, but never for
- // incoming calls. Now we do it for incoming calls too, as
- // long as the call was answered by the user. (We always go
- // back where you came from after a rejected or missed incoming
- // call.)
- //
- // And in any case, *never* go to the call log if we're in
- // emergency mode (i.e. if the screen is locked and a lock
- // pattern or PIN/password is set), or if we somehow got here
- // on a non-voice-capable device.
-
- if (VDBG) log("- Post-call behavior:");
- if (VDBG) log(" - mLastDisconnectCause = " + mLastDisconnectCause);
- if (VDBG) log(" - isPhoneStateRestricted() = " + isPhoneStateRestricted());
-
- // DisconnectCause values in the most common scenarios:
- // - INCOMING_MISSED: incoming ringing call times out, or the
- // other end hangs up while still ringing
- // - INCOMING_REJECTED: user rejects the call while ringing
- // - LOCAL: user hung up while a call was active (after
- // answering an incoming call, or after making an
- // outgoing call)
- // - NORMAL: the other end hung up (after answering an incoming
- // call, or after making an outgoing call)
-
- if ((mLastDisconnectCause != Connection.DisconnectCause.INCOMING_MISSED)
- && (mLastDisconnectCause != Connection.DisconnectCause.INCOMING_REJECTED)
- && !isPhoneStateRestricted()
- && PhoneGlobals.sVoiceCapable) {
- final Intent intent = mApp.createPhoneEndIntentUsingCallOrigin();
- ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
- R.anim.activity_close_enter, R.anim.activity_close_exit);
- if (VDBG) {
- log("- Show Call Log (or Dialtacts) after disconnect. Current intent: "
- + intent);
- }
- try {
- startActivity(intent, opts.toBundle());
- } catch (ActivityNotFoundException e) {
- // Don't crash if there's somehow no "Call log" at
- // all on this device.
- // (This should never happen, though, since we already
- // checked PhoneApp.sVoiceCapable above, and any
- // voice-capable device surely *should* have a call
- // log activity....)
- Log.w(LOG_TAG, "delayedCleanupAfterDisconnect: "
- + "transition to call log failed; intent = " + intent);
- // ...so just return back where we came from....
- }
- // Even if we did go to the call log, note that we still
- // call endInCallScreenSession (below) to make sure we don't
- // stay in the activity history.
- }
-
- }
- endInCallScreenSession();
-
- // Reset the call origin when the session ends and this in-call UI is being finished.
- mApp.setLatestActiveCallOrigin(null);
- }
- }
-
-
- /**
- * View.OnClickListener implementation.
- *
- * This method handles clicks from UI elements that use the
- * InCallScreen itself as their OnClickListener.
- *
- * Note: Currently this method is used only for a few special buttons:
- * - the mButtonManageConferenceDone "Back to call" button
- * - the "dim" effect for the secondary call photo in CallCard as the second "swap" button
- * - other OTASP-specific buttons managed by OtaUtils.java.
- *
- * *Most* in-call controls are handled by the handleOnscreenButtonClick() method, via the
- * InCallTouchUi widget.
- */
- @Override
- public void onClick(View view) {
- int id = view.getId();
- if (VDBG) log("onClick(View " + view + ", id " + id + ")...");
-
- switch (id) {
- case R.id.manage_done: // mButtonManageConferenceDone
- if (VDBG) log("onClick: mButtonManageConferenceDone...");
- // Hide the Manage Conference panel, return to NORMAL mode.
- setInCallScreenMode(InCallScreenMode.NORMAL);
- requestUpdateScreen();
- break;
-
- case R.id.dim_effect_for_secondary_photo:
- if (mInCallControlState.canSwap) {
- internalSwapCalls();
- }
- break;
-
- default:
- // Presumably one of the OTASP-specific buttons managed by
- // OtaUtils.java.
- // (TODO: It would be cleaner for the OtaUtils instance itself to
- // be the OnClickListener for its own buttons.)
-
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
- || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
- && mApp.otaUtils != null) {
- mApp.otaUtils.onClickHandler(id);
- } else {
- // Uh oh: we *should* only receive clicks here from the
- // buttons managed by OtaUtils.java, but if we're not in one
- // of the special OTASP modes, those buttons shouldn't have
- // been visible in the first place.
- Log.w(LOG_TAG,
- "onClick: unexpected click from ID " + id + " (View = " + view + ")");
- }
- break;
- }
-
- EventLog.writeEvent(EventLogTags.PHONE_UI_BUTTON_CLICK,
- (view instanceof TextView) ? ((TextView) view).getText() : "");
-
- // Clicking any onscreen UI element counts as explicit "user activity".
- mApp.pokeUserActivity();
- }
-
- private void onHoldClick() {
- final boolean hasActiveCall = mCM.hasActiveFgCall();
- final boolean hasHoldingCall = mCM.hasActiveBgCall();
- log("onHoldClick: hasActiveCall = " + hasActiveCall
- + ", hasHoldingCall = " + hasHoldingCall);
- boolean newHoldState;
- boolean holdButtonEnabled;
- if (hasActiveCall && !hasHoldingCall) {
- // There's only one line in use, and that line is active.
- PhoneUtils.switchHoldingAndActive(
- mCM.getFirstActiveBgCall()); // Really means "hold" in this state
- newHoldState = true;
- holdButtonEnabled = true;
- } else if (!hasActiveCall && hasHoldingCall) {
- // There's only one line in use, and that line is on hold.
- PhoneUtils.switchHoldingAndActive(
- mCM.getFirstActiveBgCall()); // Really means "unhold" in this state
- newHoldState = false;
- holdButtonEnabled = true;
- } else {
- // Either zero or 2 lines are in use; "hold/unhold" is meaningless.
- newHoldState = false;
- holdButtonEnabled = false;
- }
- // No need to forcibly update the onscreen UI; just wait for the
- // onPhoneStateChanged() callback. (This seems to be responsive
- // enough.)
-
- // Also, any time we hold or unhold, force the DTMF dialpad to close.
- closeDialpadInternal(true); // do the "closing" animation
- }
-
- /**
- * Toggles in-call audio between speaker and the built-in earpiece (or
- * wired headset.)
- */
- public void toggleSpeaker() {
- // TODO: Turning on the speaker seems to enable the mic
- // whether or not the "mute" feature is active!
- // Not sure if this is an feature of the telephony API
- // that I need to handle specially, or just a bug.
- boolean newSpeakerState = !PhoneUtils.isSpeakerOn(this);
- log("toggleSpeaker(): newSpeakerState = " + newSpeakerState);
-
- PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
-
- // And update the InCallTouchUi widget (since the "audio mode"
- // button might need to change its appearance based on the new
- // audio state.)
- updateInCallTouchUi();
- }
-
- /*
- * onMuteClick is called only when there is a foreground call
- */
- private void onMuteClick() {
- boolean newMuteState = !PhoneUtils.getMute();
- log("onMuteClick(): newMuteState = " + newMuteState);
- PhoneUtils.setMute(newMuteState);
- }
-
- /**
- * Handle a click on the "Open/Close dialpad" button.
- *
- * @see DTMFTwelveKeyDialer#openDialer(boolean)
- * @see DTMFTwelveKeyDialer#closeDialer(boolean)
- */
- private void onOpenCloseDialpad() {
- if (VDBG) log("onOpenCloseDialpad()...");
- if (mDialer.isOpened()) {
- closeDialpadInternal(true); // do the "closing" animation
- } else {
- openDialpadInternal(true); // do the "opening" animation
- }
- }
-
- /** Internal wrapper around {@link DTMFTwelveKeyDialer#openDialer(boolean)} */
- private void openDialpadInternal(boolean animate) {
- mDialer.openDialer(animate);
- // And update the InCallUiState (so that we'll restore the dialpad
- // to the correct state if we get paused/resumed).
- mApp.inCallUiState.showDialpad = true;
- }
-
- // Internal wrapper around DTMFTwelveKeyDialer.closeDialer()
- private void closeDialpadInternal(boolean animate) {
- mDialer.closeDialer(animate);
- // And update the InCallUiState (so that we'll restore the dialpad
- // to the correct state if we get paused/resumed).
- mApp.inCallUiState.showDialpad = false;
- }
-
- /**
- * Handles button clicks from the InCallTouchUi widget.
- */
- /* package */ void handleOnscreenButtonClick(int id) {
- if (DBG) log("handleOnscreenButtonClick(id " + id + ")...");
-
- switch (id) {
- // Actions while an incoming call is ringing:
- case R.id.incomingCallAnswer:
- internalAnswerCall();
- break;
- case R.id.incomingCallReject:
- hangupRingingCall();
- break;
- case R.id.incomingCallRespondViaSms:
- internalRespondViaSms();
- break;
-
- // The other regular (single-tap) buttons used while in-call:
- case R.id.holdButton:
- onHoldClick();
- break;
- case R.id.swapButton:
- internalSwapCalls();
- break;
- case R.id.endButton:
- internalHangup();
- break;
- case R.id.dialpadButton:
- onOpenCloseDialpad();
- break;
- case R.id.muteButton:
- onMuteClick();
- break;
- case R.id.addButton:
- PhoneUtils.startNewCall(mCM); // Fires off an ACTION_DIAL intent
- break;
- case R.id.mergeButton:
- case R.id.cdmaMergeButton:
- PhoneUtils.mergeCalls(mCM);
- break;
- case R.id.manageConferenceButton:
- // Show the Manage Conference panel.
- setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
- requestUpdateScreen();
- break;
-
- default:
- Log.w(LOG_TAG, "handleOnscreenButtonClick: unexpected ID " + id);
- break;
- }
-
- // Clicking any onscreen UI element counts as explicit "user activity".
- mApp.pokeUserActivity();
-
- // Just in case the user clicked a "stateful" UI element (like one
- // of the toggle buttons), we force the in-call buttons to update,
- // to make sure the user sees the *new* current state.
- //
- // Note that some in-call buttons will *not* immediately change the
- // state of the UI, namely those that send a request to the telephony
- // layer (like "Hold" or "End call".) For those buttons, the
- // updateInCallTouchUi() call here won't have any visible effect.
- // Instead, the UI will be updated eventually when the next
- // onPhoneStateChanged() event comes in and triggers an updateScreen()
- // call.
- //
- // TODO: updateInCallTouchUi() is overkill here; it would be
- // more efficient to update *only* the affected button(s).
- // (But this isn't a big deal since updateInCallTouchUi() is pretty
- // cheap anyway...)
- updateInCallTouchUi();
- }
-
- /**
- * Display a status or error indication to the user according to the
- * specified InCallUiState.CallStatusCode value.
- */
- private void showStatusIndication(CallStatusCode status) {
- switch (status) {
- case SUCCESS:
- // The InCallScreen does not need to display any kind of error indication,
- // so we shouldn't have gotten here in the first place.
- Log.wtf(LOG_TAG, "showStatusIndication: nothing to display");
- break;
-
- case POWER_OFF:
- // Radio is explictly powered off, presumably because the
- // device is in airplane mode.
- //
- // TODO: For now this UI is ultra-simple: we simply display
- // a message telling the user to turn off airplane mode.
- // But it might be nicer for the dialog to offer the option
- // to turn the radio on right there (and automatically retry
- // the call once network registration is complete.)
- showGenericErrorDialog(R.string.incall_error_power_off,
- true /* isStartupError */);
- break;
-
- case EMERGENCY_ONLY:
- // Only emergency numbers are allowed, but we tried to dial
- // a non-emergency number.
- // (This state is currently unused; see comments above.)
- showGenericErrorDialog(R.string.incall_error_emergency_only,
- true /* isStartupError */);
- break;
-
- case OUT_OF_SERVICE:
- // No network connection.
- showGenericErrorDialog(R.string.incall_error_out_of_service,
- true /* isStartupError */);
- break;
-
- case NO_PHONE_NUMBER_SUPPLIED:
- // The supplied Intent didn't contain a valid phone number.
- // (This is rare and should only ever happen with broken
- // 3rd-party apps.) For now just show a generic error.
- showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied,
- true /* isStartupError */);
- break;
-
- case DIALED_MMI:
- // Our initial phone number was actually an MMI sequence.
- // There's no real "error" here, but we do bring up the
- // a Toast (as requested of the New UI paradigm).
- //
- // In-call MMIs do not trigger the normal MMI Initiate
- // Notifications, so we should notify the user here.
- // Otherwise, the code in PhoneUtils.java should handle
- // user notifications in the form of Toasts or Dialogs.
- if (mCM.getState() == PhoneConstants.State.OFFHOOK) {
- Toast.makeText(mApp, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
- .show();
- }
- break;
-
- case CALL_FAILED:
- // We couldn't successfully place the call; there was some
- // failure in the telephony layer.
- // TODO: Need UI spec for this failure case; for now just
- // show a generic error.
- showGenericErrorDialog(R.string.incall_error_call_failed,
- true /* isStartupError */);
- break;
-
- case VOICEMAIL_NUMBER_MISSING:
- // We tried to call a voicemail: URI but the device has no
- // voicemail number configured.
- handleMissingVoiceMailNumber();
- break;
-
- case CDMA_CALL_LOST:
- // This status indicates that InCallScreen should display the
- // CDMA-specific "call lost" dialog. (If an outgoing call fails,
- // and the CDMA "auto-retry" feature is enabled, *and* the retried
- // call fails too, we display this specific dialog.)
- //
- // TODO: currently unused; see InCallUiState.needToShowCallLostDialog
- break;
-
- case EXITED_ECM:
- // This status indicates that InCallScreen needs to display a
- // warning that we're exiting ECM (emergency callback mode).
- showExitingECMDialog();
- break;
-
- default:
- throw new IllegalStateException(
- "showStatusIndication: unexpected status code: " + status);
- }
-
- // TODO: still need to make sure that pressing OK or BACK from
- // *any* of the dialogs we launch here ends up calling
- // inCallUiState.clearPendingCallStatusCode()
- // *and*
- // make sure the Dialog handles both OK *and* cancel by calling
- // endInCallScreenSession. (See showGenericErrorDialog() for an
- // example.)
- //
- // (showGenericErrorDialog() currently does this correctly,
- // but handleMissingVoiceMailNumber() probably needs to be fixed too.)
- //
- // Also need to make sure that bailing out of any of these dialogs by
- // pressing Home clears out the pending status code too. (If you do
- // that, neither the dialog's clickListener *or* cancelListener seems
- // to run...)
- }
-
- /**
- * Utility function to bring up a generic "error" dialog, and then bail
- * out of the in-call UI when the user hits OK (or the BACK button.)
- */
- private void showGenericErrorDialog(int resid, boolean isStartupError) {
- CharSequence msg = getResources().getText(resid);
- if (DBG) log("showGenericErrorDialog('" + msg + "')...");
-
- // create the clicklistener and cancel listener as needed.
- DialogInterface.OnClickListener clickListener;
- OnCancelListener cancelListener;
- if (isStartupError) {
- clickListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- bailOutAfterErrorDialog();
- }};
- cancelListener = new OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- bailOutAfterErrorDialog();
- }};
- } else {
- clickListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- delayedCleanupAfterDisconnect();
- }};
- cancelListener = new OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- delayedCleanupAfterDisconnect();
- }};
- }
-
- // TODO: Consider adding a setTitle() call here (with some generic
- // "failure" title?)
- mGenericErrorDialog = new AlertDialog.Builder(this)
- .setMessage(msg)
- .setPositiveButton(R.string.ok, clickListener)
- .setOnCancelListener(cancelListener)
- .create();
-
- // When the dialog is up, completely hide the in-call UI
- // underneath (which is in a partially-constructed state).
- mGenericErrorDialog.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
- mGenericErrorDialog.show();
- }
-
- private void showCallLostDialog() {
- if (DBG) log("showCallLostDialog()...");
-
- // Don't need to show the dialog if InCallScreen isn't in the forgeround
- if (!mIsForegroundActivity) {
- if (DBG) log("showCallLostDialog: not the foreground Activity! Bailing out...");
- return;
- }
-
- // Don't need to show the dialog again, if there is one already.
- if (mCallLostDialog != null) {
- if (DBG) log("showCallLostDialog: There is a mCallLostDialog already.");
- return;
- }
-
- mCallLostDialog = new AlertDialog.Builder(this)
- .setMessage(R.string.call_lost)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .create();
- mCallLostDialog.show();
- }
-
- /**
- * Displays the "Exiting ECM" warning dialog.
- *
- * Background: If the phone is currently in ECM (Emergency callback
- * mode) and we dial a non-emergency number, that automatically
- * *cancels* ECM. (That behavior comes from CdmaCallTracker.dial().)
- * When that happens, we need to warn the user that they're no longer
- * in ECM (bug 4207607.)
- *
- * So bring up a dialog explaining what's happening. There's nothing
- * for the user to do, by the way; we're simply providing an
- * indication that they're exiting ECM. We *could* use a Toast for
- * this, but toasts are pretty easy to miss, so instead use a dialog
- * with a single "OK" button.
- *
- * TODO: it's ugly that the code here has to make assumptions about
- * the behavior of the telephony layer (namely that dialing a
- * non-emergency number while in ECM causes us to exit ECM.)
- *
- * Instead, this warning dialog should really be triggered by our
- * handler for the
- * TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED intent in
- * PhoneApp.java. But that won't work until that intent also
- * includes a *reason* why we're exiting ECM, since we need to
- * display this dialog when exiting ECM because of an outgoing call,
- * but NOT if we're exiting ECM because the user manually turned it
- * off via the EmergencyCallbackModeExitDialog.
- *
- * Or, it might be simpler to just have outgoing non-emergency calls
- * *not* cancel ECM. That way the UI wouldn't have to do anything
- * special here.
- */
- private void showExitingECMDialog() {
- Log.i(LOG_TAG, "showExitingECMDialog()...");
-
- if (mExitingECMDialog != null) {
- if (DBG) log("- DISMISSING mExitingECMDialog.");
- mExitingECMDialog.dismiss(); // safe even if already dismissed
- mExitingECMDialog = null;
- }
-
- // When the user dismisses the "Exiting ECM" dialog, we clear out
- // the pending call status code field (since we're done with this
- // dialog), but do *not* bail out of the InCallScreen.
-
- final InCallUiState inCallUiState = mApp.inCallUiState;
- DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- inCallUiState.clearPendingCallStatusCode();
- }};
- OnCancelListener cancelListener = new OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- inCallUiState.clearPendingCallStatusCode();
- }};
-
- // Ultra-simple AlertDialog with only an OK button:
- mExitingECMDialog = new AlertDialog.Builder(this)
- .setMessage(R.string.progress_dialog_exiting_ecm)
- .setPositiveButton(R.string.ok, clickListener)
- .setOnCancelListener(cancelListener)
- .create();
- mExitingECMDialog.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
- mExitingECMDialog.show();
- }
-
- private void bailOutAfterErrorDialog() {
- if (mGenericErrorDialog != null) {
- if (DBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
- mGenericErrorDialog.dismiss();
- mGenericErrorDialog = null;
- }
- if (DBG) log("bailOutAfterErrorDialog(): end InCallScreen session...");
-
- // Now that the user has dismissed the error dialog (presumably by
- // either hitting the OK button or pressing Back, we can now reset
- // the pending call status code field.
- //
- // (Note that the pending call status is NOT cleared simply
- // by the InCallScreen being paused or finished, since the resulting
- // dialog is supposed to persist across orientation changes or if the
- // screen turns off.)
- //
- // See the "Error / diagnostic indications" section of
- // InCallUiState.java for more detailed info about the
- // pending call status code field.
- final InCallUiState inCallUiState = mApp.inCallUiState;
- inCallUiState.clearPendingCallStatusCode();
-
- // Force the InCallScreen to truly finish(), rather than just
- // moving it to the back of the activity stack (which is what
- // our finish() method usually does.)
- // This is necessary to avoid an obscure scenario where the
- // InCallScreen can get stuck in an inconsistent state, somehow
- // causing a *subsequent* outgoing call to fail (bug 4172599).
- endInCallScreenSession(true /* force a real finish() call */);
- }
-
- /**
- * Dismisses (and nulls out) all persistent Dialogs managed
- * by the InCallScreen. Useful if (a) we're about to bring up
- * a dialog and want to pre-empt any currently visible dialogs,
- * or (b) as a cleanup step when the Activity is going away.
- */
- private void dismissAllDialogs() {
- if (DBG) log("dismissAllDialogs()...");
-
- // Note it's safe to dismiss() a dialog that's already dismissed.
- // (Even if the AlertDialog object(s) below are still around, it's
- // possible that the actual dialog(s) may have already been
- // dismissed by the user.)
-
- if (mMissingVoicemailDialog != null) {
- if (VDBG) log("- DISMISSING mMissingVoicemailDialog.");
- mMissingVoicemailDialog.dismiss();
- mMissingVoicemailDialog = null;
- }
- if (mMmiStartedDialog != null) {
- if (VDBG) log("- DISMISSING mMmiStartedDialog.");
- mMmiStartedDialog.dismiss();
- mMmiStartedDialog = null;
- }
- if (mGenericErrorDialog != null) {
- if (VDBG) log("- DISMISSING mGenericErrorDialog.");
- mGenericErrorDialog.dismiss();
- mGenericErrorDialog = null;
- }
- if (mSuppServiceFailureDialog != null) {
- if (VDBG) log("- DISMISSING mSuppServiceFailureDialog.");
- mSuppServiceFailureDialog.dismiss();
- mSuppServiceFailureDialog = null;
- }
- if (mWaitPromptDialog != null) {
- if (VDBG) log("- DISMISSING mWaitPromptDialog.");
- mWaitPromptDialog.dismiss();
- mWaitPromptDialog = null;
- }
- if (mWildPromptDialog != null) {
- if (VDBG) log("- DISMISSING mWildPromptDialog.");
- mWildPromptDialog.dismiss();
- mWildPromptDialog = null;
- }
- if (mCallLostDialog != null) {
- if (VDBG) log("- DISMISSING mCallLostDialog.");
- mCallLostDialog.dismiss();
- mCallLostDialog = null;
- }
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
- || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
- && mApp.otaUtils != null) {
- mApp.otaUtils.dismissAllOtaDialogs();
- }
- if (mPausePromptDialog != null) {
- if (DBG) log("- DISMISSING mPausePromptDialog.");
- mPausePromptDialog.dismiss();
- mPausePromptDialog = null;
- }
- if (mExitingECMDialog != null) {
- if (DBG) log("- DISMISSING mExitingECMDialog.");
- mExitingECMDialog.dismiss();
- mExitingECMDialog = null;
- }
- }
-
- /**
- * Updates the state of the onscreen "progress indication" used in
- * some (relatively rare) scenarios where we need to wait for
- * something to happen before enabling the in-call UI.
- *
- * If necessary, this method will cause a ProgressDialog (i.e. a
- * spinning wait cursor) to be drawn *on top of* whatever the current
- * state of the in-call UI is.
- *
- * @see InCallUiState.ProgressIndicationType
- */
- private void updateProgressIndication() {
- // If an incoming call is ringing, that takes priority over any
- // possible value of inCallUiState.progressIndication.
- if (mCM.hasActiveRingingCall()) {
- dismissProgressIndication();
- return;
- }
-
- // Otherwise, put up a progress indication if indicated by the
- // inCallUiState.progressIndication field.
- final InCallUiState inCallUiState = mApp.inCallUiState;
- switch (inCallUiState.getProgressIndication()) {
- case NONE:
- // No progress indication necessary, so make sure it's dismissed.
- dismissProgressIndication();
- break;
-
- case TURNING_ON_RADIO:
- showProgressIndication(
- R.string.emergency_enable_radio_dialog_title,
- R.string.emergency_enable_radio_dialog_message);
- break;
-
- case RETRYING:
- showProgressIndication(
- R.string.emergency_enable_radio_dialog_title,
- R.string.emergency_enable_radio_dialog_retry);
- break;
-
- default:
- Log.wtf(LOG_TAG, "updateProgressIndication: unexpected value: "
- + inCallUiState.getProgressIndication());
- dismissProgressIndication();
- break;
- }
- }
-
- /**
- * Show an onscreen "progress indication" with the specified title and message.
- */
- private void showProgressIndication(int titleResId, int messageResId) {
- if (DBG) log("showProgressIndication(message " + messageResId + ")...");
-
- // TODO: make this be a no-op if the progress indication is
- // already visible with the exact same title and message.
-
- dismissProgressIndication(); // Clean up any prior progress indication
- mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setTitle(getText(titleResId));
- mProgressDialog.setMessage(getText(messageResId));
- mProgressDialog.setIndeterminate(true);
- mProgressDialog.setCancelable(false);
- mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
- mProgressDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
- mProgressDialog.show();
- }
-
- /**
- * Dismiss the onscreen "progress indication" (if present).
- */
- private void dismissProgressIndication() {
- if (DBG) log("dismissProgressIndication()...");
- if (mProgressDialog != null) {
- mProgressDialog.dismiss(); // safe even if already dismissed
- mProgressDialog = null;
- }
- }
-
-
- //
- // Helper functions for answering incoming calls.
- //
-
- /**
- * Answer a ringing call. This method does nothing if there's no
- * ringing or waiting call.
- */
- private void internalAnswerCall() {
- if (DBG) log("internalAnswerCall()...");
- // if (DBG) PhoneUtils.dumpCallState(mPhone);
-
- final boolean hasRingingCall = mCM.hasActiveRingingCall();
-
- if (hasRingingCall) {
- Phone phone = mCM.getRingingPhone();
- Call ringing = mCM.getFirstActiveRingingCall();
- int phoneType = phone.getPhoneType();
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- if (DBG) log("internalAnswerCall: answering (CDMA)...");
- if (mCM.hasActiveFgCall()
- && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
- // The incoming call is CDMA call and the ongoing
- // call is a SIP call. The CDMA network does not
- // support holding an active call, so there's no
- // way to swap between a CDMA call and a SIP call.
- // So for now, we just don't allow a CDMA call and
- // a SIP call to be active at the same time.We'll
- // "answer incoming, end ongoing" in this case.
- if (DBG) log("internalAnswerCall: answer "
- + "CDMA incoming and end SIP ongoing");
- PhoneUtils.answerAndEndActive(mCM, ringing);
- } else {
- PhoneUtils.answerCall(ringing);
- }
- } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
- if (DBG) log("internalAnswerCall: answering (SIP)...");
- if (mCM.hasActiveFgCall()
- && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
- // Similar to the PHONE_TYPE_CDMA handling.
- // The incoming call is SIP call and the ongoing
- // call is a CDMA call. The CDMA network does not
- // support holding an active call, so there's no
- // way to swap between a CDMA call and a SIP call.
- // So for now, we just don't allow a CDMA call and
- // a SIP call to be active at the same time.We'll
- // "answer incoming, end ongoing" in this case.
- if (DBG) log("internalAnswerCall: answer "
- + "SIP incoming and end CDMA ongoing");
- PhoneUtils.answerAndEndActive(mCM, ringing);
- } else {
- PhoneUtils.answerCall(ringing);
- }
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
- if (DBG) log("internalAnswerCall: answering (GSM)...");
- // GSM: this is usually just a wrapper around
- // PhoneUtils.answerCall(), *but* we also need to do
- // something special for the "both lines in use" case.
-
- final boolean hasActiveCall = mCM.hasActiveFgCall();
- final boolean hasHoldingCall = mCM.hasActiveBgCall();
-
- if (hasActiveCall && hasHoldingCall) {
- if (DBG) log("internalAnswerCall: answering (both lines in use!)...");
- // The relatively rare case where both lines are
- // already in use. We "answer incoming, end ongoing"
- // in this case, according to the current UI spec.
- PhoneUtils.answerAndEndActive(mCM, ringing);
-
- // Alternatively, we could use
- // PhoneUtils.answerAndEndHolding(mPhone);
- // here to end the on-hold call instead.
- } else {
- if (DBG) log("internalAnswerCall: answering...");
- PhoneUtils.answerCall(ringing); // Automatically holds the current active call,
- // if there is one
- }
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
-
- // Call origin is valid only with outgoing calls. Disable it on incoming calls.
- mApp.setLatestActiveCallOrigin(null);
- }
- }
-
- /**
- * Hang up the ringing call (aka "Don't answer").
- */
- /* package */ void hangupRingingCall() {
- if (DBG) log("hangupRingingCall()...");
- if (VDBG) PhoneUtils.dumpCallManager();
- // In the rare case when multiple calls are ringing, the UI policy
- // it to always act on the first ringing call.
- PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
- }
-
- /**
- * Silence the ringer (if an incoming call is ringing.)
- */
- private void internalSilenceRinger() {
- if (DBG) log("internalSilenceRinger()...");
- final CallNotifier notifier = mApp.notifier;
- if (notifier.isRinging()) {
- // ringer is actually playing, so silence it.
- notifier.silenceRinger();
- }
- }
-
- /**
- * Respond via SMS to the ringing call.
- * @see RespondViaSmsManager
- */
- private void internalRespondViaSms() {
- log("internalRespondViaSms()...");
- if (VDBG) PhoneUtils.dumpCallManager();
-
- // In the rare case when multiple calls are ringing, the UI policy
- // it to always act on the first ringing call.
- Call ringingCall = mCM.getFirstActiveRingingCall();
-
- // Silence the ringer, since it would be distracting while you're trying
- // to pick a response. (Note that we'll restart the ringer if you bail
- // out of the popup, though; see RespondViaSmsCancelListener.)
- internalSilenceRinger();
- }
-
- /**
- * Hang up the current active call.
- */
- private void internalHangup() {
- PhoneConstants.State state = mCM.getState();
- log("internalHangup()... phone state = " + state);
-
- // Regardless of the phone state, issue a hangup request.
- // (If the phone is already idle, this call will presumably have no
- // effect (but also see the note below.))
- PhoneUtils.hangup(mCM);
-
- // If the user just hung up the only active call, we'll eventually exit
- // the in-call UI after the following sequence:
- // - When the hangup() succeeds, we'll get a DISCONNECT event from
- // the telephony layer (see onDisconnect()).
- // - We immediately switch to the "Call ended" state (see the "delayed
- // bailout" code path in onDisconnect()) and also post a delayed
- // DELAYED_CLEANUP_AFTER_DISCONNECT message.
- // - When the DELAYED_CLEANUP_AFTER_DISCONNECT message comes in (see
- // delayedCleanupAfterDisconnect()) we do some final cleanup, and exit
- // this activity unless the phone is still in use (i.e. if there's
- // another call, or something else going on like an active MMI
- // sequence.)
-
- if (state == PhoneConstants.State.IDLE) {
- // The user asked us to hang up, but the phone was (already) idle!
- Log.w(LOG_TAG, "internalHangup(): phone is already IDLE!");
-
- // This is rare, but can happen in a few cases:
- // (a) If the user quickly double-taps the "End" button. In this case
- // we'll see that 2nd press event during the brief "Call ended"
- // state (where the phone is IDLE), or possibly even before the
- // radio has been able to respond to the initial hangup request.
- // (b) More rarely, this can happen if the user presses "End" at the
- // exact moment that the call ends on its own (like because of the
- // other person hanging up.)
- // (c) Finally, this could also happen if we somehow get stuck here on
- // the InCallScreen with the phone truly idle, perhaps due to a
- // bug where we somehow *didn't* exit when the phone became idle
- // in the first place.
-
- // TODO: as a "safety valve" for case (c), consider immediately
- // bailing out of the in-call UI right here. (The user can always
- // bail out by pressing Home, of course, but they'll probably try
- // pressing End first.)
- //
- // Log.i(LOG_TAG, "internalHangup(): phone is already IDLE! Bailing out...");
- // endInCallScreenSession();
- }
- }
-
- /**
- * InCallScreen-specific wrapper around PhoneUtils.switchHoldingAndActive().
- */
- private void internalSwapCalls() {
- if (DBG) log("internalSwapCalls()...");
-
- // Any time we swap calls, force the DTMF dialpad to close.
- // (We want the regular in-call UI to be visible right now, so the
- // user can clearly see which call is now in the foreground.)
- closeDialpadInternal(true); // do the "closing" animation
-
- // Also, clear out the "history" of DTMF digits you typed, to make
- // sure you don't see digits from call #1 while call #2 is active.
- // (Yes, this does mean that swapping calls twice will cause you
- // to lose any previous digits from the current call; see the TODO
- // comment on DTMFTwelvKeyDialer.clearDigits() for more info.)
- mDialer.clearDigits();
- }
-
- /**
- * Sets the current high-level "mode" of the in-call UI.
- *
- * NOTE: if newMode is CALL_ENDED, the caller is responsible for
- * posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
- * sure the "call ended" state goes away after a couple of seconds.
- *
- * Note this method does NOT refresh of the onscreen UI; the caller is
- * responsible for calling updateScreen() or requestUpdateScreen() if
- * necessary.
- */
- private void setInCallScreenMode(InCallScreenMode newMode) {
- if (DBG) log("setInCallScreenMode: " + newMode);
- mApp.inCallUiState.inCallScreenMode = newMode;
-
- switch (newMode) {
- case MANAGE_CONFERENCE:
- if (!PhoneUtils.isConferenceCall(mCM.getActiveFgCall())) {
- Log.w(LOG_TAG, "MANAGE_CONFERENCE: no active conference call!");
- // Hide the Manage Conference panel, return to NORMAL mode.
- setInCallScreenMode(InCallScreenMode.NORMAL);
- return;
- }
- List<Connection> connections = mCM.getFgCallConnections();
- // There almost certainly will be > 1 connection,
- // since isConferenceCall() just returned true.
- if ((connections == null) || (connections.size() <= 1)) {
- Log.w(LOG_TAG,
- "MANAGE_CONFERENCE: Bogus TRUE from isConferenceCall(); connections = "
- + connections);
- // Hide the Manage Conference panel, return to NORMAL mode.
- setInCallScreenMode(InCallScreenMode.NORMAL);
- return;
- }
-
- // TODO: Don't do this here. The call to
- // initManageConferencePanel() should instead happen
- // automagically in ManageConferenceUtils the very first
- // time you call updateManageConferencePanel() or
- // setPanelVisible(true).
- mManageConferenceUtils.initManageConferencePanel(); // if necessary
-
- mManageConferenceUtils.updateManageConferencePanel(connections);
-
- // The "Manage conference" UI takes up the full main frame,
- // replacing the CallCard PopupWindow.
- mManageConferenceUtils.setPanelVisible(true);
-
- // Start the chronometer.
- // TODO: Similarly, we shouldn't expose startConferenceTime()
- // and stopConferenceTime(); the ManageConferenceUtils
- // class ought to manage the conferenceTime widget itself
- // based on setPanelVisible() calls.
-
- // Note: there is active Fg call since we are in conference call
- long callDuration =
- mCM.getActiveFgCall().getEarliestConnection().getDurationMillis();
- mManageConferenceUtils.startConferenceTime(
- SystemClock.elapsedRealtime() - callDuration);
-
- // No need to close the dialer here, since the Manage
- // Conference UI will just cover it up anyway.
-
- break;
-
- case CALL_ENDED:
- case NORMAL:
- mManageConferenceUtils.setPanelVisible(false);
- mManageConferenceUtils.stopConferenceTime();
- break;
-
- case OTA_NORMAL:
- mApp.otaUtils.setCdmaOtaInCallScreenUiState(
- OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL);
- break;
-
- case OTA_ENDED:
- mApp.otaUtils.setCdmaOtaInCallScreenUiState(
- OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
- break;
-
- case UNDEFINED:
- // Set our Activities intent to ACTION_UNDEFINED so
- // that if we get resumed after we've completed a call
- // the next call will not cause checkIsOtaCall to
- // return true.
- //
- // TODO(OTASP): update these comments
- //
- // With the framework as of October 2009 the sequence below
- // causes the framework to call onResume, onPause, onNewIntent,
- // onResume. If we don't call setIntent below then when the
- // first onResume calls checkIsOtaCall via checkOtaspStateOnResume it will
- // return true and the Activity will be confused.
- //
- // 1) Power up Phone A
- // 2) Place *22899 call and activate Phone A
- // 3) Press the power key on Phone A to turn off the display
- // 4) Call Phone A from Phone B answering Phone A
- // 5) The screen will be blank (Should be normal InCallScreen)
- // 6) Hang up the Phone B
- // 7) Phone A displays the activation screen.
- //
- // Step 3 is the critical step to cause the onResume, onPause
- // onNewIntent, onResume sequence. If step 3 is skipped the
- // sequence will be onNewIntent, onResume and all will be well.
- setIntent(new Intent(ACTION_UNDEFINED));
-
- // Cleanup Ota Screen if necessary and set the panel
- // to VISIBLE.
- if (mCM.getState() != PhoneConstants.State.OFFHOOK) {
- if (mApp.otaUtils != null) {
- mApp.otaUtils.cleanOtaScreen(true);
- }
- } else {
- log("WARNING: Setting mode to UNDEFINED but phone is OFFHOOK,"
- + " skip cleanOtaScreen.");
- }
- break;
- }
- }
-
- /**
- * @return true if the "Manage conference" UI is currently visible.
- */
- /* package */ boolean isManageConferenceMode() {
- return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE);
- }
-
- /**
- * Checks if the "Manage conference" UI needs to be updated.
- * If the state of the current conference call has changed
- * since our previous call to updateManageConferencePanel()),
- * do a fresh update. Also, if the current call is no longer a
- * conference call at all, bail out of the "Manage conference" UI and
- * return to InCallScreenMode.NORMAL mode.
- */
- private void updateManageConferencePanelIfNecessary() {
- if (VDBG) log("updateManageConferencePanelIfNecessary: " + mCM.getActiveFgCall() + "...");
-
- List<Connection> connections = mCM.getFgCallConnections();
- if (connections == null) {
- if (VDBG) log("==> no connections on foreground call!");
- // Hide the Manage Conference panel, return to NORMAL mode.
- setInCallScreenMode(InCallScreenMode.NORMAL);
- SyncWithPhoneStateStatus status = syncWithPhoneState();
- if (status != SyncWithPhoneStateStatus.SUCCESS) {
- Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
- // We shouldn't even be in the in-call UI in the first
- // place, so bail out:
- if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 1");
- endInCallScreenSession();
- return;
- }
- return;
- }
-
- int numConnections = connections.size();
- if (numConnections <= 1) {
- if (VDBG) log("==> foreground call no longer a conference!");
- // Hide the Manage Conference panel, return to NORMAL mode.
- setInCallScreenMode(InCallScreenMode.NORMAL);
- SyncWithPhoneStateStatus status = syncWithPhoneState();
- if (status != SyncWithPhoneStateStatus.SUCCESS) {
- Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
- // We shouldn't even be in the in-call UI in the first
- // place, so bail out:
- if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 2");
- endInCallScreenSession();
- return;
- }
- return;
- }
-
- // TODO: the test to see if numConnections has changed can go in
- // updateManageConferencePanel(), rather than here.
- if (numConnections != mManageConferenceUtils.getNumCallersInConference()) {
- if (VDBG) log("==> Conference size has changed; need to rebuild UI!");
- mManageConferenceUtils.updateManageConferencePanel(connections);
- }
- }
-
- /**
- * Updates {@link #mCallCard}'s visibility state per DTMF dialpad visibility. They
- * cannot be shown simultaneously and thus we should reflect DTMF dialpad visibility into
- * another.
- *
- * Note: During OTA calls or users' managing conference calls, we should *not* call this method
- * but manually manage both visibility.
- *
- * @see #updateScreen()
- */
- private void updateCallCardVisibilityPerDialerState(boolean animate) {
- // We need to hide the CallCard while the dialpad is visible.
- if (isDialerOpened()) {
- if (VDBG) {
- log("- updateCallCardVisibilityPerDialerState(animate="
- + animate + "): dialpad open, hide mCallCard...");
- }
- if (animate) {
- AnimationUtils.Fade.hide(mCallCard, View.GONE);
- } else {
- mCallCard.setVisibility(View.GONE);
- }
- } else {
- // Dialpad is dismissed; bring back the CallCard if it's supposed to be visible.
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.NORMAL)
- || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.CALL_ENDED)) {
- if (VDBG) {
- log("- updateCallCardVisibilityPerDialerState(animate="
- + animate + "): dialpad dismissed, show mCallCard...");
- }
- if (animate) {
- AnimationUtils.Fade.show(mCallCard);
- } else {
- mCallCard.setVisibility(View.VISIBLE);
- }
- }
- }
- }
-
- /**
- * @see DTMFTwelveKeyDialer#isOpened()
- */
- /* package */ boolean isDialerOpened() {
- return (mDialer != null && mDialer.isOpened());
- }
-
- /**
- * Called any time the DTMF dialpad is opened.
- * @see DTMFTwelveKeyDialer#openDialer(boolean)
- */
- /* package */ void onDialerOpen(boolean animate) {
- if (DBG) log("onDialerOpen()...");
-
- // Update the in-call touch UI.
- updateInCallTouchUi();
-
- // Update CallCard UI, which depends on the dialpad.
- updateCallCardVisibilityPerDialerState(animate);
-
- // This counts as explicit "user activity".
- mApp.pokeUserActivity();
-
- //If on OTA Call, hide OTA Screen
- // TODO: This may not be necessary, now that the dialpad is
- // always visible in OTA mode.
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
- || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
- && mApp.otaUtils != null) {
- mApp.otaUtils.hideOtaScreen();
- }
- }
-
- /**
- * Called any time the DTMF dialpad is closed.
- * @see DTMFTwelveKeyDialer#closeDialer(boolean)
- */
- /* package */ void onDialerClose(boolean animate) {
- if (DBG) log("onDialerClose()...");
-
- // OTA-specific cleanup upon closing the dialpad.
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
- || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
- || ((mApp.cdmaOtaScreenState != null)
- && (mApp.cdmaOtaScreenState.otaScreenState ==
- CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
- if (mApp.otaUtils != null) {
- mApp.otaUtils.otaShowProperScreen();
- }
- }
-
- // Update the in-call touch UI.
- updateInCallTouchUi();
-
- // Update CallCard UI, which depends on the dialpad.
- updateCallCardVisibilityPerDialerState(animate);
-
- // This counts as explicit "user activity".
- mApp.pokeUserActivity();
- }
-
- /**
- * Determines when we can dial DTMF tones.
- */
- /* package */ boolean okToDialDTMFTones() {
- final boolean hasRingingCall = mCM.hasActiveRingingCall();
- final Call.State fgCallState = mCM.getActiveFgCallState();
-
- // We're allowed to send DTMF tones when there's an ACTIVE
- // foreground call, and not when an incoming call is ringing
- // (since DTMF tones are useless in that state), or if the
- // Manage Conference UI is visible (since the tab interferes
- // with the "Back to call" button.)
-
- // We can also dial while in ALERTING state because there are
- // some connections that never update to an ACTIVE state (no
- // indication from the network).
- boolean canDial =
- (fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
- && !hasRingingCall
- && (mApp.inCallUiState.inCallScreenMode != InCallScreenMode.MANAGE_CONFERENCE);
-
- if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
- ", ringing state: " + hasRingingCall +
- ", call screen mode: " + mApp.inCallUiState.inCallScreenMode +
- ", result: " + canDial);
-
- return canDial;
- }
-
- /**
- * @return true if the in-call DTMF dialpad should be available to the
- * user, given the current state of the phone and the in-call UI.
- * (This is used to control the enabledness of the "Show
- * dialpad" onscreen button; see InCallControlState.dialpadEnabled.)
- */
- /* package */ boolean okToShowDialpad() {
- // Very similar to okToDialDTMFTones(), but allow DIALING here.
- final Call.State fgCallState = mCM.getActiveFgCallState();
- return okToDialDTMFTones() || (fgCallState == Call.State.DIALING);
- }
-
- /**
- * Initializes the in-call touch UI on devices that need it.
- */
- private void initInCallTouchUi() {
- if (DBG) log("initInCallTouchUi()...");
- // TODO: we currently use the InCallTouchUi widget in at least
- // some states on ALL platforms. But if some devices ultimately
- // end up not using *any* onscreen touch UI, we should make sure
- // to not even inflate the InCallTouchUi widget on those devices.
- mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
- mInCallTouchUi.setInCallScreenInstance(this);
-
- // RespondViaSmsManager implements the "Respond via SMS"
- // feature that's triggered from the incoming call widget.
- mRespondViaSmsManager = new RespondViaSmsManager();
- mRespondViaSmsManager.setInCallScreenInstance(this);
- }
-
- /**
- * Updates the state of the in-call touch UI.
- */
- private void updateInCallTouchUi() {
- if (mInCallTouchUi != null) {
- mInCallTouchUi.updateState(mCM);
- }
- }
-
- /**
- * @return the InCallTouchUi widget
- */
- /* package */ InCallTouchUi getInCallTouchUi() {
- return mInCallTouchUi;
- }
-
- /**
- * Posts a handler message telling the InCallScreen to refresh the
- * onscreen in-call UI.
- *
- * This is just a wrapper around updateScreen(), for use by the
- * rest of the phone app or from a thread other than the UI thread.
- *
- * updateScreen() is a no-op if the InCallScreen is not the foreground
- * activity, so it's safe to call this whether or not the InCallScreen
- * is currently visible.
- */
- /* package */ void requestUpdateScreen() {
- if (DBG) log("requestUpdateScreen()...");
- mHandler.removeMessages(REQUEST_UPDATE_SCREEN);
- mHandler.sendEmptyMessage(REQUEST_UPDATE_SCREEN);
- }
-
- /**
- * @return true if we're in restricted / emergency dialing only mode.
- */
- public boolean isPhoneStateRestricted() {
- // TODO: This needs to work IN TANDEM with the KeyGuardViewMediator Code.
- // Right now, it looks like the mInputRestricted flag is INTERNAL to the
- // KeyGuardViewMediator and SPECIFICALLY set to be FALSE while the emergency
- // phone call is being made, to allow for input into the InCallScreen.
- // Having the InCallScreen judge the state of the device from this flag
- // becomes meaningless since it is always false for us. The mediator should
- // have an additional API to let this app know that it should be restricted.
- int serviceState = mCM.getServiceState();
- return ((serviceState == ServiceState.STATE_EMERGENCY_ONLY) ||
- (serviceState == ServiceState.STATE_OUT_OF_SERVICE) ||
- (mApp.getKeyguardManager().inKeyguardRestrictedInputMode()));
- }
-
- /**
- * Posts a handler message telling the InCallScreen to close
- * the OTA failure notice after the specified delay.
- * @see OtaUtils.otaShowProgramFailureNotice
- */
- /* package */ void requestCloseOtaFailureNotice(long timeout) {
- if (DBG) log("requestCloseOtaFailureNotice() with timeout: " + timeout);
- mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_OTA_FAILURE_NOTICE, timeout);
-
- // TODO: we probably ought to call removeMessages() for this
- // message code in either onPause or onResume, just to be 100%
- // sure that the message we just posted has no way to affect a
- // *different* call if the user quickly backs out and restarts.
- // (This is also true for requestCloseSpcErrorNotice() below, and
- // probably anywhere else we use mHandler.sendEmptyMessageDelayed().)
- }
-
- /**
- * Posts a handler message telling the InCallScreen to close
- * the SPC error notice after the specified delay.
- * @see OtaUtils.otaShowSpcErrorNotice
- */
- /* package */ void requestCloseSpcErrorNotice(long timeout) {
- if (DBG) log("requestCloseSpcErrorNotice() with timeout: " + timeout);
- mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_SPC_ERROR_NOTICE, timeout);
- }
-
- public boolean isOtaCallInActiveState() {
- if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
- || ((mApp.cdmaOtaScreenState != null)
- && (mApp.cdmaOtaScreenState.otaScreenState ==
- CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Handle OTA Call End scenario when display becomes dark during OTA Call
- * and InCallScreen is in pause mode. CallNotifier will listen for call
- * end indication and call this api to handle OTA Call end scenario
- */
- public void handleOtaCallEnd() {
- if (DBG) log("handleOtaCallEnd entering");
- if (((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
- || ((mApp.cdmaOtaScreenState != null)
- && (mApp.cdmaOtaScreenState.otaScreenState !=
- CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))
- && ((mApp.cdmaOtaProvisionData != null)
- && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
- if (DBG) log("handleOtaCallEnd - Set OTA Call End stater");
- setInCallScreenMode(InCallScreenMode.OTA_ENDED);
- updateScreen();
- }
- }
-
- public boolean isOtaCallInEndState() {
- return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED);
- }
-
-
- /**
- * Upon resuming the in-call UI, check to see if an OTASP call is in
- * progress, and if so enable the special OTASP-specific UI.
- *
- * TODO: have a simple single flag in InCallUiState for this rather than
- * needing to know about all those mApp.cdma*State objects.
- *
- * @return true if any OTASP-related UI is active
- */
- private boolean checkOtaspStateOnResume() {
- // If there's no OtaUtils instance, that means we haven't even tried
- // to start an OTASP call (yet), so there's definitely nothing to do here.
- if (mApp.otaUtils == null) {
- if (DBG) log("checkOtaspStateOnResume: no OtaUtils instance; nothing to do.");
- return false;
- }
-
- if ((mApp.cdmaOtaScreenState == null) || (mApp.cdmaOtaProvisionData == null)) {
- // Uh oh -- something wrong with our internal OTASP state.
- // (Since this is an OTASP-capable device, these objects
- // *should* have already been created by PhoneApp.onCreate().)
- throw new IllegalStateException("checkOtaspStateOnResume: "
- + "app.cdmaOta* objects(s) not initialized");
- }
-
- // The PhoneApp.cdmaOtaInCallScreenUiState instance is the
- // authoritative source saying whether or not the in-call UI should
- // show its OTASP-related UI.
-
- OtaUtils.CdmaOtaInCallScreenUiState.State cdmaOtaInCallScreenState =
- mApp.otaUtils.getCdmaOtaInCallScreenUiState();
- // These states are:
- // - UNDEFINED: no OTASP-related UI is visible
- // - NORMAL: OTASP call in progress, so show in-progress OTASP UI
- // - ENDED: OTASP call just ended, so show success/failure indication
-
- boolean otaspUiActive =
- (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL)
- || (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
-
- if (otaspUiActive) {
- // Make sure the OtaUtils instance knows about the InCallScreen's
- // OTASP-related UI widgets.
- //
- // (This call has no effect if the UI widgets have already been set up.
- // It only really matters the very first time that the InCallScreen instance
- // is onResume()d after starting an OTASP call.)
- mApp.otaUtils.updateUiWidgets(this, mInCallTouchUi, mCallCard);
-
- // Also update the InCallScreenMode based on the cdmaOtaInCallScreenState.
-
- if (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL) {
- if (DBG) log("checkOtaspStateOnResume - in OTA Normal mode");
- setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
- } else if (cdmaOtaInCallScreenState ==
- OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED) {
- if (DBG) log("checkOtaspStateOnResume - in OTA END mode");
- setInCallScreenMode(InCallScreenMode.OTA_ENDED);
- }
-
- // TODO(OTASP): we might also need to go into OTA_ENDED mode
- // in one extra case:
- //
- // else if (mApp.cdmaOtaScreenState.otaScreenState ==
- // CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG) {
- // if (DBG) log("checkOtaspStateOnResume - set OTA END Mode");
- // setInCallScreenMode(InCallScreenMode.OTA_ENDED);
- // }
-
- } else {
- // OTASP is not active; reset to regular in-call UI.
-
- if (DBG) log("checkOtaspStateOnResume - Set OTA NORMAL Mode");
- setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
-
- if (mApp.otaUtils != null) {
- mApp.otaUtils.cleanOtaScreen(false);
- }
- }
-
- // TODO(OTASP):
- // The original check from checkIsOtaCall() when handling ACTION_MAIN was this:
- //
- // [ . . . ]
- // else if (action.equals(intent.ACTION_MAIN)) {
- // if (DBG) log("checkIsOtaCall action ACTION_MAIN");
- // boolean isRingingCall = mCM.hasActiveRingingCall();
- // if (isRingingCall) {
- // if (DBG) log("checkIsOtaCall isRingingCall: " + isRingingCall);
- // return false;
- // } else if ((mApp.cdmaOtaInCallScreenUiState.state
- // == CdmaOtaInCallScreenUiState.State.NORMAL)
- // || (mApp.cdmaOtaInCallScreenUiState.state
- // == CdmaOtaInCallScreenUiState.State.ENDED)) {
- // if (DBG) log("action ACTION_MAIN, OTA call already in progress");
- // isOtaCall = true;
- // } else {
- // if (mApp.cdmaOtaScreenState.otaScreenState !=
- // CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED) {
- // if (DBG) log("checkIsOtaCall action ACTION_MAIN, "
- // + "OTA call in progress with UNDEFINED");
- // isOtaCall = true;
- // }
- // }
- // }
- //
- // Also, in internalResolveIntent() we used to do this:
- //
- // if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
- // || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)) {
- // // If in OTA Call, update the OTA UI
- // updateScreen();
- // return;
- // }
- //
- // We still need more cleanup to simplify the mApp.cdma*State objects.
-
- return otaspUiActive;
- }
-
- /**
- * Updates and returns the InCallControlState instance.
- */
- public InCallControlState getUpdatedInCallControlState() {
- if (VDBG) log("getUpdatedInCallControlState()...");
- mInCallControlState.update();
- return mInCallControlState;
- }
-
- public void resetInCallScreenMode() {
- if (DBG) log("resetInCallScreenMode: setting mode to UNDEFINED...");
- setInCallScreenMode(InCallScreenMode.UNDEFINED);
- }
-
- /**
- * Updates the onscreen hint displayed while the user is dragging one
- * of the handles of the RotarySelector widget used for incoming
- * calls.
- *
- * @param hintTextResId resource ID of the hint text to display,
- * or 0 if no hint should be visible.
- * @param hintColorResId resource ID for the color of the hint text
- */
- /* package */ void updateIncomingCallWidgetHint(int hintTextResId, int hintColorResId) {
- if (VDBG) log("updateIncomingCallWidgetHint(" + hintTextResId + ")...");
- if (mCallCard != null) {
- mCallCard.setIncomingCallWidgetHint(hintTextResId, hintColorResId);
- mCallCard.updateState(mCM);
- // TODO: if hintTextResId == 0, consider NOT clearing the onscreen
- // hint right away, but instead post a delayed handler message to
- // keep it onscreen for an extra second or two. (This might make
- // the hint more helpful if the user quickly taps one of the
- // handles without dragging at all...)
- // (Or, maybe this should happen completely within the RotarySelector
- // widget, since the widget itself probably wants to keep the colored
- // arrow visible for some extra time also...)
- }
- }
-
-
- /**
- * Used when we need to update buttons outside InCallTouchUi's updateInCallControls() along
- * with that method being called. CallCard may call this too because it doesn't have
- * enough information to update buttons inside itself (more specifically, the class cannot
- * obtain mInCallControllState without some side effect. See also
- * {@link #getUpdatedInCallControlState()}. We probably don't want a method like
- * getRawCallControlState() which returns raw intance with no side effect just for this
- * corner case scenario)
- *
- * TODO: need better design for buttons outside InCallTouchUi.
- */
- /* package */ void updateButtonStateOutsideInCallTouchUi() {
- if (mCallCard != null) {
- mCallCard.setSecondaryCallClickable(mInCallControlState.canSwap);
- }
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.dispatchPopulateAccessibilityEvent(event);
- mCallCard.dispatchPopulateAccessibilityEvent(event);
- return true;
- }
-
- /**
- * Manually handle configuration changes.
- *
- * Originally android:configChanges was set to "orientation|keyboardHidden|uiMode"
- * in order "to make sure the system doesn't destroy and re-create us due to the
- * above config changes". However it is currently set to "keyboardHidden" since
- * the system needs to handle rotation when inserted into a compatible cardock.
- * Even without explicitly handling orientation and uiMode, the app still runs
- * and does not drop the call when rotated.
- *
- */
- public void onConfigurationChanged(Configuration newConfig) {
- if (DBG) log("onConfigurationChanged: newConfig = " + newConfig);
-
- // Note: At the time this function is called, our Resources object
- // will have already been updated to return resource values matching
- // the new configuration.
-
- // Watch out: we *can* still get destroyed and recreated if a
- // configuration change occurs that is *not* listed in the
- // android:configChanges attribute. TODO: Any others we need to list?
-
- super.onConfigurationChanged(newConfig);
-
- // Nothing else to do here, since (currently) the InCallScreen looks
- // exactly the same regardless of configuration.
- // (Specifically, we'll never be in landscape mode because we set
- // android:screenOrientation="portrait" in our manifest, and we don't
- // change our UI at all based on newConfig.keyboardHidden or
- // newConfig.uiMode.)
-
- // TODO: we do eventually want to handle at least some config changes, such as:
- boolean isKeyboardOpen = (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO);
- if (DBG) log(" - isKeyboardOpen = " + isKeyboardOpen);
- boolean isLandscape = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
- if (DBG) log(" - isLandscape = " + isLandscape);
- if (DBG) log(" - uiMode = " + newConfig.uiMode);
- // See bug 2089513.
- }
-
- /**
- * Handles an incoming RING event from the telephony layer.
- */
- private void onIncomingRing() {
- if (DBG) log("onIncomingRing()...");
- // IFF we're visible, forward this event to the InCallTouchUi
- // instance (which uses this event to drive the animation of the
- // incoming-call UI.)
- if (mIsForegroundActivity && (mInCallTouchUi != null)) {
- mInCallTouchUi.onIncomingRing();
- }
- }
-
- /**
- * Handles a "new ringing connection" event from the telephony layer.
- *
- * This event comes in right at the start of the incoming-call sequence,
- * exactly once per incoming call.
- *
- * Watch out: this won't be called if InCallScreen isn't ready yet,
- * which typically happens for the first incoming phone call (even before
- * the possible first outgoing call).
- */
- private void onNewRingingConnection() {
- if (DBG) log("onNewRingingConnection()...");
-
- // We use this event to reset any incoming-call-related UI elements
- // that might have been left in an inconsistent state after a prior
- // incoming call.
- // (Note we do this whether or not we're the foreground activity,
- // since this event comes in *before* we actually get launched to
- // display the incoming-call UI.)
-
- // If there's a "Respond via SMS" popup still around since the
- // last time we were the foreground activity, make sure it's not
- // still active(!) since that would interfere with *this* incoming
- // call.
- // (Note that we also do this same check in onResume(). But we
- // need it here too, to make sure the popup gets reset in the case
- // where a call-waiting call comes in while the InCallScreen is
- // already in the foreground.)
- mRespondViaSmsManager.dismissPopup(); // safe even if already dismissed
- }
-
- /**
- * Enables or disables the status bar "window shade" based on the current situation.
- */
- private void updateExpandedViewState() {
- if (mIsForegroundActivity) {
- // We should not enable notification's expanded view on RINGING state.
- mApp.notificationMgr.statusBarHelper.enableExpandedView(
- mCM.getState() != PhoneConstants.State.RINGING);
- } else {
- mApp.notificationMgr.statusBarHelper.enableExpandedView(true);
- }
- }
-
- private void log(String msg) {
- Log.d(LOG_TAG, msg);
- }
-
- /**
- * Requests to remove provider info frame after having
- * {@link #PROVIDER_INFO_TIMEOUT}) msec delay.
- */
- /* package */ void requestRemoveProviderInfoWithDelay() {
- // Remove any zombie messages and then send a message to
- // self to remove the provider info after some time.
- mHandler.removeMessages(EVENT_HIDE_PROVIDER_INFO);
- Message msg = Message.obtain(mHandler, EVENT_HIDE_PROVIDER_INFO);
- mHandler.sendMessageDelayed(msg, PROVIDER_INFO_TIMEOUT);
- if (DBG) {
- log("Requested to remove provider info after " + PROVIDER_INFO_TIMEOUT + " msec.");
- }
- }
-
- /**
- * Indicates whether or not the QuickResponseDialog is currently showing in the call screen
- */
- public boolean isQuickResponseDialogShowing() {
- return mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
- }
}